/* deep-dive.jsx — Optional "Deep Dive" panel for the Learn view.
 *
 * Renders nothing if the current topic isn't in the server-side manifest.
 * The manifest is fetched once on module load (cached on window). Each
 * concept's guide is fetched on first open, parsed with marked,
 * sanitized with DOMPurify, and rendered inline.
 *
 * Design choices worth knowing:
 *
 *   - Single source of truth: the server's GET /api/study-guide/
 *     manifest. We do NOT ship a static mirror — the button stays
 *     hidden until the manifest resolves (avoids stale/missing-guide
 *     mismatches between client and server).
 *
 *   - We sanitize even though the content is author-trusted, because
 *     marked outputs raw HTML for any HTML embedded in the markdown and
 *     a typo'd <script> tag in a future guide would be a footgun.
 *
 *   - We intentionally DO NOT participate in the inline term-highlighter
 *     or the selection-bubble note system. Grafting those onto markdown
 *     content is real risk for no stated benefit, and the spec is
 *     additive-only.
 *
 *   - The panel is in-flow (it pushes content below it), not a modal
 *     overlay. Long guides become scrollable via the page itself.
 */

(function(){
  // Module-scoped caches
  const renderCache = new Map();   // topicId -> { frag, title, siblings }
  const inflight = new Map();      // topicId -> Promise (de-dupes concurrent opens)

  // ───────── Manifest: fetched once, exposed as a promise ─────────
  // We start the fetch at module-load so it overlaps with React mount.
  // The manifest is small (just an array of topicIds), one HTTP call.
  let manifestPromise = null;
  function getManifest() {
    if (manifestPromise) return manifestPromise;
    manifestPromise = fetch('/api/study-guide/')
      .then(r => r.ok ? r.json() : { topics: [] })
      .then(d => new Set(Array.isArray(d.topics) ? d.topics : []))
      .catch(() => new Set()); // network failure → hide all buttons; honest empty state
    return manifestPromise;
  }
  // Kick off immediately; component reads from the cached promise.
  getManifest();

  // Hook: returns null until manifest resolves, then the Set of topicIds with guides.
  function useManifest() {
    const [topics, setTopics] = React.useState(null);
    React.useEffect(() => {
      let cancelled = false;
      getManifest().then(s => { if (!cancelled) setTopics(s); });
      return () => { cancelled = true; };
    }, []);
    return topics;
  }

  // ───────── Markdown rendering ─────────
  function renderMarkdown(md) {
    if (!window.marked || !window.DOMPurify) {
      throw new Error('Markdown renderer not loaded — marked or DOMPurify missing.');
    }
    const rawHtml = window.marked.parse(md, {
      gfm: true, breaks: false, headerIds: false, mangle: false,
    });
    const clean = window.DOMPurify.sanitize(rawHtml, {
      ALLOWED_TAGS: [
        'h1','h2','h3','h4','h5','h6',
        'p','br','hr',
        'ul','ol','li',
        'strong','em','b','i','u','s','del','ins','mark','sup','sub',
        'a','code','pre',
        'blockquote',
        'table','thead','tbody','tr','th','td',
        'span','div'
      ],
      ALLOWED_ATTR: ['href','title','target','rel','colspan','rowspan','align'],
      ADD_ATTR: ['target','rel'],
    });
    const tmp = document.createElement('template');
    tmp.innerHTML = clean;
    // Post-process: force external links to open in a new tab + noopener
    tmp.content.querySelectorAll('a[href]').forEach(a => {
      const href = a.getAttribute('href') || '';
      if (/^https?:/i.test(href)) {
        a.setAttribute('target', '_blank');
        a.setAttribute('rel', 'noopener noreferrer');
      }
    });
    return tmp.content;
  }

  async function fetchAndRender(topicId) {
    if (renderCache.has(topicId)) return renderCache.get(topicId);
    if (inflight.has(topicId)) return inflight.get(topicId);

    const p = (async () => {
      const r = await fetch('/api/study-guide/' + encodeURIComponent(topicId));
      if (r.status === 404) {
        throw new Error('No guide available for this topic on the server.');
      }
      if (!r.ok) {
        throw new Error('Server returned ' + r.status);
      }
      const data = await r.json();
      const frag = renderMarkdown(data.markdown || '');
      const entry = {
        frag,
        title: data.title || null,
        siblings: Array.isArray(data.siblings) ? data.siblings : [],
      };
      renderCache.set(topicId, entry);
      return entry;
    })();

    inflight.set(topicId, p);
    try { return await p; } finally { inflight.delete(topicId); }
  }

  // Resolve sibling topicIds to human-readable titles via window.CONCEPTS.
  // Used in the "Also covers …" note at the top of the panel.
  function siblingTitles(siblings) {
    const all = window.CONCEPTS || [];
    return siblings.map(tid => {
      const c = all.find(x => x.topicId === tid);
      return c ? c.title : tid;
    });
  }

  // ───────── Component ─────────
  function DeepDivePanel({ topicId, conceptTitle }) {
    const manifest = useManifest();
    const [open, setOpen] = React.useState(false);
    const [status, setStatus] = React.useState('idle'); // idle | loading | ready | error
    const [error, setError] = React.useState(null);
    const [meta, setMeta] = React.useState({ title: null, siblings: [] });
    const contentRef = React.useRef(null);

    // Reset when topic changes
    React.useEffect(() => {
      setOpen(false);
      setStatus('idle');
      setError(null);
      setMeta({ title: null, siblings: [] });
    }, [topicId]);

    // On open: fetch (or cache) + inject the rendered fragment
    React.useEffect(() => {
      if (!open) return;
      let cancelled = false;
      setStatus('loading');
      setError(null);

      fetchAndRender(topicId)
        .then(({ frag, title, siblings }) => {
          if (cancelled) return;
          setMeta({ title, siblings });
          requestAnimationFrame(() => {
            if (cancelled || !contentRef.current) return;
            contentRef.current.innerHTML = '';
            contentRef.current.appendChild(frag.cloneNode(true));
            setStatus('ready');
          });
        })
        .catch(e => {
          if (cancelled) return;
          setError(e.message || String(e));
          setStatus('error');
        });

      return () => { cancelled = true; };
    }, [open, topicId]);

    // Manifest still loading, or topic not in manifest → render nothing
    if (manifest === null) return null;
    if (!manifest.has(topicId)) return null;

    const headerTitle = meta.title || conceptTitle || 'Deep Dive';
    const sibs = siblingTitles(meta.siblings);

    return (
      React.createElement(React.Fragment, null,
        React.createElement('button', {
          type: 'button',
          className: 'dd-trigger',
          'aria-expanded': open,
          'aria-controls': 'dd-panel-' + topicId,
          onClick: () => setOpen(o => !o),
        },
          React.createElement('span', { className: 'dd-trigger-icon' }, '📖'),
          React.createElement('span', null, open ? 'Hide Deep Dive' : 'Deep Dive'),
          React.createElement('span', { className: 'dd-trigger-meta' }, open ? '' : '— study guide')
        ),
        open && React.createElement('div', {
          className: 'dd-panel',
          id: 'dd-panel-' + topicId,
          role: 'region',
          'aria-label': 'Deep dive study guide for ' + (conceptTitle || topicId),
        },
          React.createElement('div', { className: 'dd-panel-head' },
            React.createElement('span', { className: 'dd-panel-eye' }, 'Deep Dive'),
            React.createElement('span', { className: 'dd-panel-title' }, headerTitle),
            React.createElement('button', {
              type: 'button',
              className: 'dd-panel-close',
              onClick: () => setOpen(false),
              'aria-label': 'Close deep dive',
            }, '✕')
          ),
          // Siblings note — only when the guide is shared with other topics
          status === 'ready' && sibs.length > 0 && React.createElement('div', {
            className: 'dd-siblings',
          },
            'This deep dive also covers: ',
            React.createElement('span', { className: 'dd-siblings-list' }, sibs.join(' · '))
          ),
          status === 'loading' && React.createElement('div', { className: 'dd-state' }, 'Loading study guide…'),
          status === 'error' && React.createElement('div', { className: 'dd-state dd-state-err' },
            'Could not load study guide: ' + (error || 'unknown error')
          ),
          React.createElement('div', {
            ref: contentRef,
            className: 'dd-md',
            style: { display: status === 'ready' ? '' : 'none' },
          })
        )
      )
    );
  }

  window.DeepDivePanel = DeepDivePanel;
})();
