import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import SearchModal, { SearchEntry } from './SearchModal';

interface BookIndexProps {
  title: string;
  logoText: string;
  pages: BookPage[];
}

function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .trim();
}

export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
  const pageNums = useMemo(() => pages.map(p => p.n), [pages]);
  const total = pageNums.length;

  // Single DOMParser pass per page: build search index then inject permalink buttons.
  // Both outputs are stable memos so React's dangerouslySetInnerHTML diffing never
  // strips the injected buttons on re-render.
  const { processedPages, searchIndex } = useMemo(() => {
    const parser = new DOMParser();
    const slugCount: Record<string, number> = {};
    const searchIndex: SearchEntry[] = [];

    const processedPages = pages.map(({ n, content }) => {
      const doc = parser.parseFromString(content, 'text/html');

      // 1. Index text blocks before mutation so heading textContent has no '#' noise
      let currentHeading = '';
      doc.body.querySelectorAll('h1, h2, h3, p, li, blockquote, td').forEach(el => {
        if (/^H[1-3]$/.test(el.tagName)) {
          currentHeading = el.textContent?.trim() || '';
        } else {
          const text = el.textContent?.trim() || '';
          if (text.length > 20) {
            searchIndex.push({ pageNum: n, headingContext: currentHeading, text });
          }
        }
      });

      // 2. Add IDs and permalink buttons to headings
      doc.querySelectorAll('h1, h2, h3').forEach(heading => {
        const baseSlug = slugify(heading.textContent || '');
        if (!baseSlug) return;
        slugCount[baseSlug] = (slugCount[baseSlug] || 0) + 1;
        const id = slugCount[baseSlug] === 1 ? baseSlug : `${baseSlug}-${slugCount[baseSlug] - 1}`;
        heading.id = id;
        const btn = doc.createElement('button');
        btn.className = 'heading-link';
        btn.setAttribute('aria-label', 'Copy permalink');
        btn.textContent = '#';
        heading.insertBefore(btn, heading.firstChild);
      });

      return { n, content: doc.body.innerHTML };
    });

    return { processedPages, searchIndex };
  }, [pages]);

  const [currentIdx, setCurrentIdx] = useState(() => {
    const m = window.location.hash.match(/^#page-(\d+)$/);
    if (m) {
      const idx = pageNums.indexOf(parseInt(m[1], 10));
      if (idx !== -1) return idx;
    }
    return 0;
  });

  const [isSearchOpen, setIsSearchOpen] = useState(false);

  const contentRef = useRef<HTMLDivElement>(null);

  const goTo = useCallback((n: number, smooth: boolean, push: boolean) => {
    const idx = pageNums.indexOf(n);
    if (idx === -1) return;
    const sec = document.getElementById(`page-${n}`);
    if (!sec) return;
    sec.scrollIntoView({ behavior: smooth ? 'smooth' : 'instant', block: 'start' });
    setCurrentIdx(idx);
    if (push) history.pushState(null, '', `#page-${n}`);
    else history.replaceState(null, '', `#page-${n}`);
  }, [pageNums]);

  // Scroll active sidebar item into view when page changes
  useEffect(() => {
    document.querySelector(`#page-list li[data-page="${pageNums[currentIdx]}"]`)
      ?.scrollIntoView({ block: 'nearest' });
  }, [currentIdx, pageNums]);

  // Initial scroll to hash page
  useEffect(() => {
    const n = pageNums[currentIdx];
    goTo(n, false, false);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  // Scroll tracking — update currentIdx as user scrolls
  useEffect(() => {
    const container = contentRef.current;
    if (!container) return;
    const handleScroll = () => {
      const top = container.getBoundingClientRect().top;
      let found = 0;
      for (let i = 0; i < pageNums.length; i++) {
        const sec = document.getElementById(`page-${pageNums[i]}`);
        if (sec && sec.getBoundingClientRect().top - top <= 40) found = i;
        else break;
      }
      setCurrentIdx(prev => {
        if (found === prev) return prev;
        history.replaceState(null, '', `#page-${pageNums[found]}`);
        return found;
      });
    };
    container.addEventListener('scroll', handleScroll, { passive: true });
    return () => container.removeEventListener('scroll', handleScroll);
  }, [pageNums]);

  // Keyboard navigation (page-level) and search shortcut
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      // Ctrl+K / Ctrl+F → open search
      if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'f')) {
        e.preventDefault();
        setIsSearchOpen(true);
        return;
      }
      const tag = (e.target as HTMLElement).tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA') return;
      setCurrentIdx(prev => {
        if ((e.key === 'ArrowLeft' || e.key === 'ArrowUp') && prev > 0) {
          goTo(pageNums[prev - 1], true, true);
        }
        if ((e.key === 'ArrowRight' || e.key === 'ArrowDown') && prev < total - 1) {
          goTo(pageNums[prev + 1], true, true);
        }
        return prev;
      });
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [goTo, pageNums, total]);

  // Browser back/forward
  useEffect(() => {
    const handlePopState = () => {
      const hash = location.hash.slice(1);
      const pageM = hash.match(/^page-(\d+)$/);
      if (pageM) {
        const n = parseInt(pageM[1], 10);
        if (pageNums.includes(n)) goTo(n, false, false);
      } else if (hash) {
        const target = document.getElementById(hash);
        if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }
    };
    window.addEventListener('popstate', handlePopState);
    return () => window.removeEventListener('popstate', handlePopState);
  }, [goTo, pageNums]);

  // Event delegation for heading permalink buttons
  useEffect(() => {
    const container = contentRef.current;
    if (!container) return;
    const handleClick = (e: MouseEvent) => {
      const btn = (e.target as Element).closest('.heading-link') as HTMLButtonElement | null;
      if (!btn) return;
      const id = btn.closest('h1, h2, h3')?.id;
      if (!id) return;
      e.preventDefault();
      const url = `${window.location.origin}${window.location.pathname}#${id}`;
      history.pushState(null, '', `#${id}`);
      navigator.clipboard.writeText(url).catch(() => {
        const ta = document.createElement('textarea');
        ta.value = url;
        document.body.appendChild(ta);
        ta.select();
        document.execCommand('copy');
        document.body.removeChild(ta);
      });
      const prev = btn.textContent!;
      btn.textContent = '✓';
      btn.classList.add('heading-link--copied');
      setTimeout(() => {
        btn.textContent = prev;
        btn.classList.remove('heading-link--copied');
      }, 1500);
    };
    container.addEventListener('click', handleClick);
    return () => container.removeEventListener('click', handleClick);
  }, []);

  // Scroll to heading hash on initial load
  useEffect(() => {
    const hash = window.location.hash.slice(1);
    if (hash && !/^page-\d+$/.test(hash)) {
      const target = document.getElementById(hash);
      if (target) target.scrollIntoView({ behavior: 'instant', block: 'start' });
    }
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const currentPage = pageNums[currentIdx];

  return (
    <>
      <header>
        <div className="logo">{logoText}</div>
        <div className="toolbar">
          <button className="tab" onClick={() => setIsSearchOpen(true)} title="Search (Ctrl+K)">
            Search
          </button>
          <span id="page-indicator">{currentIdx + 1} / {total}</span>
        </div>
      </header>

      <div id="layout">
        <nav id="sidebar">
          <div id="sidebar-header">
            <div
              className="section-title"
              style={{ marginBottom: 0, borderBottom: 'none', paddingBottom: 0 }}
            >
              <span className="icon">✦</span> Pages
            </div>
            <p>{pages.length} pages</p>
          </div>
          <ul id="page-list">
            {pages.map(({ n }) => (
              <li key={n} data-page={n} className={n === currentPage ? 'active' : undefined}>
                <button onClick={() => goTo(n, false, true)}>
                  <span className="page-num">{n}</span>
                  <span>Page {n}</span>
                </button>
              </li>
            ))}
          </ul>
        </nav>

        <div id="main">
          <div id="nav">
            <button
              id="btn-prev"
              className="tab"
              disabled={currentIdx === 0}
              onClick={() => goTo(pageNums[currentIdx - 1], true, true)}
            >
              &#8592; Prev
            </button>
            <span id="page-title">Page {currentPage}</span>
            <button
              id="btn-next"
              className="tab"
              disabled={currentIdx === total - 1}
              onClick={() => goTo(pageNums[currentIdx + 1], true, true)}
            >
              Next &#8594;
            </button>
          </div>
          <div id="content" ref={contentRef}>
            {processedPages.map(({ n, content }) => (
              <section
                key={n}
                id={`page-${n}`}
                className="page-section"
                dangerouslySetInnerHTML={{ __html: content }}
              />
            ))}
          </div>
        </div>
      </div>

      {isSearchOpen && (
        <SearchModal
          searchIndex={searchIndex}
          onNavigate={n => goTo(n, false, true)}
          onClose={() => setIsSearchOpen(false)}
        />
      )}
    </>
  );
}
