/* === PHYSL 210 — Lessons === */
const { useState: useSL, useEffect: useEL, useRef: useRL } = React;

/* ────────────────────────────────────────────────────────────
   Helpers
──────────────────────────────────────────────────────────── */

/* PDFs from older Word/PowerPoint files encode bullets in the Windows
   Symbol/Wingdings font, which pdftotext maps to the Unicode PUA range
   (U+F000–U+F0FF). No web font has those glyphs → they render as □ boxes.
   Remap them to real Unicode before any further processing. */
const _PUA_RE  = /[]/g;
const _PUA_MAP = {
  '': '•',  // Symbol 0xB7 → bullet
  '': '▪',  // Symbol 0xA7 → small black square
  '': '►',  // Symbol 0xD8 → right-pointing triangle
  '': '→',  // Symbol 0x76 → right arrow
  '': '✓',  // Symbol 0xFC → check mark
  '': '▶',  // Symbol 0xA8 → right-pointing pointer
  '': '◆',  // Symbol 0xD6 → diamond
};
function normText(s) { return s.replace(_PUA_RE, ch => _PUA_MAP[ch] || '•'); }

const BULLET_RE   = /^[•▪►▶➢❖▸→◦✓◆]\s*|^o\s+/;
const LONE_BULLET = /^[•▪►▶➢❖▸→◦✓◆o]$/;

function mergeAndParse(items) {
  const merged = [];
  for (const raw of items) {
    const s = normText(raw.trim());
    if (!s || LONE_BULLET.test(s)) continue;
    const isBullet   = BULLET_RE.test(s);
    const isNumbered = /^\d+[.)]\s/.test(s);
    const isCap      = /^[A-Z]/.test(s);
    const isContinuation = !isBullet && !isNumbered && !isCap;
    if (isContinuation && merged.length > 0) {
      merged[merged.length - 1] += ' ' + s;
    } else {
      merged.push(s);
    }
  }

  const out = [];
  let bullets = [];
  const flush = () => {
    if (!bullets.length) return;
    out.push({ type: 'ul', items: [...bullets] });
    bullets = [];
  };

  for (const s of merged) {
    const isH4      = /^[A-Z]/.test(s) && s.endsWith(':') && s.length < 90;
    const isBullet  = BULLET_RE.test(s);
    const isNum     = /^\d+[.)]\s/.test(s);
    if (isH4) {
      flush();
      out.push({ type: 'h4', text: s });
    } else if (isBullet) {
      bullets.push(s.replace(BULLET_RE, '').trim());
    } else if (isNum) {
      bullets.push(s);
    } else {
      flush();
      out.push({ type: 'p', text: s });
    }
  }
  flush();
  return out;
}

function shortTocLabel(heading) {
  const m = heading.match(/Lecture (\d+) recording (\d+): (.+)/);
  if (!m) return heading;
  const title = m[3].length > 38 ? m[3].slice(0, 38) + '…' : m[3];
  return `${m[1]}.${m[2]}  ${title}`;
}

/* ────────────────────────────────────────────────────────────
   Notes panel (TOC sidebar + scrollable section cards)
──────────────────────────────────────────────────────────── */
function NotesPanelBody({ mod, scrollToId }) {
  const c        = modColor(mod.hue);
  /* Module study notes: rich recording-level SOURCE_NOTES where available, else the
     concise MODULES.notes regrouped by topic (TOPIC_NOTES) so every module has notes. */
  const tnotes   = (typeof TOPIC_NOTES !== 'undefined' && TOPIC_NOTES[mod.id]) || [];
  const sections = (typeof SOURCE_NOTES !== 'undefined' && SOURCE_NOTES[mod.id])
    || tnotes.map(g => ({ id: g.anchor, heading: g.label, items: g.paras }));
  const [activeId, setActiveId] = useSL(sections[0]?.id || '');
  const contentRef = useRL(null);

  /* track active section on scroll */
  useEL(() => {
    const container = contentRef.current;
    if (!container) return;
    const onScroll = () => {
      const els = [...container.querySelectorAll('[data-sec]')];
      const top = container.getBoundingClientRect().top;
      for (let i = els.length - 1; i >= 0; i--) {
        if (els[i].getBoundingClientRect().top <= top + 120) {
          setActiveId(els[i].dataset.sec);
          return;
        }
      }
      if (els.length) setActiveId(els[0].dataset.sec);
    };
    container.addEventListener('scroll', onScroll, { passive: true });
    return () => container.removeEventListener('scroll', onScroll);
  }, [sections]);

  const scrollTo = (id) => {
    const el = contentRef.current?.querySelector(`[data-sec="${id}"]`);
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
    setActiveId(id);
  };

  /* jump to a requested topic's notes section when opened from a topic's "Study notes" */
  useEL(() => {
    if (!scrollToId) return;
    const t = setTimeout(() => scrollTo(scrollToId), 90);
    return () => clearTimeout(t);
  }, [scrollToId]);

  const renderParsed = (nodes) => nodes.map((n, i) => {
    if (n.type === 'h4') return <h4 key={i}>{n.text}</h4>;
    if (n.type === 'p')  return <p  key={i}>{n.text}</p>;
    if (n.type === 'ul') return (
      <ul key={i} style={{ '--bullet-color': c.ink }}>
        {n.items.map((b, j) => <li key={j}>{b}</li>)}
      </ul>
    );
    return null;
  });

  return (
    <div className="notes-col-layout">
      {/* ── TOC sidebar ── */}
      <div className="notes-toc-col" style={{ background: c.soft }}>
        <div className="page-eyebrow" style={{ color: c.ink, marginBottom: 12, padding: '0 4px' }}>
          Contents
        </div>
        {sections.map((s) => (
          <button key={s.id} className={`notes-toc-btn${activeId === s.id ? ' active' : ''}`}
            title={s.heading}
            style={activeId === s.id
              ? { background: 'var(--surface)', color: c.ink, borderColor: c.ink }
              : {}}
            onClick={() => scrollTo(s.id)}>
            {shortTocLabel(s.heading)}
          </button>
        ))}
      </div>

      {/* ── Scrollable section cards ── */}
      <div className="notes-content-col" ref={contentRef}>
        {sections.map((sec) => {
          const nodes = mergeAndParse(sec.items);
          return (
            <div key={sec.id} data-sec={sec.id}
              className="notes-sec"
              style={{ borderLeft: `4px solid ${c.ink}`, background: 'var(--surface)' }}>
              <h3 style={{ color: c.ink }}>{sec.heading}</h3>
              {renderParsed(nodes)}
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Image-based PDF viewer (pdf.js → blob URL <img> tags)
──────────────────────────────────────────────────────────── */
function PdfImgViewer({ src, visible }) {
  const [pages, setPages] = React.useState([]);
  const loadedSrc = React.useRef(null);
  const blobUrls = React.useRef([]);

  React.useEffect(() => {
    return () => { blobUrls.current.forEach(u => URL.revokeObjectURL(u)); };
  }, []);

  React.useEffect(() => {
    if (!visible || !src || !window.pdfjsLib) return;
    if (loadedSrc.current === src) return;
    loadedSrc.current = src;

    blobUrls.current.forEach(u => URL.revokeObjectURL(u));
    blobUrls.current = [];
    setPages([]);

    (async () => {
      try {
        const pdf = await pdfjsLib.getDocument(src).promise;
        const dpr = window.devicePixelRatio || 1;
        const urls = [];
        for (let i = 1; i <= pdf.numPages; i++) {
          const page = await pdf.getPage(i);
          const vp = page.getViewport({ scale: 1 });
          const scale = (1200 * dpr) / vp.width;
          const svp = page.getViewport({ scale });
          const c = document.createElement('canvas');
          c.width = svp.width;
          c.height = svp.height;
          await page.render({ canvasContext: c.getContext('2d'), viewport: svp }).promise;
          const blob = await new Promise(r => c.toBlob(r, 'image/png'));
          const url = URL.createObjectURL(blob);
          urls.push(url);
          blobUrls.current.push(url);
        }
        setPages(urls);
      } catch (e) {
        console.error('PDF render:', e);
      }
    })();
  }, [src, visible]);

  if (!window.pdfjsLib) {
    return <iframe src={src} title="slides" style={{
      width: '100%', height: '100%', border: 'none',
      display: visible ? 'block' : 'none',
    }} />;
  }

  return (
    <div style={{ display: visible ? 'block' : 'none' }}>
      {pages.map((url, i) => (
        <img key={i} src={url} alt={`Slide ${i + 1}`}
          style={{ display: 'block', width: '100%' }} />
      ))}
      {pages.length === 0 && visible && (
        <div style={{ padding: 40, textAlign: 'center', color: 'rgba(255,255,255,0.5)',
          fontFamily: 'Quicksand', fontSize: 15 }}>
          Loading slides…
        </div>
      )}
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Slides panel
──────────────────────────────────────────────────────────── */
function SlidesPanelBody({ mod }) {
  const c      = modColor(mod.hue);
  const slides = (typeof SOURCE_SLIDES !== 'undefined' && SOURCE_SLIDES[mod.id]) || [];
  const [activeLec, setActiveLec] = useSL(0);
  const hasSidebar = slides.length > 1;

  return (
    <div style={{ flex: 1, display: 'flex', minHeight: 0, overflow: 'hidden' }}>
      {hasSidebar && (
        <div className="pdf-lec-rail" style={{ flexShrink: 0, overflowY: 'auto',
          borderRight: '1.5px solid var(--line)', padding: '10px 8px', background: 'var(--bg)' }}>
          {slides.map((s, i) => (
            <button key={i} className="pdf-lec-btn" onClick={() => setActiveLec(i)}
              style={{
                background:   i === activeLec ? c.soft : 'transparent',
                color:        i === activeLec ? c.ink  : 'var(--ink-soft)',
                borderColor:  i === activeLec ? c.ink  : 'transparent',
              }}>
              {s.label}
            </button>
          ))}
        </div>
      )}

      <div style={{ flex: 1, minWidth: 0, overflowY: 'auto', background: '#525659' }}>
        {slides.map((s, i) => (
          <PdfImgViewer key={s.path} src={s.path} visible={i === activeLec} />
        ))}
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Topic resources panel (curated YouTube videos + web articles)
──────────────────────────────────────────────────────────── */
function ResourcesPanelBody({ mod, topicId }) {
  const c = modColor(mod.hue);
  const all = (typeof TOPIC_RESOURCES !== 'undefined' && TOPIC_RESOURCES[mod.id]) || {};
  const res = all[topicId] || { videos: [], articles: [] };
  const videos = res.videos || [];
  const articles = res.articles || [];
  const topic = ((typeof TOPICS !== 'undefined' && TOPICS[mod.id]) || []).find(t => t.id === topicId);
  const [playing, setPlaying] = useSL(null);

  return (
    <div className="res-scroll">
      <div className="res-inner">
        {topic && <div className="res-topic-head" style={{ color: c.ink }}>{topic.label}</div>}
        <div className="res-topic-sub">Curated videos &amp; reading to study this topic. Links open the original source.</div>

        {videos.length > 0 && (
          <>
            <div className="res-sec-h" style={{ color: c.ink }}>▶&nbsp; Watch</div>
            <div className="res-vid-grid">
              {videos.map(v => (
                <div key={v.id} className="res-vid">
                  {playing === v.id ? (
                    <div className="video-embed" style={{ borderRadius: 0, border: 'none', boxShadow: 'none' }}>
                      <iframe
                        src={`https://www.youtube-nocookie.com/embed/${v.id}?rel=0&modestbranding=1&autoplay=1`}
                        title={v.title} loading="lazy"
                        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                        allowFullScreen />
                    </div>
                  ) : (
                    <button className="res-vid-thumb" onClick={() => setPlaying(v.id)} aria-label={`Play: ${v.title}`}
                      style={{ backgroundImage: `url(https://i.ytimg.com/vi/${v.id}/mqdefault.jpg)` }}>
                      <span className="res-play" aria-hidden="true">▶</span>
                    </button>
                  )}
                  <div className="res-vid-meta">
                    <div className="res-vid-title">{v.title}</div>
                    <div className="res-vid-sub">{v.channel}</div>
                    {v.blurb && <div className="res-blurb">{v.blurb}</div>}
                    <a className="res-yt-link" href={`https://www.youtube.com/watch?v=${v.id}`}
                      target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}>Open on YouTube ↗</a>
                  </div>
                </div>
              ))}
            </div>
          </>
        )}

        {articles.length > 0 && (
          <>
            <div className="res-sec-h" style={{ color: c.ink, marginTop: videos.length ? 28 : 0 }}>📖&nbsp; Read &amp; explore</div>
            <div className="res-art-list">
              {articles.map((a, i) => (
                <a key={i} className="res-art" href={a.url} target="_blank" rel="noopener noreferrer">
                  <div className="res-art-main">
                    <div className="res-art-title">{a.title}</div>
                    {a.blurb && <div className="res-blurb">{a.blurb}</div>}
                  </div>
                  <div className="res-art-side">
                    <span className="res-art-src">{a.source}</span>
                    <span className="res-art-arrow" aria-hidden="true">↗</span>
                  </div>
                </a>
              ))}
            </div>
          </>
        )}

        {videos.length === 0 && articles.length === 0 && (
          <div style={{ color: 'var(--muted)', fontSize: 14.5, padding: '12px 0', lineHeight: 1.6 }}>
            No curated resources for this topic yet.
          </div>
        )}
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Main drawer shell
──────────────────────────────────────────────────────────── */
function PDFDrawer({ type, mod, onClose, visible, scrollToId, topicId }) {
  const c = modColor(mod.hue);

  useEL(() => {
    if (!visible) return;
    const prevBody = document.body.style.overflow;
    const prevHtml = document.documentElement.style.overflow;
    document.body.style.overflow = 'hidden';
    document.documentElement.style.overflow = 'hidden';
    return () => {
      document.body.style.overflow = prevBody;
      document.documentElement.style.overflow = prevHtml;
    };
  }, [visible]);

  return (
    <>
      {visible && (
        <div onClick={onClose}
          style={{ position: 'fixed', inset: 0,
            background: 'rgba(40, 36, 30, 0.58)',
            backdropFilter: 'blur(6px)', zIndex: 69 }} />
      )}

      <div className="pdf-drawer"
        style={{
          position: 'fixed', top: 0, right: 0, bottom: 0,
          background: 'var(--bg)',
          borderLeft: '1.5px solid var(--line)',
          boxShadow: '-12px 0 48px rgba(100, 85, 65, 0.18)',
          zIndex: 70,
          display: visible ? 'flex' : 'none',
          flexDirection: 'column',
        }}>

        <div style={{
          display: 'flex', alignItems: 'center', gap: 16,
          padding: '16px 24px 14px',
          borderBottom: '1.5px solid var(--line)',
          flexShrink: 0, background: 'var(--surface)',
        }}>
          <div style={{ flex: 1 }}>
            <div className="page-eyebrow" style={{ color: c.ink }}>{mod.name}</div>
            <div className="disp" style={{ fontSize: 19, fontWeight: 700 }}>
              {type === 'slides' ? 'Lecture Slides' : type === 'resources' ? 'Study Resources' : 'Study Notes'}
            </div>
          </div>
          <button className="btn btn-md btn-ghost" onClick={onClose}
            style={{ padding: '7px 16px', fontSize: 22, lineHeight: 1, fontWeight: 400 }}>
            ×
          </button>
        </div>

        {type === 'slides'
          ? <SlidesPanelBody mod={mod} />
          : type === 'resources'
            ? <ResourcesPanelBody mod={mod} topicId={topicId} />
            : <NotesPanelBody  mod={mod} scrollToId={scrollToId} />}
      </div>
    </>
  );
}

/* ────────────────────────────────────────────────────────────
   Module card (with optional source buttons)
──────────────────────────────────────────────────────────── */
function LessonsCard({ m, idx, onPick, meta, onDrawer }) {
  const c = modColor(m.hue);
  const hasSource = typeof SOURCE_SLIDES !== 'undefined' && !!SOURCE_SLIDES[m.id];
  return (
    <div className="mod-card fade" style={{ animationDelay: `${idx * 40}ms` }}
      onClick={() => onPick(m)}>
      {m.current && (
        <span className="tag" style={{ position: 'absolute', top: 18, right: 18,
          background: 'var(--coral)', color: '#fff' }}>This week</span>
      )}
      <ModuleEmblem m={m} />
      <div className="mod-name">{m.name}</div>
      <div className="mod-meta">{meta ? meta(m) : m.blurb}</div>
      {typeof m.progress === 'number' && (
        <div className="mini-bar" style={{ background: c.soft }}>
          <i style={{ width: `${Math.round(m.progress * 100)}%`, background: c.ink }} />
        </div>
      )}
      {hasSource && (
        <div style={{ display: 'flex', gap: 8, marginTop: 'auto', paddingTop: 10 }}
          onClick={(e) => e.stopPropagation()}>
          <button className="btn btn-md btn-ghost"
            style={{ flex: 1, fontSize: 11.5, padding: '7px 6px', whiteSpace: 'nowrap' }}
            onClick={() => onDrawer('slides', m)}>Lecture Slides</button>
          <button className="btn btn-md btn-ghost"
            style={{ flex: 1, fontSize: 11.5, padding: '7px 6px', whiteSpace: 'nowrap' }}
            onClick={() => onDrawer('notes', m)}>Study Notes</button>
        </div>
      )}
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Lesson detail (unchanged)
──────────────────────────────────────────────────────────── */
/* A lecture's recording(s), embedded as Unlisted YouTube players. Multiple recordings
   show pill tabs; remounts (key=lecture) so the selection resets when the lecture changes. */
function LectureVideo({ videos, c, label }) {
  const [i, setI] = useSL(0);
  const idx = Math.min(i, videos.length - 1);
  const v = videos[idx];
  return (
    <div style={{ marginBottom: 14 }}>
      <div className="video-embed">
        <iframe
          src={`https://www.youtube-nocookie.com/embed/${v.yt}?rel=0&modestbranding=1`}
          title={v.label || label} loading="lazy"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
          allowFullScreen />
      </div>
      {videos.length > 1 && (
        <div className="video-recs">
          {videos.map((vv, k) => (
            <button key={k} type="button"
              className={`video-rec ${k === idx ? 'is-on' : ''}`}
              style={k === idx ? { background: c.ink, borderColor: c.ink, color: 'var(--surface)' } : null}
              onClick={() => setI(k)}>{vv.label || `Recording ${vv.rec || k + 1}`}</button>
          ))}
        </div>
      )}
    </div>
  );
}

function LessonDetail({ m, onBack, go, onDrawer }) {
  const c = modColor(m.hue);
  const [active, setActive] = useSL(0);
  const lec = m.lectures[active];
  const hasNotes = (typeof SOURCE_NOTES !== 'undefined' && !!SOURCE_NOTES[m.id])
    || (typeof TOPIC_NOTES !== 'undefined' && !!(TOPIC_NOTES[m.id] && TOPIC_NOTES[m.id].length));
  /* one row per lecture-topic (+ an Overview row only if it carries content), each with
     a live per-topic question count and the two per-topic actions */
  const topicRes = (id) => {
    const r = (typeof TOPIC_RESOURCES !== 'undefined' && TOPIC_RESOURCES[m.id] && TOPIC_RESOURCES[m.id][id]) || null;
    return r ? ((r.videos || []).length + (r.articles || []).length) : 0;
  };
  const topicRows = ((typeof TOPICS !== 'undefined' && TOPICS[m.id]) || []).map((t, k) => ({
    id: t.id, label: t.label,
    recs: m.lectures[k] ? m.lectures[k].recs : null,
    qCount: (typeof topicCount === 'function') ? topicCount(m.id, t.id) : 0,
    cCount: (typeof topicCardCount === 'function') ? topicCardCount(m.id, t.id) : 0,
    hasNotes: !!(((typeof TOPIC_NOTES !== 'undefined' && TOPIC_NOTES[m.id]) || []).find(n => n.topicId === t.id)),
    resCount: topicRes(t.id),
  })).filter(t => t.id !== 'overview' || t.qCount || t.cCount || t.hasNotes || t.resCount);
  const noteTarget = (id) => (typeof topicNoteTarget === 'function') ? topicNoteTarget(m.id, id) : null;
  return (
    <div className="page fade">
      <button className="crumb" onClick={onBack}>← All lessons</button>
      <div style={{ display: 'flex', alignItems: 'center', gap: 18, marginBottom: 8 }}>
        <ModuleEmblem m={m} size={58} />
        <div>
          <div className="page-eyebrow" style={{ color: c.ink }}>
            {m.term === 'spring' ? 'Spring term' : 'Summer term'} · {m.instructor}
          </div>
          <h1 className="page-title" style={{ fontSize: 34 }}>{m.name}</h1>
        </div>
      </div>

      <div className="lesson-detail-grid">
        <div>
          {(() => {
            const vids = (((typeof LECTURE_VIDEOS !== 'undefined' && LECTURE_VIDEOS[m.id]) || {})[String(active + 1)] || []).filter(v => v && v.yt);
            return vids.length
              ? <LectureVideo key={active} videos={vids} c={c} label={`${m.short} L${active + 1}`} />
              : (
                <div className="media-ph" style={{ marginBottom: 14 }}>
                  <div style={{ width: 54, height: 54, borderRadius: '50%', background: 'var(--surface)',
                    display: 'grid', placeItems: 'center', boxShadow: 'var(--shadow-sm)' }}>
                    {Icons.lessons(c.ink)}
                  </div>
                  <span>video · {m.short} L{active + 1} · {lec.recs} recordings</span>
                </div>
              );
          })()}
          <div className="disp" style={{ fontSize: 21, fontWeight: 600 }}>{lec.t}</div>
          <div style={{ fontSize: 14.5, color: 'var(--muted)', marginTop: 4 }}>
            Lecture {active + 1} of {m.lectures.length} · {m.chapter}
          </div>
          <div style={{ display: 'flex', gap: 10, marginTop: 18 }}>
            <button className="btn btn-md btn-ghost"
              onClick={() => hasNotes ? onDrawer('notes', m) : go('notes', m.id)}>Study notes</button>
            <button className="btn btn-md btn-primary" onClick={() => go('practice', m.id)}>Practice this topic →</button>
          </div>
        </div>

        <div>
          <div className="page-eyebrow" style={{ marginBottom: 12 }}>Topics</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {topicRows.map((t, k) => (
              <div key={t.id} className="row" onClick={() => t.recs != null && setActive(k)}
                style={{ borderColor: k === active && t.recs != null ? c.ink : 'var(--line)',
                  background: k === active && t.recs != null ? c.soft : 'var(--surface)',
                  alignItems: 'flex-start', cursor: t.recs != null ? 'pointer' : 'default' }}>
                <div className="row-num" style={{ background: c.bg, color: c.ink }}>{t.recs != null ? k + 1 : '∗'}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="disp" style={{ fontSize: 15.5, fontWeight: 600, lineHeight: 1.2 }}>{t.label}</div>
                  <div style={{ fontSize: 12.5, color: 'var(--muted)', marginTop: 2 }}>
                    {t.recs != null ? `${t.recs} recordings · ` : ''}{t.qCount} question{t.qCount === 1 ? '' : 's'}{t.cCount ? ` · ${t.cCount} cards` : ''}
                  </div>
                  <div style={{ display: 'flex', gap: 8, marginTop: 9, flexWrap: 'wrap' }} onClick={(e) => e.stopPropagation()}>
                    <button className="btn btn-md btn-ghost" disabled={!t.hasNotes}
                      style={{ fontSize: 12, padding: '6px 12px', opacity: t.hasNotes ? 1 : 0.45 }}
                      onClick={() => t.hasNotes && onDrawer('notes', m, noteTarget(t.id))}>Study notes</button>
                    <button className="btn btn-md btn-ghost" disabled={!t.resCount}
                      style={{ fontSize: 12, padding: '6px 12px', opacity: t.resCount ? 1 : 0.45 }}
                      onClick={() => t.resCount && onDrawer('resources', m, null, t.id)}>▶ Resources</button>
                    <button className="btn btn-md btn-primary" disabled={!t.qCount}
                      style={{ fontSize: 12, padding: '6px 12px', opacity: t.qCount ? 1 : 0.45 }}
                      onClick={() => t.qCount && go('practice', { mod: m.id, topicId: t.id })}>Practice this topic →</button>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Lessons page
──────────────────────────────────────────────────────────── */
function LessonsPage({ focus, go }) {
  const [sel, setSel]       = useSL(focus ? MOD_BY_ID[focus] : null);
  const [drawer, setDrawer] = useSL(null);
  /* Keep PDFDrawer mounted after first open so iframes preserve scroll position */
  const lastDrawerRef = useRL(null);
  if (drawer) lastDrawerRef.current = drawer;
  const activeDrawer = drawer || lastDrawerRef.current;
  useEL(() => { if (focus) setSel(MOD_BY_ID[focus]); }, [focus]);
  const totalRecs = (m) => m.lectures.reduce((n, l) => n + l.recs, 0);
  const openDrawer = (type, mod, scrollToId = null, topicId = null) => setDrawer({ type, mod, scrollToId, topicId });

  /* Rendered in both the grid and detail views so the unit's notes/slides
     drawer is reachable from the module detail, not just the card grid. */
  const drawerEl = activeDrawer && (
    <PDFDrawer
      visible={!!drawer}
      type={activeDrawer.type}
      mod={activeDrawer.mod}
      scrollToId={activeDrawer.scrollToId}
      topicId={activeDrawer.topicId}
      onClose={() => setDrawer(null)} />
  );

  if (sel) return (
    <>
      <LessonDetail m={sel} onBack={() => { setDrawer(null); setSel(null); }} go={go} onDrawer={openDrawer} />
      {drawerEl}
    </>
  );
  /* one module = one stop on the course path */
  const PathRow = ({ m, idx }) => {
    const c = modColor(m.hue);
    const pct = Math.round((m.progress || 0) * 100);
    const done = pct >= 100;
    const dotColor = done ? 'var(--p-mint-i)' : (m.current ? 'var(--coral)' : c.ink);
    const hasSlides = typeof SOURCE_SLIDES !== 'undefined' && !!SOURCE_SLIDES[m.id];
    const hasNotes  = (typeof SOURCE_NOTES !== 'undefined' && !!SOURCE_NOTES[m.id])
      || (typeof TOPIC_NOTES !== 'undefined' && !!(TOPIC_NOTES[m.id] && TOPIC_NOTES[m.id].length));
    return (
      <div className="lpath-row">
        <div className="lpath-rail"><span className="lpath-dot" style={{ background: dotColor }} /></div>
        <div className="lcard fade" style={{ animationDelay:`${idx*40}ms` }} onClick={() => setSel(m)}>
          <div className="lcard-stripe" style={{ background: c.ink }} />
          <div className="mod-emblem" style={{ width:48, height:48, background:c.bg, color:c.ink }}><ModuleGlyph m={m} size={26} color={c.ink} /></div>
          <div className="lcard-main">
            <div style={{ display:'flex', alignItems:'center', gap:10, flexWrap:'wrap' }}>
              <span className="lcard-name">{m.name}</span>
              {m.current && <span className="tag" style={{ background:'var(--coral)', color:'#fff' }}>This week</span>}
            </div>
            <div className="lcard-meta">{m.chapter} · {m.instructor} · {m.lectures.length} lectures · {totalRecs(m)} recordings</div>
            <div className="lcard-prog">
              <AnimatedBar pct={pct} color={c.ink} track={c.soft} height={7} />
              <b style={{ color:c.ink }}>{pct}%</b>
            </div>
            {(hasSlides || hasNotes) && (
              <div className="lcard-srcs" onClick={(e) => e.stopPropagation()}>
                {hasSlides && <button className="btn btn-md btn-ghost" style={{ fontSize:12, padding:'7px 14px' }} onClick={() => openDrawer('slides', m)}>Lecture slides</button>}
                {hasNotes  && <button className="btn btn-md btn-ghost" style={{ fontSize:12, padding:'7px 14px' }} onClick={() => openDrawer('notes', m)}>Study notes</button>}
                <button className="btn btn-md btn-ghost" style={{ fontSize:12, padding:'7px 14px' }} onClick={() => go('practice', m.id)}>Practice</button>
              </div>
            )}
          </div>
          <div className="lcard-side">
            {done
              ? <span className="tag" style={{ background:'oklch(0.93 0.04 165)', color:'var(--p-mint-i)' }}>✓ Done</span>
              : <span className="lcard-chev">›</span>}
          </div>
        </div>
      </div>
    );
  };

  const TERMS = [{ key:'spring', label:'Spring term' }, { key:'summer', label:'Summer term' }];

  return (
    <>
      <div className="page fade">
        <div className="page-head">
          <div className="page-eyebrow">Lessons</div>
          <h1 className="page-title">Course path</h1>
          <p className="page-sub">Your two-term syllabus, in order. Follow the path through each Vander chapter — open a module for its lectures, slides and notes.</p>
        </div>

        {TERMS.map((t) => {
          const mods = MODULES.filter(m => m.term === t.key);
          if (!mods.length) return null;
          const done = mods.filter(m => (m.progress || 0) >= 1).length;
          return (
            <div key={t.key}>
              <div className="lpath-term">
                <h2>{t.label}</h2>
                <span className="lpath-sub">{mods.length} modules</span>
                <span className="lpath-done">{done}/{mods.length} complete</span>
              </div>
              <div>
                {mods.map((m, idx) => <PathRow key={m.id} m={m} idx={idx} />)}
              </div>
            </div>
          );
        })}
      </div>
      {drawerEl}
    </>
  );
}
window.LessonsPage = LessonsPage;
