// Review.jsx — material viewer + flashcard review mode (SRS)

const RATES = [
{ id: 'again', lab: 'Again', iv: '<1 min', cls: 'again' },
{ id: 'hard', lab: 'Hard', iv: '<10 min', cls: 'hard' },
{ id: 'good', lab: 'Good', iv: '1 day', cls: 'good' },
{ id: 'easy', lab: 'Easy', iv: '4 days', cls: 'easy' }];


function ReviewHeader({ material, go, goBack, openTopic, openGenerate, toast, editing, setEditing }) {
  const t = topicById(material.topic);
  const [flagged, setFlagged] = React.useState(false);
  const [title, setTitle] = React.useState(material.title);
  const [complete, setComplete] = React.useState(() => getReviewed().includes(material.id));
  const [shareOpen, setShareOpen] = React.useState(false);
  React.useEffect(() => {setTitle(material.title);setFlagged(false);setComplete(getReviewed().includes(material.id));}, [material.id]);
  // Keep the button in sync if a deck auto-completes (or another view marks it).
  React.useEffect(() => {
    const onRev = () => setComplete(getReviewed().includes(material.id));
    window.addEventListener('pl-reviewed', onRev);
    return () => window.removeEventListener('pl-reviewed', onRev);
  }, [material.id]);
  const toggleComplete = () => {
    const now = toggleReviewed(material.id).includes(material.id);
    setComplete(now);
    try {window.dispatchEvent(new CustomEvent('pl-reviewed'));} catch (e) {}
    toast(now ? 'Marked complete — saved to Today' : 'Marked as not complete');
  };
  return (
    <React.Fragment>
      <div className="page-head" style={{ marginBottom: 20, alignItems: 'center' }}>
        <div>
          <button className="btn sm" onClick={goBack} style={{ marginBottom: 12, paddingLeft: 8 }}><Icon name="arrow-left" size={15} /> Back</button>
          <p className="eyebrow" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <Icon name={MAT_TYPE[material.type].icon} size={14} style={{ color: MAT_TYPE[material.type].tint }} />
            {MAT_TYPE[material.type].label} · {t ? t.name : ''}
          </p>
          <h1 contentEditable={editing} suppressContentEditableWarning spellCheck={false}
          onBlur={editing ? (e) => setTitle(e.currentTarget.textContent) : undefined}
          style={{ fontSize: 'clamp(22px,2.6vw,30px)', outline: editing ? '1px dashed var(--blue)' : 'none', borderRadius: 6, padding: editing ? '2px 6px' : 0, marginLeft: editing ? -6 : 0 }}>{title}</h1>
        </div>
        <div className="actions" style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <button className="btn sm act" onClick={toggleComplete} title={complete ? 'Mark as not complete' : 'Mark this review complete'}
            style={complete ? { background: 'color-mix(in srgb,var(--positive) 15%, var(--paper))', color: 'var(--positive)', borderColor: 'color-mix(in srgb,var(--positive) 34%, var(--line))' } : undefined}>
            <Icon name="check" size={15} /> {complete ? 'Completed' : 'Mark complete'}</button>
          <button className={'btn sm act' + (editing ? ' flag-on' : '')} onClick={() => {if (material.type === 'summary' || material.type === 'deck' || material.type === 'map') {const n = !editing;setEditing(n);toast(n ? 'Editing — click any text' : 'Changes saved');} else {toast('Opening editor…');}}}><Icon name={editing ? 'check' : 'edit'} size={15} /> {editing ? 'Done' : 'Edit'}</button>
          <button className="btn sm act" onClick={() => setShareOpen(true)} title="Share this material with a friend"><Icon name="users" size={15} /> Share</button>
          <button className="btn sm act" onClick={() => openGenerate(material.topic, material.type, null, material.id)}><Icon name="refresh" size={15} /> Regenerate</button>
          <button className={'btn sm act' + (flagged ? ' flag-on' : '')} onClick={() => {const n = !flagged;setFlagged(n);toast(n ? 'Flagged for review' : 'Flag removed');}}><Icon name="flag" size={15} /> {flagged ? 'Flagged' : 'Flag'}</button>
        </div>
      </div>
      {shareOpen && window.ShareTopicModal && t &&
      <ShareTopicModal topic={t} onClose={() => setShareOpen(false)} onShared={(n) => { if (window.setMasteryFlag) window.setMasteryFlag(material.topic, 'shared', true); setShareOpen(false); toast('Shared “' + material.title + '” with ' + n + (n === 1 ? ' friend' : ' friends')); }} />}
    </React.Fragment>);

}

function DeckReview({ material, go, openTopic, openGenerate, toast, editing, readAloud }) {
  const [cards, setCards] = React.useState(() => (FLASHCARDS[material.id] || FALLBACK_DECK).map((c) => ({ ...c })));
  const updateCard = (field, val) => setCards((cs) => cs.map((c, j) => j === i ? { ...c, [field]: val } : c));
  const [i, setI] = React.useState(0);
  const [flipped, setFlipped] = React.useState(false);
  const [ratings, setRatings] = React.useState([]);
  const done = i >= cards.length;
  const card = cards[Math.min(i, cards.length - 1)];

  // Completing the deck marks it reviewed, so Today shows it done on return.
  // Finishing a full deck also earns the "flashcard challenge" mastery step.
  React.useEffect(() => {if (done) {markReviewed(material.id);if (window.setMasteryFlag && material.topic) window.setMasteryFlag(material.topic, 'deck', true);try {window.dispatchEvent(new CustomEvent('pl-reviewed'));} catch (e) {}}}, [done, material.id]);

  const rate = React.useCallback((id) => {
    setRatings((r) => [...r, id]);
    setFlipped(false);
    setI((n) => n + 1);
    try {window.dispatchEvent(new CustomEvent('pl-step'));} catch (e) {}
  }, []);

  React.useEffect(() => {
    const onKey = (e) => {
      if (done) return;
      if (e.code === 'Space') {e.preventDefault();setFlipped((f) => !f);} else
      if (flipped && ['1', '2', '3', '4'].includes(e.key)) rate(RATES[+e.key - 1].id);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [flipped, done, rate]);

  if (done) {
    const tally = (id) => ratings.filter((r) => r === id).length;
    return (
      <div className="review-wrap fade-up" style={{ textAlign: 'center', paddingTop: 10 }}>
        <span className="itile" style={{ width: 64, height: 64, borderRadius: 18, background: 'rgba(46,122,58,.14)', color: 'var(--positive)', margin: '0 auto 18px' }}><Icon name="check" size={32} /></span>
        <h1 style={{ fontSize: 28, fontWeight: 600, letterSpacing: '-0.02em' }}>Deck complete</h1>
        <p className="t-lead" style={{ marginTop: 8 }}>You reviewed {cards.length} cards. Streak kept alive.</p>
        <div className="card pad" style={{ marginTop: 24, display: 'flex', justifyContent: 'space-around', flexWrap: 'wrap', gap: 14 }}>
          {RATES.map((r) =>
          <div key={r.id} style={{ textAlign: 'center' }}>
              <div style={{ fontSize: 24, fontWeight: 600, letterSpacing: '-0.02em' }}>{tally(r.id)}</div>
              <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.04em' }}>{r.lab}</div>
            </div>
          )}
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 24, flexWrap: 'wrap' }}>
          <button className="btn" onClick={() => {setI(0);setRatings([]);setFlipped(false);}}><Icon name="rotate" size={16} /> Review again</button>
          <button className="btn blue" onClick={() => go('today')}><Icon name="sparkles" size={16} /> Back to Today</button>
          {material.topic && openTopic && <button className="btn" onClick={() => openTopic(material.topic)}><Icon name="layers" size={16} /> {topicById(material.topic) ? topicById(material.topic).name : 'Topic'}</button>}
          <button className="btn" onClick={() => go('materials')}>Library <Icon name="arrow-right" size={16} /></button>
        </div>
      </div>);

  }

  return (
    <div className="review-wrap">
      <div className="deckbar">
        <div className="seg-dots">
          {cards.map((_, n) => <i key={n} className={n < i ? 'done' : n === i ? 'cur' : ''} />)}
        </div>
        <span className="mono" style={{ marginLeft: 'auto', fontSize: 12.5, color: 'var(--ink-2)', fontWeight: 600 }}>Card {i + 1} / {cards.length}</span>
      </div>

      {editing ?
      <div className="fc-face" style={{ alignItems: 'stretch', gap: 16, textAlign: 'left', minHeight: 'auto', padding: '30px 34px' }}>
          <div>
            <div className="ftag" style={{ position: 'static', marginBottom: 7 }}>Front · question</div>
            <div className="q" contentEditable suppressContentEditableWarning spellCheck={false}
          onBlur={(e) => updateCard('q', e.currentTarget.textContent)}
          style={{ fontSize: 20, fontWeight: 600, outline: '1px dashed var(--blue)', outlineOffset: '3px', borderRadius: 8, padding: '8px 10px', maxWidth: '100%', textAlign: 'left', cursor: 'text' }}>{card.q}</div>
          </div>
          <div style={{ borderTop: '1px dashed var(--line)' }} />
          <div>
            <div className="ftag" style={{ position: 'static', marginBottom: 7 }}>Back · answer</div>
            <div className="a" contentEditable suppressContentEditableWarning spellCheck={false}
          onBlur={(e) => updateCard('a', e.currentTarget.textContent)}
          style={{ outline: '1px dashed var(--blue)', outlineOffset: '3px', borderRadius: 8, padding: '8px 10px', maxWidth: '100%', textAlign: 'left', cursor: 'text' }}>{card.a}</div>
          </div>
        </div> :

      <div className="flashcard" onClick={() => setFlipped((f) => !f)}>
        <div className="fc-face" key={(flipped ? 'b' : 'f') + i}>
          {!flipped ?
          <React.Fragment>
              <span className="ftag">Front · {card.label || 'Card'}</span>
              <div className="q">{card.q}</div>
              {readAloud && <div className="fc-listen" onClick={(e) => e.stopPropagation()}><RaSpeakButton text={card.q} label="Read the question" /></div>}
              <span className="flip-hint"><Icon name="rotate" size={14} /> tap or press space to flip</span>
            </React.Fragment> :

          <React.Fragment>
              <span className="ftag">Back · answer</span>
              <span className="label">{card.label || 'Answer'}</span>
              <div className="a">{card.a}</div>
              {readAloud && <div className="fc-listen" onClick={(e) => e.stopPropagation()}><RaSpeakButton text={card.a} label="Read the answer" /></div>}
              <span className="flip-hint">rate how well you knew it ↓</span>
            </React.Fragment>
          }
        </div>
      </div>
      }

      {editing ?
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 18 }}>
          <button className="btn" disabled={i === 0} onClick={() => {setFlipped(false);setI((n) => Math.max(0, n - 1));}}><Icon name="arrow-left" size={16} /> Prev</button>
          <span className="mono" style={{ fontSize: 12.5, color: 'var(--ink-2)' }}>Editing card {i + 1} / {cards.length} · changes save automatically</span>
          <button className="btn" disabled={i >= cards.length - 1} onClick={() => {setFlipped(false);setI((n) => Math.min(cards.length - 1, n + 1));}}>Next <Icon name="arrow-right" size={16} /></button>
        </div> :
      flipped ?
      <div className="rate-row fade-up">
          {RATES.map((r, n) =>
        <button key={r.id} className={'rate ' + r.cls} onClick={() => rate(r.id)}>
              <span className="lab">{r.lab}</span>
              <span className="iv">{r.iv}</span>
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{n + 1}</span>
            </button>
        )}
        </div> :

      <button className="btn blue block lg" style={{ marginTop: 18 }} onClick={() => setFlipped(true)}>
          Show answer <Icon name="arrow-right" size={16} />
        </button>
      }
    </div>);

}

function SummaryView({ material, editing, readAloud }) {
  const t = topicById(material.topic) || { name: material.title || 'Topic', notes: 0, domain: (DOMAINS[0] || {}).id };
  const preset = (window.SUMMARIES || {})[material.id];
  const [doc, setDoc] = React.useState(() => preset ? {
    head: preset.head,
    intro: preset.intro,
    secs: preset.secs.map((s) => ({ ...s })),
    links: preset.links || []
  } : {
    head: `${t.name}: the essentials`,
    intro: `This summary distills your notes on ${t.name.toLowerCase()} into the ideas most worth remembering. Each section maps back to a source note, so you can jump to the original when you need depth.`,
    secs: [
    { h: 'Core idea', b: 'A few sentences captured from your notes would appear here — the key claim, an example, and the takeaway you should be able to recall on demand.' },
    { h: 'Why it matters', b: 'A few sentences captured from your notes would appear here — the key claim, an example, and the takeaway you should be able to recall on demand.' },
    { h: 'Common pitfalls', b: 'A few sentences captured from your notes would appear here — the key claim, an example, and the takeaway you should be able to recall on demand.' }]

  });
  const eStyle = editing ? { outline: '1px dashed var(--blue)', outlineOffset: '2px', borderRadius: '6px', cursor: 'text' } : {};
  const Ed = ({ value, onSave, tag = 'div', className, style }) =>
  React.createElement(tag, {
    className, contentEditable: editing, suppressContentEditableWarning: true, spellCheck: false,
    onBlur: editing ? (e) => onSave(e.currentTarget.textContent) : undefined,
    style: { ...style, ...eStyle }
  }, value);
  const [activeKey, setActiveKey] = React.useState(null);
  const heardRef = React.useRef(false); // earns the "listen to a summary" mastery step on first playback
  const blocks = React.useMemo(() => [
    { key: 'head', text: doc.head },
    { key: 'intro', text: doc.intro },
    ...doc.secs.map((s, i) => ({ key: 'sec' + i, text: s.h + '. ' + s.b }))
  ], [doc]);
  const showPlayer = readAloud && !editing;
  const cls = (k) => showPlayer && activeKey === k ? 'ra-active' : undefined;
  React.useEffect(() => { if (!showPlayer) setActiveKey(null); }, [showPlayer]);
  return (
    <div className="review-wrap fade-up" style={{ marginLeft: 0, marginRight: 'auto' }}>
      {editing && <div className="voice-bar" style={{ background: 'color-mix(in srgb,var(--blue) 10%, var(--paper))' }}>
        <span className="mic" style={{ background: 'var(--blue)', color: '#fff', borderColor: 'var(--blue)' }}><Icon name="edit" size={15} /></span>
        <div style={{ flex: 1 }}><div className="vb-title">Editing this summary</div><div className="vb-sub">Click any heading or paragraph to change the words, then press “Done”.</div></div>
      </div>}
      <div className="card pad" style={{ padding: '32px 34px' }}>
        <div style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 18, display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="link" size={14} /> {(doc.links && doc.links.some((l) => l.file)) ? 'Generated from ' + doc.links.filter((l) => l.file).length + ' uploaded file' + (doc.links.filter((l) => l.file).length > 1 ? 's' : '') : 'Generated from ' + t.notes + ' linked notes'} · {material.updated}
        </div>
        {showPlayer && <ReadAloudPlayer blocks={blocks} topicName={t.name} materialId={material.id} onActive={(k) => { setActiveKey(k); if (k && !heardRef.current) { heardRef.current = true; if (window.setMasteryFlag && material.topic) window.setMasteryFlag(material.topic, 'summary', true); } }} />}
        <div className={cls('head')}><Ed tag="h2" value={doc.head} onSave={(v) => setDoc((d) => ({ ...d, head: v }))}
        style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', margin: '0 0 14px' }} /></div>
        <div className={cls('intro')}><Ed tag="p" className="t-body" value={doc.intro} onSave={(v) => setDoc((d) => ({ ...d, intro: v }))}
        style={{ fontSize: 15.5, lineHeight: 1.7 }} /></div>
        {doc.secs.map((s, i) =>
        <div key={i} className={cls('sec' + i)} style={{ marginTop: 22 }}>
            <Ed value={s.h} onSave={(v) => setDoc((d) => {const secs = [...d.secs];secs[i] = { ...secs[i], h: v };return { ...d, secs };})}
          style={{ fontSize: 12, fontWeight: 600, color: 'var(--blue-ink)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 8, fontFamily: 'var(--font-mono)' }} />
            <Ed tag="p" className="t-body" value={s.b} onSave={(v) => setDoc((d) => {const secs = [...d.secs];secs[i] = { ...secs[i], b: v };return { ...d, secs };})}
          style={{ fontSize: 15, lineHeight: 1.65, margin: 0 }} />
          </div>
        )}
        {doc.links && doc.links.length > 0 &&
        <div style={{ marginTop: 28, paddingTop: 18, borderTop: '1px solid var(--line)' }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 10, fontFamily: 'var(--font-mono)', display: 'flex', alignItems: 'center', gap: 7 }}><Icon name="link" size={13} /> Sources</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
            {doc.links.map((l, i) => l.file ?
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 9, fontSize: 13.5, color: 'var(--ink-2)' }}>
              <span className="thumb" style={{ width: 30, height: 30, background: 'var(--surface-2)', color: 'var(--blue-ink)', borderRadius: 8, flex: 'none' }}><Icon name="book" size={15} /></span>
              <span style={{ fontWeight: 600, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{l.label}</span>
              <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>your upload</span>
            </div> :
            <a key={i} href={l.url} target="_blank" rel="noreferrer" style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13.5, color: 'var(--blue-ink)', textDecoration: 'none' }}>
              <Icon name="arrow-up-right" size={14} style={{ flex: 'none', opacity: .7 }} /> <span style={{ textDecoration: 'underline', textUnderlineOffset: 2 }}>{l.label}</span>
            </a>
            )}
          </div>
        </div>}
      </div>
    </div>);

}

// Cache of AI-expanded concept descriptions, keyed by topic id → { label: fullText }.
window.__CM_EXPAND = window.__CM_EXPAND || {};
// Rewrite each concept's short note into a fuller, self-contained 2-sentence
// explanation via the built-in Claude helper. Returns { label: text } or null.
async function expandConceptNotes(topicName, nodes) {
  if (!(window.claude && window.claude.complete) || !nodes || !nodes.length) return null;
  const list = nodes.map((n, i) => (i + 1) + '. ' + n.label + ' — ' + (n.note || '')).join('\n');
  const prompt = 'For the topic "' + topicName + '", expand each concept\'s note into a fuller, self-contained explanation of about 2 complete sentences (roughly 28-45 words). ' +
    'Be specific and keep every name, number, date, or example from the original note — add the missing context that makes it a complete thought, never a vague paraphrase. ' +
    'Return ONLY minified JSON: {"notes":[{"label":the exact concept label,"note":the expanded explanation}]}. No markdown, no commentary.\n\nCONCEPTS:\n' + list;
  try {
    const raw = await window.claude.complete(prompt);
    const cleaned = String(raw).replace(/^[^{]*/, '').replace(/[^}]*$/, '');
    const j = JSON.parse(cleaned);
    const out = {};
    (j.notes || []).forEach((o) => { if (o && o.label) out[o.label] = o.note || ''; });
    return Object.keys(out).length ? out : null;
  } catch (e) { return null; }
}

function MapView({ material, editing, openMaterial }) {
  const t = topicById(material.topic) || { name: material.title || 'Topic', notes: 0, domain: (DOMAINS[0] || {}).id };
  const d = domainById(t.domain) || DOMAINS[0];
  const cmData = (window.CONCEPT_MAPS || {})[material.topic];
  const ownDeck = FLASHCARDS[material.id];
  const topicDeck = MATERIALS.find((m) => m.topic === material.topic && m.type === 'deck' && FLASHCARDS[m.id]);
  const deck = ownDeck || (topicDeck ? FLASHCARDS[topicDeck.id] : null);
  const noteFor = {};if (cmData) cmData.forEach((c) => {noteFor[c.label] = c.note || '';});
  const baseLabels = cmData ? cmData.map((c) => c.label) : deck ? [...new Set(deck.map((c) => c.label))] : [];
  // Label count varies with the material — use every concept node (no padding to a fixed 6).
  let init = baseLabels.slice(0, 16);
  if (init.length === 0) init = ['Foundations', 'Key terms', 'Processes', 'Examples', 'Connections', 'Recap'];
  const stageRef = React.useRef(null);
  const [labels, setLabels] = React.useState(init);
  const [centerName, setCenterName] = React.useState(t.name);
  const [pos, setPos] = React.useState(() => init.map((lab, k) => {
    const ang = (-90 + k * (360 / init.length)) * Math.PI / 180;
    return [50 + Math.cos(ang) * 36, 50 + Math.sin(ang) * 36];
  }));
  const [center, setCenter] = React.useState([50, 50]);
  const [selected, setSelected] = React.useState(null);
  React.useEffect(() => { setSelected(null); }, [material.id]);
  // Fuller, AI-expanded descriptions for every concept (cached per topic). Falls
  // back to the original short note until/unless the expansion is available.
  const [expanded, setExpanded] = React.useState(() => window.__CM_EXPAND[material.topic] || null);
  React.useEffect(() => {
    let alive = true;
    const cached = window.__CM_EXPAND[material.topic];
    if (cached) { setExpanded(cached); return; }
    setExpanded(null);
    if (cmData && cmData.length && window.claude && window.claude.complete) {
      expandConceptNotes(t.name, cmData).then((res) => {
        if (!alive || !res) return;
        window.__CM_EXPAND[material.topic] = res;
        setExpanded(res);
      });
    }
    return () => { alive = false; };
  }, [material.topic]);
  const noteText = (label, fallback) => (expanded && expanded[label]) || fallback || '';
  const sumMat = MATERIALS.find((m) => m.topic === material.topic && m.type === 'summary');
  const summary = sumMat ? (window.SUMMARIES || {})[sumMat.id] : null;
  const findInSummary = (lab) => {
    if (!summary) return null;
    const low = (lab || '').toLowerCase();
    const hit = (summary.secs || []).find((s) => (s.b || '').toLowerCase().includes(low));
    if (hit) return { h: hit.h, b: hit.b };
    if ((summary.intro || '').toLowerCase().includes(low)) return { h: 'Overview', b: summary.intro };
    return null;
  };
  const findCards = (lab) => {
    if (!deck) return [];
    const low = (lab || '').toLowerCase();
    return deck.filter((c) => (c.label || '').toLowerCase() === low || (c.q || '').toLowerCase().includes(low) || (c.a || '').toLowerCase().includes(low)).slice(0, 3);
  };
  const leafDown = (k, lab) => (e) => {
    if (editing) return;
    e.preventDefault(); e.stopPropagation();
    const rect = stageRef.current.getBoundingClientRect();
    const sx = e.clientX, sy = e.clientY, st = pos[k];
    let moved = false;
    const move = (ev) => {
      if (Math.abs(ev.clientX - sx) > 3 || Math.abs(ev.clientY - sy) > 3) moved = true;
      const nx = st[0] + (ev.clientX - sx) / rect.width * 100;
      const ny = st[1] + (ev.clientY - sy) / rect.height * 100;
      setPos((p) => p.map((x, j) => j === k ? [Math.max(4, Math.min(96, nx)), Math.max(5, Math.min(95, ny))] : x));
    };
    const up = () => {
      document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up);
      if (!moved) setSelected((s) => s === lab ? null : lab);
    };
    document.addEventListener('pointermove', move); document.addEventListener('pointerup', up);
  };
  const drag = (get, set) => (e) => {
    e.preventDefault();e.stopPropagation();
    const rect = stageRef.current.getBoundingClientRect();
    const sx = e.clientX,sy = e.clientY,st = get();
    const move = (ev) => {
      const nx = st[0] + (ev.clientX - sx) / rect.width * 100;
      const ny = st[1] + (ev.clientY - sy) / rect.height * 100;
      set([Math.max(4, Math.min(96, nx)), Math.max(5, Math.min(95, ny))]);
    };
    const up = () => {document.removeEventListener('pointermove', move);document.removeEventListener('pointerup', up);};
    document.addEventListener('pointermove', move);document.addEventListener('pointerup', up);
  };
  return (
    <div className="review-wrap fade-up">
      <div className="map-stage" ref={stageRef} style={{ display: 'block', aspectRatio: '4/3', maxHeight: 520 }}>
        <ConstellationField opacity={0.4} />
        <svg className="map-lines" viewBox="0 0 100 100" preserveAspectRatio="none">
          {pos.map((r, k) => <line key={k} x1={center[0]} y1={center[1]} x2={r[0]} y2={r[1]} stroke="var(--ink-4)" strokeWidth="1" strokeDasharray="2.5 2.5" vectorEffect="non-scaling-stroke" opacity="0.7" />)}
        </svg>
        <div className="map-node mn-center" style={{ left: center[0] + '%', top: center[1] + '%', width: 'clamp(96px,16vw,140px)', cursor: editing ? 'text' : 'grab', touchAction: 'none' }} onPointerDown={editing ? undefined : drag(() => center, setCenter)}>
          <span className="nm" contentEditable={editing} suppressContentEditableWarning spellCheck={false}
          onBlur={editing ? (e) => setCenterName(e.currentTarget.textContent) : undefined}
          style={{ fontSize: 15, outline: editing ? '1px dashed var(--blue)' : 'none', borderRadius: 4, padding: editing ? '1px 4px' : 0 }}>{centerName}</span>
        </div>
        {labels.map((lab, k) =>
        <div key={k} className="map-node mn-leaf" title={noteText(lab, noteFor[lab])} style={{ left: pos[k][0] + '%', top: pos[k][1] + '%', cursor: editing ? 'text' : 'pointer', touchAction: 'none', boxShadow: selected === lab ? '0 0 0 2px ' + d.accent : undefined, background: selected === lab ? 'color-mix(in srgb,' + d.accent + ' 14%, var(--paper))' : undefined }}
        onPointerDown={editing ? undefined : leafDown(k, lab)}>
            <span className="accent" style={{ background: d.accent }} /> <span contentEditable={editing} suppressContentEditableWarning spellCheck={false}
          onBlur={editing ? (e) => setLabels((ls) => ls.map((x, j) => j === k ? e.currentTarget.textContent : x)) : undefined}
          style={{ outline: editing ? '1px dashed var(--blue)' : 'none', borderRadius: 4, padding: editing ? '0 3px' : 0 }}>{lab}</span>
          </div>
        )}
      </div>
      <p className="t-small" style={{ textAlign: 'center', marginTop: 14 }}>{editing ? 'Editing — click any label to rename it.' : 'Concept map generated from your linked notes — drag a node to rearrange, or tap a concept to see its explanation.'}</p>
      {!editing && selected && (() => {
        const note = noteText(selected, noteFor[selected]);
        const sumSnip = findInSummary(selected);
        const cards = findCards(selected);
        return (
          <div className="card pad fade-up" style={{ marginTop: 14 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
              <span style={{ width: 11, height: 11, borderRadius: 99, background: d.accent, flex: 'none' }} />
              <span style={{ fontSize: 16.5, fontWeight: 600, letterSpacing: '-0.02em', flex: 1, minWidth: 0 }}>{selected}</span>
              {note && <RaSpeakButton text={selected + '. ' + note} label="Listen" stopLabel="Stop" className="btn sm act" />}
              <button className="icon-btn" style={{ width: 28, height: 28, border: 'none', flex: 'none' }} onClick={() => setSelected(null)}><Icon name="x" size={16} /></button>
            </div>
            {note && <p className="t-body" style={{ fontSize: 14.5, lineHeight: 1.65, margin: '10px 0 0' }}>{note}</p>}
            {sumSnip && <div style={{ marginTop: 16, paddingTop: 14, borderTop: '1px solid var(--line)' }}>
              <div className="mono" style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 7, display: 'flex', alignItems: 'center', gap: 6 }}><Icon name="book" size={12} /> From the summary · {sumSnip.h}</div>
              <p className="t-body" style={{ fontSize: 14, lineHeight: 1.6, margin: 0, color: 'var(--ink-2)' }}>{sumSnip.b}</p>
            </div>}
            {cards.length > 0 && <div style={{ marginTop: 16, paddingTop: 14, borderTop: '1px solid var(--line)' }}>
              <div className="mono" style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 9, display: 'flex', alignItems: 'center', gap: 6 }}><Icon name="cards" size={12} /> In your flashcards</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {cards.map((c, i) => <div key={i} style={{ fontSize: 13.5, lineHeight: 1.5 }}>
                  <div style={{ fontWeight: 600 }}>{c.q}</div>
                  <div style={{ color: 'var(--ink-3)', marginTop: 2 }}>{c.a}</div>
                </div>)}
              </div>
            </div>}
            {(sumMat || topicDeck) && openMaterial && <div style={{ display: 'flex', gap: 8, marginTop: 16, flexWrap: 'wrap' }}>
              {sumMat && <button className="btn sm" onClick={() => openMaterial(sumMat.id)}><Icon name="book" size={14} /> Open summary</button>}
              {topicDeck && <button className="btn sm" onClick={() => openMaterial(topicDeck.id)}><Icon name="cards" size={14} /> Open flashcards</button>}
            </div>}
            {!note && !sumSnip && cards.length === 0 && <p className="t-body" style={{ fontSize: 14, lineHeight: 1.6, margin: '10px 0 0', color: 'var(--ink-3)' }}>A key concept from this topic. Open the summary for the full explanation.</p>}
          </div>);
      })()}
      {cmData && cmData.length > 0 &&
      <div style={{ marginTop: 26 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', fontFamily: 'var(--font-mono)', marginBottom: 12, display: 'flex', alignItems: 'center', gap: 7 }}><Icon name="map" size={13} /> Key concepts</div>
          <div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fill,minmax(240px,1fr))', gap: 10 }}>
            {cmData.map((c, i) =>
          <div key={i} className="card tight" style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
                <span style={{ width: 8, height: 8, borderRadius: 99, background: d.accent, marginTop: 6, flex: 'none' }} />
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, letterSpacing: '-0.01em', flex: 1, minWidth: 0 }}>{c.label}</div>
                    <RaSpeakButton text={c.label + '. ' + noteText(c.label, c.note)} label="" stopLabel="" className="icon-btn ra-mini" />
                  </div>
                  <div style={{ fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 3 }}>{noteText(c.label, c.note)}</div>
                </div>
              </div>
          )}
          </div>
        </div>}
    </div>);

}

function ReviewScreen({ material, go, goBack, openTopic, openMaterial, openGenerate, toast, session, sessionGo, endSession, readAloud }) {
  const [editing, setEditing] = React.useState(false);
  React.useEffect(() => {setEditing(false);}, [material.id]);
  // Bump when materials are (re)generated, so an in-place regenerate of the
  // material currently on screen remounts with its fresh content.
  const [genV, setGenV] = React.useState(0);
  React.useEffect(() => {
    const onGen = () => setGenV((v) => v + 1);
    window.addEventListener('pl-generated', onGen);
    return () => window.removeEventListener('pl-generated', onGen);
  }, []);
  // Completion is now explicit: the “Mark complete” button in the header records the
  // review (a deck also auto-completes once every card has been rated).
  return (
    <div className="content-pad fade-up">
      {session && session.ids.includes(material.id) &&
        <div className="card" style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', marginBottom: 14, flexWrap: 'wrap', background: 'color-mix(in srgb,var(--blue) 7%, var(--paper))' }}>
          <span className="itile" style={{ width: 28, height: 28, borderRadius: 8, background: 'color-mix(in srgb,var(--blue) 16%, var(--paper))', color: 'var(--blue-ink)', flex: 'none' }}><Icon name="bolt" size={15} /></span>
          <span style={{ fontWeight: 600, fontSize: 13.5 }}>Review session</span>
          <div className="seg-dots" style={{ flex: 'none' }}>
            {session.ids.map((id, k) => <i key={k} className={k < session.i ? 'done' : k === session.i ? 'cur' : ''} onClick={() => sessionGo(k - session.i)} style={{ cursor: 'pointer' }} />)}
          </div>
          <span className="mono" style={{ fontSize: 12, color: 'var(--ink-2)' }}>{session.i + 1} / {session.ids.length}</span>
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
            <button className="btn sm" disabled={session.i === 0} onClick={() => sessionGo(-1)}><Icon name="arrow-left" size={14} /> Prev</button>
            {session.i < session.ids.length - 1
              ? <button className="btn sm blue" onClick={() => sessionGo(1)}>Next <Icon name="arrow-right" size={14} /></button>
              : <button className="btn sm blue" onClick={endSession}><Icon name="check" size={14} /> Finish</button>}
          </div>
        </div>}
      <ReviewHeader material={material} go={go} goBack={goBack} openTopic={openTopic} openGenerate={openGenerate} toast={toast} editing={editing} setEditing={setEditing} />
      {material.type === 'deck' ? <DeckReview key={material.id + '-' + genV} material={material} go={go} openTopic={openTopic} openGenerate={openGenerate} toast={toast} editing={editing} readAloud={readAloud} /> :
      material.type === 'summary' ? <SummaryView key={material.id + '-' + genV} material={material} editing={editing} readAloud={readAloud} /> :
      <MapView key={material.id + '-' + genV} material={material} editing={editing} openMaterial={openMaterial} />}
    </div>);

}
window.ReviewScreen = ReviewScreen;