/* global React, ReactDOM, lucide */
// MomOps Framework Reader: application shell.
// Wires the structured content store (content.js) into a responsive web
// reading experience: sticky TOC scrollspy, reading progress, in-document
// search, focus mode, and a persistent version badge + PDF download.
const { useState, useEffect, useRef, useCallback } = React;
const DATA = window.FRAMEWORK_V4;

function refreshIcons() {
  if (window.lucide && window.lucide.createIcons) {
    window.lucide.createIcons({ attrs: { 'stroke-width': 1.6 } });
  }
}

/* ── Flatten chapters into a lightweight search index ─────────────────── */
function buildIndex(chapters) {
  const rows = [];
  chapters.forEach((ch, ci) => {
    const push = (text) => { if (text) rows.push({ ci, id: ch.id, kicker: ch.kicker, title: ch.title, text }); };
    push(ch.title);
    ch.blocks.forEach((b) => {
      if (b.text) push((b.lead ? b.lead + ' ' : '') + b.text);
      if (b.label) push(b.label + ': ' + (b.text || ''));
      if (b.items) b.items.forEach((it) => push(typeof it === 'string' ? it : ((it.lead ? it.lead + ' ' : '') + it.text)));
    });
  });
  return rows;
}

function snippet(text, q) {
  const i = text.toLowerCase().indexOf(q.toLowerCase());
  if (i < 0) return text.slice(0, 120) + (text.length > 120 ? '...' : '');
  const start = Math.max(0, i - 40);
  const end = Math.min(text.length, i + q.length + 80);
  return (start > 0 ? '...' : '') + text.slice(start, end) + (end < text.length ? '...' : '');
}

/* ── Search overlay ───────────────────────────────────────────────────── */
function SearchOverlay({ index, onClose, onJump }) {
  const [q, setQ] = useState('');
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current && inputRef.current.focus(); refreshIcons(); }, []);
  useEffect(() => {
    const esc = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', esc);
    return () => window.removeEventListener('keydown', esc);
  }, [onClose]);

  const results = q.length < 2 ? [] :
    index.filter((r) => r.text.toLowerCase().includes(q.toLowerCase())).slice(0, 24);

  return (
    <div className="fw-search-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="fw-search-box">
        <div className="fw-search-input-row">
          <i data-lucide="search" style={{ width: 20, height: 20, color: 'var(--momops-ink-soft)' }}></i>
          <input ref={inputRef} value={q} placeholder="Search the framework..."
                 onChange={(e) => setQ(e.target.value)} />
          <button className="fw-btn fw-icon-btn" onClick={onClose} aria-label="Close search">
            <i className="ic" data-lucide="x"></i>
          </button>
        </div>
        <div className="fw-search-results">
          {q.length >= 2 && results.length === 0 ? (
            <div className="fw-search-empty">No matches for “{q}”.</div>
          ) : null}
          {results.map((r, i) => (
            <button key={i} onClick={() => onJump(r.id, q)}>
              <div className="r-ch">{String(r.ci + 1).padStart(2, '0')} · {r.kicker}</div>
              <div className="r-sn" dangerouslySetInnerHTML={{ __html:
                snippet(r.text, q).replace(new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'ig'),
                  '<mark class="fw-hit">$1</mark>') }} />
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ── Table of contents (sticky rail) ──────────────────────────────────── */
function TOC({ chapters, activeId, onPick }) {
  return (
    <nav className="fw-toc" aria-label="Contents">
      <div className="fw-toc-head">Contents</div>
      <ol>
        {chapters.map((ch) => (
          <li key={ch.id}>
            <a href={'#' + ch.id} className={activeId === ch.id ? 'is-active' : ''}
               onClick={() => onPick(ch.id)}>
              <span>{ch.title}</span>
            </a>
          </li>
        ))}
      </ol>
    </nav>
  );
}

/* ── App ──────────────────────────────────────────────────────────────── */
function App() {
  const { meta, chapters } = DATA;
  const index = useRef(buildIndex(chapters)).current;
  const getInitialTarget = () => {
    const params = new URLSearchParams(window.location.search);
    const requested = params.get('chapter') || window.location.hash.replace('#', '');
    if (requested === 'cover') return 'cover';
    return chapters.some((ch) => ch.id === requested) ? requested : 'cover';
  };
  const [activeId, setActiveId] = useState(getInitialTarget);
  const [progress, setProgress] = useState(0);
  const [searchOpen, setSearchOpen] = useState(false);
  const [focus, setFocus] = useState(false);
  const [notesOpen, setNotesOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [cited, setCited] = useState(false);
  const [animOn, setAnimOn] = useState(false);

  // Reading progress, scrollspy, and reveal are driven by a requestAnimationFrame
  // loop rather than scroll/IntersectionObserver callbacks, which are not
  // reliably delivered inside some embedded webviews.
  useEffect(() => {
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (!reduce) setAnimOn(true);

    const sections = ['cover', ...chapters.map((c) => c.id)]
      .map((id) => document.getElementById(id)).filter(Boolean);

    let lastY = -1;
    const tick = () => {
      const doc = document.documentElement;
      const y = doc.scrollTop || window.scrollY || 0;
      if (y === lastY) return;
      lastY = y;
      const max = doc.scrollHeight - doc.clientHeight;
      setProgress(max > 0 ? Math.min(100, (y / max) * 100) : 0);

      // Reveal anything that has entered the lower 92% of the viewport.
      const vh = window.innerHeight;
      document.querySelectorAll('.fw-reveal:not(.is-in)').forEach((el) => {
        if (el.getBoundingClientRect().top < vh * 0.92) el.classList.add('is-in');
      });

      // Scrollspy: last section whose top has crossed the 120px line.
      let current = sections[0] ? sections[0].id : 'cover';
      for (const s of sections) {
        if (s.getBoundingClientRect().top - 120 <= 0) current = s.id;
      }
      setActiveId(current);
    };
    // A scroll listener keeps it responsive in normal browsers; the interval
    // is a safety net for embedded webviews that throttle rAF / drop scroll
    // and IntersectionObserver callbacks.
    window.addEventListener('scroll', tick, { passive: true });
    const poll = setInterval(tick, 120);
    tick();

    return () => { window.removeEventListener('scroll', tick); clearInterval(poll); };
  }, [chapters]);

  // Keyboard: ⌘K / Ctrl+K and "/" open search
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setSearchOpen(true); }
      else if (e.key === '/' && !/input|textarea/i.test(document.activeElement.tagName)) { e.preventDefault(); setSearchOpen(true); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  useEffect(() => { refreshIcons(); });

  const scrollTo = useCallback((id, replaceUrl = true) => {
    const el = document.getElementById(id);
    if (el) {
      if (replaceUrl) {
        const url = new URL(window.location.href);
        if (id === 'cover') url.searchParams.delete('chapter');
        else url.searchParams.set('chapter', id);
        url.hash = id;
        window.history.replaceState({}, '', url);
      }
      window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 84, behavior: 'smooth' });
    }
  }, []);

  useEffect(() => {
    const target = getInitialTarget();
    if (target !== 'cover') setTimeout(() => scrollTo(target, false), 260);
  }, [scrollTo]);

  const onJump = useCallback((id, q) => {
    setQuery(q || '');
    setSearchOpen(false);
    setTimeout(() => scrollTo(id), 60);
  }, [scrollTo]);

  const onCite = useCallback(() => {
    const text = meta.citation;
    if (navigator.clipboard) navigator.clipboard.writeText(text).catch(() => {});
    setCited(true);
    setTimeout(() => setCited(false), 2200);
  }, [meta.citation]);

  const Interactives = window.INTERACTIVES || {};

  return (
    <div className={'fw-ground' + (focus ? ' fw-focus' : '') + (animOn ? ' fw-anim-on' : '')}>
      <div className="fw-progress"><span style={{ width: progress + '%' }}></span></div>

      {/* Top control bar */}
      <header className="fw-topbar">
        <div className="fw-topbar-inner">
          <a className="fw-brand" href="#cover" onClick={() => scrollTo('cover')}>
            <img src="assets/momops-logo.png" alt="MomOps" />
            <small>{meta.versionLabel} · {meta.date}</small>
          </a>
          <div className="fw-controls">
            <a className="fw-btn fw-btn--solid" href={meta.homeUrl}>
              <i className="ic" data-lucide="arrow-left"></i><span className="lbl">MomOps.org</span>
            </a>
            <button className="fw-btn" onClick={() => setSearchOpen(true)} aria-label="Search">
              <i className="ic" data-lucide="search"></i><span className="lbl">Search</span>
            </button>
            <button className="fw-btn" aria-pressed={focus} onClick={() => setFocus((f) => !f)}
                    title="Hide interactive layers for distraction-free reading">
              <i className="ic" data-lucide={focus ? 'layers' : 'book-open-text'}></i>
              <span className="lbl">{focus ? 'Show insights' : 'Focus read'}</span>
            </button>
            <button className="fw-btn" aria-pressed={notesOpen} onClick={() => setNotesOpen((n) => !n)}
                    title="Expand or collapse all research notes">
              <i className="ic" data-lucide="sticky-note"></i>
              <span className="lbl">{notesOpen ? 'Collapse notes' : 'Expand notes'}</span>
            </button>
            <a className="fw-btn fw-btn--accent" href={meta.printDoc}
               title="Open a print-ready, fully styled PDF of the whole framework">
              <i className="ic" data-lucide="file-down"></i><span className="lbl">Save as PDF</span>
            </a>
          </div>
        </div>
      </header>

      {/* Cover */}
      <div style={{ maxWidth: 1280, margin: '0 auto', padding: '64px 22px 0' }}>
        <Cover meta={meta} onRead={() => scrollTo(chapters[0].id)}
               onCite={onCite} citeState={cited} />
      </div>

      {/* Reading layout */}
      <div className="fw-layout">
        <TOC chapters={chapters} activeId={activeId} onPick={scrollTo} />
        <main className="fw-main">
          {chapters.map((ch, i) => (
            <Chapter key={ch.id} chapter={ch} index={i} q={query} notesOpen={notesOpen}
                     focusMode={focus}
                     Interactive={ch.interactive ? Interactives[ch.interactive] : null} />
          ))}

          <footer className="fw-footer">
            <span className="fw-version-badge">
              <span className="pip"></span>{meta.versionLabel} · {meta.date}
            </span>
            <p>
              This is a living research document. This page renders the full structured
              framework as responsive web content, and the downloadable PDF is generated
              from the same styled content. To publish a revision, update the content store,
              bump the version, and regenerate the PDF.
            </p>
            <p style={{ marginTop: 14, fontStyle: 'italic', fontFamily: 'var(--font-display)' }}>
              {meta.citation}
            </p>
            <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 20, alignItems: 'center' }}>
              <a className="fw-btn fw-btn--solid" href={meta.printDoc}>
                <i className="ic" data-lucide="file-down"></i>Save the whole framework as a styled PDF
              </a>
              <button className="fw-btn" onClick={onCite}>
                <i className="ic" data-lucide={cited ? 'check' : 'quote'}></i>
                {cited ? 'Citation copied' : 'Copy citation'}
              </button>
            </div>
            <p style={{ marginTop: 16, fontSize: '0.9rem' }}>
              Prefer the portable file? <a href={meta.pdf} download style={{ color: 'var(--momops-teal-deep)', textDecoration: 'underline' }}>Download the styled PDF</a>.
            </p>
          </footer>
        </main>
      </div>

      {searchOpen ? <SearchOverlay index={index} onClose={() => setSearchOpen(false)} onJump={onJump} /> : null}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
