/* Course reader - the lesson shell, the data-driven block dispatcher, the
   course home index, the soft buy banner and the locked-module teasers.
   Reuses Icon, .card, .reveal, .mono, .btn, .eyebrow, .wrap and all CSS vars;
   figures come from <Schema> (schemas.jsx), content from MODULES_DATA. */

/* Minimal inline formatter - **bold** and *italic* inside content strings. */
function RichText({ text }) {
  if (text == null) return null;
  const out = [];
  const re = /\*\*([^*]+)\*\*|\*([^*]+)\*/g;
  let last = 0, m, k = 0;
  while ((m = re.exec(text))) {
    if (m.index > last) out.push(text.slice(last, m.index));
    if (m[1] !== undefined) out.push(<strong key={k++}>{m[1]}</strong>);
    else out.push(<em key={k++}>{m[2]}</em>);
    last = re.lastIndex;
  }
  if (last < text.length) out.push(text.slice(last));
  return <>{out}</>;
}

/* ── small list helper (items can be strings or {lead, text}) ──────────────── */
function LessonList({ items }) {
  return (
    <ul className="po-lesson-ul">
      {items.map((it, i) => (
        <li key={i}>
          <Icon name="check" size={16} stroke={2.6} />
          <span>
            {typeof it === "string"
              ? <RichText text={it} />
              : <><b><RichText text={it.lead} /></b> <RichText text={it.text} /></>}
          </span>
        </li>
      ))}
    </ul>
  );
}

/* ── block dispatcher ──────────────────────────────────────────────────────── */
function Block({ block }) {
  switch (block.type) {
    case "p":
      return <p className="po-lesson-p"><RichText text={block.text} /></p>;
    case "h":
      return <h3 className="po-lesson-h"><RichText text={block.text} /></h3>;
    case "ul":
      return <LessonList items={block.items} />;
    case "callout":
      return (
        <aside className={"po-callout po-callout--" + (block.tone || "insight")}>
          {block.icon && <span className="po-callout-ic"><Icon name={block.icon} size={18} /></span>}
          <div>
            {block.title && <strong className="po-callout-t">{block.title}</strong>}
            <p><RichText text={block.text} /></p>
          </div>
        </aside>
      );
    case "lies":
      return (
        <div className="po-lie-grid">
          {block.items.map((l, i) => (
            <div className="po-lie card" key={i}>
              <span className="po-lie-n mono">{String(i + 1).padStart(2, "0")}</span>
              <strong className="po-lie-claim">“<RichText text={l.claim} />”</strong>
              <span className="po-lie-grain"><em>Grain of truth:</em> <RichText text={l.grain} /></span>
              <span className="po-lie-trap"><RichText text={l.trap} /></span>
            </div>
          ))}
        </div>
      );
    case "reasons":
      return (
        <div className="po-reasons">
          {block.items.map((r, i) => (
            <div className="po-reason card" key={i}>
              <div className="po-reason-head">
                <span className="po-reason-n mono">{r.n}</span>
                <h4><RichText text={r.title} /></h4>
              </div>
              <div className="po-reason-body">
                <p className="po-reason-vignette"><b>What it looks like.</b> <RichText text={r.looksLike} /></p>
                <p className="po-reason-check"><b>Is this you?</b> <RichText text={r.isThisYou} /></p>
                {r.fixedIn && (
                  <a className="po-reason-fix mono" href={"/learn/" + r.fixedIn}>
                    <Icon name="arrow" size={14} /> {r.fixedLabel || "Fixed later"}
                  </a>
                )}
              </div>
            </div>
          ))}
        </div>
      );
    case "decode":
      return (
        <div className="po-decode">
          {block.items.map((d, i) => (
            <div className="po-decode-row" key={i}>
              <span className="po-decode-worry"><Icon name="quote" size={15} /> <RichText text={d.worry} /></span>
              <span className="po-decode-signal"><b>Signal it:</b> <RichText text={d.signal} /></span>
            </div>
          ))}
        </div>
      );
    case "signals":
      return (
        <div className="po-signals">
          {block.items.map((s, i) => (
            <div className="po-signal card" key={i}>
              <div className="po-signal-head">
                <span className="po-signal-n mono">{s.n}</span>
                <h4>{s.name}</h4>
              </div>
              <p className="po-signal-what"><RichText text={s.what} /></p>
              <p className="po-signal-why"><b>Why it hurts.</b> <RichText text={s.why} /></p>
              <p className="po-signal-fix"><b>The fix.</b> <RichText text={s.fix} /></p>
              <div className="po-signal-ba">
                <div className="po-signal-b"><span className="mono">Before</span><span><RichText text={s.before} /></span></div>
                <div className="po-signal-a"><span className="mono">After</span><span><RichText text={s.after} /></span></div>
              </div>
            </div>
          ))}
        </div>
      );
    case "table":
      return (
        <div className="po-table-wrap">
          {block.title && <h3 className="po-lesson-h">{block.title}</h3>}
          <table className="po-table">
            <thead><tr>{block.cols.map((c, i) => <th key={i}>{c}</th>)}</tr></thead>
            <tbody>
              {block.rows.map((row, i) => (
                <tr key={i}>{row.map((cell, j) => <td key={j} className={j === 0 ? "po-table-lead" : ""}><RichText text={cell} /></td>)}</tr>
              ))}
            </tbody>
          </table>
          {block.note && <p className="po-table-note mono">{block.note}</p>}
        </div>
      );
    case "figure":
      return (
        <figure className="po-fig">
          <Schema figure={block.figure} />
          {block.caption && <figcaption className="po-fig-cap mono">{block.caption}</figcaption>}
        </figure>
      );
    default:
      return null;
  }
}

/* ── a content section ─────────────────────────────────────────────────────── */
function SectionBlock({ section }) {
  return (
    <section id={"io-sec-" + section.n} data-mapnode className="po-lesson-sec reveal">
      <span className="po-lesson-eyebrow">
        <b className="mono">{section.n}</b> {section.eyebrow}
      </span>
      <h2 className="po-lesson-title"><RichText text={section.title} /></h2>
      {section.lead && <p className="po-lesson-lead"><RichText text={section.lead} /></p>}
      {section.blocks.map((b, i) => <Block key={i} block={b} />)}
      {section.oneLiner && (
        <p className="po-lesson-kicker"><RichText text={section.oneLiner} /></p>
      )}
    </section>
  );
}

/* ── assignment ────────────────────────────────────────────────────────────── */
function Assignment({ data }) {
  if (!data) return null;
  return (
    <section id="io-assignment" data-mapnode className="po-assignment reveal">
      <div className="po-assignment-card card">
        <span className="po-assignment-tag eyebrow"><Icon name="target" size={15} /> Your assignment</span>
        <h2>{data.title}</h2>
        {data.intro && <p className="po-assignment-intro"><RichText text={data.intro} /></p>}
        <ol className="po-assignment-steps">
          {data.steps.map((s, i) => (
            <li key={i}>
              <span className="po-assignment-n mono">{String(i + 1).padStart(2, "0")}</span>
              <span>{typeof s === "string"
                ? <RichText text={s} />
                : <><b><RichText text={s.lead} /></b> <RichText text={s.text} /></>}</span>
            </li>
          ))}
        </ol>
        {data.blocks && data.blocks.map((b, i) => <Block key={i} block={b} />)}
      </div>
    </section>
  );
}

/* ── soft buy banner (no hard paywall) ─────────────────────────────────────── */
function goToPricing(e) {
  if (e) e.preventDefault();
  window.navigate("/checkout");
}

function SoftBuyBanner({ variant = "inline" }) {
  const [hidden, setHidden] = useState(() => {
    try { return variant === "sticky" && sessionStorage.getItem("io-buybar") === "off"; }
    catch (e) { return false; }
  });
  if (isUnlocked()) return null;
  if (hidden) return null;
  return (
    <div className={"po-buybar po-buybar--" + variant}>
      <div className="po-buybar-copy">
        <strong>Ready for the full playbook?</strong>
        <span>Unlock all 6 modules - every worksheet, script &amp; template, plus lifetime access. <b className="mono">$39 CAD</b>, one-time.</span>
      </div>
      <div className="po-buybar-actions">
        <a href="/checkout" className="btn btn-primary" onClick={goToPricing}>
          <Icon name="spark" size={16} /> Get the course - $39
        </a>
        {variant === "sticky" && (
          <button className="po-buybar-x" aria-label="Dismiss"
            onClick={() => { setHidden(true); try { sessionStorage.setItem("io-buybar", "off"); } catch (e) {} }}>
            <Icon name="close" size={16} />
          </button>
        )}
      </div>
    </div>
  );
}

/* ── locked module teaser (Modules 3-6) ────────────────────────────────────── */
function LockedModuleCard({ module: m }) {
  return (
    <div className="po-locked card">
      <div className="po-locked-badge"><Icon name="badge" size={16} /> Locked</div>
      <span className="po-locked-track eyebrow">{m.n} · {m.track}</span>
      <h3>{m.title}</h3>
      <p className="po-locked-sub">{m.subtitle}</p>
      {m.whatsInside && (
        <ul className="po-locked-list">
          {m.whatsInside.map((w, i) => (
            <li key={i}><Icon name="check" size={15} stroke={2.6} /><span>{w}</span></li>
          ))}
        </ul>
      )}
      <a href="/checkout" className="btn btn-primary po-locked-cta" onClick={goToPricing}>
        <Icon name="spark" size={16} /> Unlock this module
      </a>
    </div>
  );
}

/* ── progress store (localStorage) + completion ────────────────────────────── */
const IO_PROGRESS_KEY = "io-progress";
function readProgress() { try { return JSON.parse(localStorage.getItem(IO_PROGRESS_KEY)) || {}; } catch (e) { return {}; } }
function isComplete(id) { return !!readProgress()[id]; }
function markComplete(id) {
  try { const p = readProgress(); p[id] = true; localStorage.setItem(IO_PROGRESS_KEY, JSON.stringify(p)); } catch (e) {}
  window.dispatchEvent(new CustomEvent("io-progress"));
}
function useProgressTick() {
  const [, set] = useState(0);
  useEffect(() => {
    const h = () => set((n) => n + 1);
    window.addEventListener("io-progress", h);
    return () => window.removeEventListener("io-progress", h);
  }, []);
}

/* ── the left sidebar: all six modules, current one expanded into its sections.
   Replaces the old top progress rail + section-only map. The current module's
   sections (problem → fixes → key points) nest beneath it as a submenu that
   highlights as you scroll; other modules are plain links. ──────────────────── */
const MODULE_ICONS = {
  "module-1": "target", "module-2": "doc", "module-3": "globe",
  "module-4": "chat", "module-5": "code", "module-6": "badge",
};
const SECTION_PHASES = { problem: "The problem", playbook: "The fixes", close: "Key points" };

function ModuleSections({ nodes, activeId, expanded, onJump, unlockable }) {
  let last = null;
  return (
    <ol className="po-mnav-secs">
      {nodes.map((nd) => {
        const head = nd.phase !== last ? (last = nd.phase, SECTION_PHASES[nd.phase]) : null;
        const locked = nd.locked && !expanded;
        // In the desktop rail, locked rows are disabled. In the mobile sheet
        // (unlockable), they stay tappable so the sheet is a real unlock entry point.
        const tappable = !locked || unlockable;
        const lockBadge = locked && unlockable;
        return (
          <React.Fragment key={nd.id}>
            {head && <li className="po-map-phase">{head}</li>}
            <li>
              <button disabled={!tappable}
                className={"po-map-item" + (activeId === nd.id ? " on" : "") + (locked ? " locked" : "") + (lockBadge ? " is-unlockable" : "")}
                onClick={tappable ? () => onJump(nd) : undefined}
                aria-label={lockBadge ? nd.label + " - unlocks the rest, free" : undefined}>
                <span className="po-map-n mono">{nd.n}</span>
                <span className="po-map-lbl">{nd.label}</span>
                {lockBadge && <Icon name="badge" size={12} />}
              </button>
            </li>
          </React.Fragment>
        );
      })}
    </ol>
  );
}

function ModuleNav({ current, nodes, activeId, expanded, onJump, frac = 0, variant }) {
  useProgressTick();
  // the current module's section submenu can be collapsed shut by the reader
  const [secOpen, setSecOpen] = useState(true);
  const hasSecs = nodes.length > 0;
  const list = (
    <ol className="po-mnav-list">
      {MODULES_DATA.map((m) => {
        const done = isComplete(m.id);
        const isCur = m.id === current;
        const state = isCur ? "now" : done ? "done" : "future";
        const showSecs = isCur && hasSecs;
        const ModIcon = (
          <span className="po-mnav-ic">
            {done && !isCur
              ? <Icon name="check" size={14} stroke={2.8} />
              : <Icon name={MODULE_ICONS[m.id] || "doc"} size={15} />}
          </span>
        );
        const Label = (
          <span className="po-mnav-mtxt">
            <b className="mono">{m.n}</b>
            <span className="po-mnav-mlbl">{m.track}</span>
          </span>
        );
        return (
          <li key={m.id} className={"po-mnav-mod is-" + state + (showSecs && !secOpen ? " is-collapsed" : "")}>
            {showSecs ? (
              // current module → a toggle that collapses/expands its submenu
              <button type="button" className="po-mnav-modlink po-mnav-modtoggle"
                aria-expanded={secOpen} onClick={() => setSecOpen((v) => !v)}
                title={(secOpen ? "Collapse" : "Expand") + " sections of " + m.n + " · " + m.track}>
                {ModIcon}{Label}
                <span className="po-mnav-chev" aria-hidden="true"><Icon name="arrowDown" size={15} /></span>
              </button>
            ) : (
              <a className="po-mnav-modlink" href={"/learn/" + m.id}
                 aria-current={isCur ? "page" : undefined}
                 title={m.n + " · " + m.track + " - " + m.title}>
                {ModIcon}{Label}
              </a>
            )}
            {isCur && (
              <span className="po-mnav-prog" aria-hidden="true">
                <span style={{ width: Math.round(frac * 100) + "%" }} />
              </span>
            )}
            {showSecs && secOpen && (
              <ModuleSections nodes={nodes} activeId={activeId} expanded={expanded} onJump={onJump} />
            )}
          </li>
        );
      })}
    </ol>
  );

  return (
    <nav className="po-mnav po-mnav-rail" aria-label="Modules">
      <span className="po-mnav-head mono">Modules</span>
      {list}
    </nav>
  );
}

/* ── mobile reader navigation (≤1024px): a sticky bottom bar reachable at any
   scroll depth - prev/next-module arrows + a button showing the current module
   AND the section you're reading, which opens a Modules sheet. Near the bottom it
   surfaces an Enroll CTA. `frac` drives the progress bar; `scrollFrac` (raw page
   scroll) drives the bottom detection (railFrac is scaled down for gated modules). */
function BottomNav({ current, nodes, activeId, frac = 0, scrollFrac = 0 }) {
  useProgressTick();
  const user = useUser();
  const paid = !!(user && user.paid);
  const [sheetOpen, setSheetOpen] = useState(false);

  const idx = MODULES_DATA.findIndex((x) => x.id === current);
  const cur = MODULES_DATA[idx];
  const prev = MODULES_DATA[idx - 1];
  const next = MODULES_DATA[idx + 1];
  const activeNode = nodes.find((n) => n.id === activeId);
  const sectionLabel = activeNode ? activeNode.label : null;
  const atBottom = scrollFrac >= 0.85; // surface the Enroll CTA as they reach the end

  // lock background scroll while the sheet is open (same technique as the nav burger)
  useEffect(() => {
    document.body.style.overflow = sheetOpen ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [sheetOpen]);

  // Escape closes the sheet
  useEffect(() => {
    if (!sheetOpen) return;
    const onKey = (e) => { if (e.key === "Escape") setSheetOpen(false); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [sheetOpen]);

  if (!cur) return null;

  const arrow = (mod, dir) => mod
    ? <a className="po-bnav-arrow" href={"/learn/" + mod.id} aria-label={(dir < 0 ? "Previous" : "Next") + " module: " + mod.track}>
        <Icon name="arrow" size={20} style={dir < 0 ? { transform: "scaleX(-1)" } : undefined} />
      </a>
    : <span className="po-bnav-arrow is-disabled" aria-disabled="true"><Icon name="arrow" size={20} style={dir < 0 ? { transform: "scaleX(-1)" } : undefined} /></span>;

  return (
    <>
      <nav className="po-bnav" aria-label="Module navigation">
        {atBottom && !paid && (
          <a className="po-bnav-enroll" href="/checkout">
            <Icon name="badge" size={16} /> Enroll to unlock the full course - $39 <Icon name="arrow" size={16} />
          </a>
        )}
        <div className="po-bnav-row">
          {arrow(prev, -1)}
          <button className="po-bnav-open" aria-haspopup="dialog" aria-expanded={sheetOpen} onClick={() => setSheetOpen(true)}>
            <Icon name="map" size={16} />
            <span className="po-bnav-label">
              <span className="po-bnav-mod"><b className="mono">{cur.n}</b> {cur.track}</span>
              {sectionLabel && <span className="po-bnav-sec">{sectionLabel}</span>}
            </span>
            <span className="po-bnav-prog" aria-hidden="true"><span style={{ width: Math.round(frac * 100) + "%" }} /></span>
          </button>
          {arrow(next, 1)}
        </div>
      </nav>

      {sheetOpen && (
        <div className="po-sheet-scrim" onClick={() => setSheetOpen(false)}>
          <div className="po-sheet" role="dialog" aria-modal="true" aria-label="Modules" onClick={(e) => e.stopPropagation()}>
            <span className="po-sheet-handle" aria-hidden="true" />
            <div className="po-sheet-head">
              <strong>Modules</strong>
              <button className="po-sheet-close" aria-label="Close" onClick={() => setSheetOpen(false)}><Icon name="close" size={18} /></button>
            </div>
            <div className="po-sheet-body">
              <ol className="po-sheet-mods">
                {MODULES_DATA.map((m) => {
                  const done = isComplete(m.id);
                  const isCur = m.id === current;
                  const state = isCur ? "now" : done ? "done" : "future";
                  return (
                    <li key={m.id} className={"po-mnav-mod is-" + state}>
                      <a className="po-mnav-modlink" href={"/learn/" + m.id} aria-current={isCur ? "page" : undefined} onClick={() => setSheetOpen(false)}>
                        <span className="po-mnav-ic">{done && !isCur ? <Icon name="check" size={14} stroke={2.8} /> : <Icon name={MODULE_ICONS[m.id] || "doc"} size={15} />}</span>
                        <span className="po-mnav-mtxt"><b className="mono">{m.n}</b><span className="po-mnav-mlbl">{m.track}</span></span>
                        {isCur && <span className="po-bnav-now mono">Now</span>}
                      </a>
                    </li>
                  );
                })}
              </ol>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

/* ── paywall gate (modules 2-6, not paid): one button - keep reading = pay.
   The click is still recorded as the "keep reading" intent signal, then routes to
   checkout. Paying unlocks the rest of this module + every other module. ──────── */
function SeeMore({ module: m, rest }) {
  const extras = (m.assignment ? 1 : 0) + 1; // key points + assignment
  const onKeep = () => { try { if (typeof track === "function") track("keep_reading", m.id); } catch (e) {} };
  return (
    <div className="po-seemore">
      <div className="po-seemore-fade" aria-hidden="true" />
      <a className="btn btn-primary po-seemore-btn" href="/checkout" onClick={onKeep}>
        <Icon name="badge" size={16} /> Keep reading this module - get the course, $39
      </a>
      <span className="po-seemore-note mono">Unlock the remaining {rest.length + extras} parts + every other module · one payment</span>
    </div>
  );
}

/* ── key points to remember (the conclusion signature, from section one-liners) ── */
function KeyTakeaways({ module: m }) {
  const points = (m.sections || []).map((s) => s.oneLiner).filter(Boolean);
  if (!points.length) return null;
  return (
    <section id="io-takeaways" data-mapnode className="po-takeaways reveal">
      <div className="po-takeaways-card card">
        <span className="po-takeaways-tag eyebrow"><Icon name="star" size={15} /> Key points to remember</span>
        <ul>
          {points.map((p, i) => (
            <li key={i}><Icon name="check" size={16} stroke={2.6} /><span><RichText text={p} /></span></li>
          ))}
        </ul>
      </div>
    </section>
  );
}

/* ── mark complete → next module ───────────────────────────────────────────── */
function MarkComplete({ module: m }) {
  useProgressTick();
  const done = isComplete(m.id);
  const idx = MODULES_DATA.findIndex((x) => x.id === m.id);
  const next = MODULES_DATA[idx + 1];
  const onClick = () => {
    markComplete(m.id);
    // if the reader is signed in, record progress server-side for the admin dashboard
    try {
      const u = typeof readUser === "function" ? readUser() : null;
      if (u && u.token) {
        const api = (typeof window !== "undefined" && window.IDEAL_OFFER_API) || "/api";
        fetch(api + "/progress", {
          method: "POST",
          headers: { "Content-Type": "application/json", Authorization: "Bearer " + u.token },
          body: JSON.stringify({ moduleId: m.id }),
        }).catch(() => {});
      }
    } catch (e) {}
    window.navigate(next ? "/learn/" + next.id : "/learn/module-1");
  };
  return (
    <div className="po-complete">
      {done && <span className="po-complete-tag"><Icon name="check" size={15} stroke={2.6} /> Module complete</span>}
      <button className="btn btn-primary po-complete-btn" onClick={onClick}>
        {done ? (next ? "Next module" : "Back to Module 1") : (next ? "Mark complete → next module" : "Mark complete → finish")}
        <Icon name="arrow" size={17} />
      </button>
    </div>
  );
}

/* ── per-module 👍/👎 feedback ───────────────────────────────────────────────── */
const IO_API = () => (typeof window !== "undefined" && window.IDEAL_OFFER_API) || "/api";
const FB_KEY = "io-fb"; // remembers which modules this browser already rated

function postFeedback(moduleId, helpful, comment) {
  try {
    const u = typeof readUser === "function" ? readUser() : null;
    fetch(IO_API() + "/feedback", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ moduleId, helpful, comment: comment || "", visitorId: getVisitorId(), userId: u && u.id }),
    }).catch(() => {});
  } catch (e) {}
}

function ModuleFeedback({ moduleId }) {
  const [vote, setVote] = useState(null);          // "up" | "down" | null
  const [comment, setComment] = useState("");
  const [phase, setPhase] = useState("ask");       // ask | comment | done

  useEffect(() => {
    try { const m = JSON.parse(localStorage.getItem(FB_KEY) || "{}"); if (m[moduleId]) { setVote(m[moduleId]); setPhase("done"); } } catch (e) {}
  }, [moduleId]);

  const remember = (v) => { try { const m = JSON.parse(localStorage.getItem(FB_KEY) || "{}"); m[moduleId] = v; localStorage.setItem(FB_KEY, JSON.stringify(m)); } catch (e) {} };
  const choose = (helpful) => { const v = helpful ? "up" : "down"; setVote(v); remember(v); postFeedback(moduleId, helpful, ""); setPhase("comment"); };
  const send = () => { postFeedback(moduleId, vote === "up", comment); setPhase("done"); };

  if (phase === "done") return (
    <div className="po-fb reveal"><span className="po-fb-thanks mono"><Icon name="check" size={14} stroke={2.6} /> Thanks - your feedback shapes the next revision.</span></div>
  );
  return (
    <div className="po-fb reveal">
      {phase === "ask" ? (
        <div className="po-fb-ask">
          <span className="po-fb-q">Was this module helpful?</span>
          <div className="po-fb-btns">
            <button className="po-fb-btn" onClick={() => choose(true)} aria-label="Helpful">👍</button>
            <button className="po-fb-btn" onClick={() => choose(false)} aria-label="Not helpful">👎</button>
          </div>
        </div>
      ) : (
        <div className="po-fb-comment">
          <label className="po-fb-q">{vote === "up" ? "Glad it helped. Anything we should add?" : "Sorry to hear. What was missing or unclear?"}</label>
          <textarea className="po-fb-text" rows={2} maxLength={1000} value={comment}
            placeholder="Optional - your words shape the next revision" onChange={(e) => setComment(e.target.value)} />
          <div className="po-fb-actions">
            <button className="po-fb-skip" onClick={() => setPhase("done")}>Skip</button>
            <button className="btn btn-primary po-fb-send" onClick={send}>Send</button>
          </div>
        </div>
      )}
    </div>
  );
}

/* ── free-reader email capture (warm leads from Module 1) ─────────────────────── */
const LEAD_KEY = "io-lead-done";
function FreeReaderCapture({ source = "module-1" }) {
  const [email, setEmail] = useState("");
  const [done, setDone] = useState(false);
  const [err, setErr] = useState("");
  useEffect(() => { try { if (localStorage.getItem(LEAD_KEY)) setDone(true); } catch (e) {} }, []);
  const submit = (e) => {
    e.preventDefault();
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { setErr("Enter a valid email."); return; }
    fetch(IO_API() + "/lead", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, source }) })
      .then((r) => (r.ok ? (setDone(true), localStorage.setItem(LEAD_KEY, "1")) : r.json().then((d) => setErr(d.error || "Try again."))))
      .catch(() => setErr("Network error - try again."));
  };
  if (done) return (<div className="po-lead reveal"><p className="po-lead-thanks"><Icon name="check" size={16} stroke={2.6} /> You're on the list - we'll only send the useful stuff.</p></div>);
  return (
    <div className="po-lead reveal">
      <div className="po-lead-copy">
        <h3>Want the rest of the roadmap?</h3>
        <p>Drop your email and we'll send new modules, templates, and Canadian-market job tips. No spam; unsubscribe anytime.</p>
      </div>
      <form className="po-lead-form" onSubmit={submit}>
        <input className="po-lead-input" type="email" value={email} placeholder="you@email.com" aria-label="Email"
          onChange={(e) => { setEmail(e.target.value); setErr(""); }} />
        <button className="btn btn-primary" type="submit">Keep me posted</button>
      </form>
      {err && <span className="po-lead-err">{err}</span>}
    </div>
  );
}

/* ── lesson shell ──────────────────────────────────────────────────────────── */
const PREVIEW_SECTIONS = 2;

function scrollToId(elId) {
  const el = document.getElementById(elId);
  if (!el) return;
  const y = el.getBoundingClientRect().top + window.scrollY - 96; // clear sticky nav
  window.scrollTo({ top: Math.max(0, y), behavior: "smooth" });
}

function Lesson({ moduleId }) {
  const m = getModule(moduleId);
  const user = useUser();
  const paid = !!(user && user.paid);
  const [expanded, setExpanded] = useState(false);
  const [frac, setFrac] = useState(0);
  const [activeId, setActiveId] = useState(null);

  // count this visitor as having entered the Modules section (deduped server-side)
  useEffect(() => { try { if (typeof track === "function") track("modules", moduleId); } catch (e) {} }, [moduleId]);

  useEffect(() => {
    const onScroll = () => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      setFrac(max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0);
      const nodes = Array.from(document.querySelectorAll("[data-mapnode]"));
      let cur = null;
      for (const n of nodes) { if (n.getBoundingClientRect().top - 140 <= 0) cur = n.id; }
      setActiveId(cur || (nodes[0] && nodes[0].id) || null);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, [expanded]);

  // useReveal only re-scans on route change, so nudge it when "See more" reveals
  // fresh .reveal content (otherwise the newly-shown sections stay faded in).
  useEffect(() => {
    if (!expanded) return;
    const id = setTimeout(() => window.dispatchEvent(new Event("scroll")), 60);
    return () => clearTimeout(id);
  }, [expanded]);

  if (!m) {
    return (
      <main className="po-lesson wrap">
        <div className="po-lesson-missing">
          <h1>Module not found</h1>
          <a className="btn btn-primary" href="/learn/module-1"><Icon name="map" size={16} /> Back to Module 1</a>
        </div>
      </main>
    );
  }

  const hasContent = m.sections && m.sections.length > 0;
  // Module 1 is free (fully visible); paid users see everything. Others get a preview.
  const unlocked = !!m.free || paid;
  const preview = hasContent ? (unlocked ? m.sections : m.sections.slice(0, PREVIEW_SECTIONS)) : [];
  const rest = hasContent ? (unlocked ? [] : m.sections.slice(PREVIEW_SECTIONS)) : [];
  const gated = hasContent && rest.length > 0 && !expanded;

  // Honest progress: don't let a short preview read as 100%. Scale the current
  // segment's fill by how much of the module is actually rendered right now.
  const totalUnits = hasContent ? m.sections.length + (m.assignment ? 1 : 0) + 1 : 1; // +1 = key points
  const renderedFraction = gated ? Math.min(1, preview.length / totalUnits) : 1;
  const railFrac = frac * renderedFraction;

  // The in-module map: the signature shape (problem → playbook → keep it).
  const mapNodes = [];
  if (hasContent) {
    m.sections.forEach((s, i) => mapNodes.push({
      id: "io-sec-" + s.n, n: s.n, label: s.eyebrow || s.title,
      phase: i === 0 ? "problem" : "playbook", locked: i >= preview.length,
    }));
    mapNodes.push({ id: "io-takeaways", n: "★", label: "Key points to remember", phase: "close", locked: rest.length > 0 });
    if (m.assignment) mapNodes.push({ id: "io-assignment", n: "✓", label: m.assignment.title, phase: "close", locked: rest.length > 0 });
  }

  const onJump = (nd) => {
    if (nd.locked) { window.navigate("/checkout"); } // pay to read locked sections
    else scrollToId(nd.id);
  };

  const Header = (
    <header className="po-lesson-head reveal">
      <span className="po-lesson-kick eyebrow">Module {m.n} of {m.of} · {m.track}</span>
      <h1>{m.title}</h1>
      <p className="po-lesson-subtitle"><RichText text={m.subtitle} /></p>
      {m.meta && (
        <div className="po-lesson-meta">
          {m.meta.map((x, i) => <span className="po-lesson-chip mono" key={i}>{x}</span>)}
        </div>
      )}
      {m.build && <p className="po-lesson-build"><Icon name="rocket" size={16} /> <span><RichText text={m.build} /></span></p>}
    </header>
  );

  return (
    <main className="po-lesson">
      {!hasContent ? (
        <div className="po-lesson-body wrap">
          {Header}
          <LockedModuleCard module={m} />
        </div>
      ) : (
        <>
          <div className="po-lesson-shell wrap">
            <div className="po-lesson-grid">
              <aside className="po-lesson-aside">
                <ModuleNav current={m.id} nodes={mapNodes} activeId={activeId} expanded={expanded} onJump={onJump} frac={railFrac} variant="rail" />
              </aside>
              <div className="po-lesson-col">
                {Header}
                {preview.map((s) => <SectionBlock key={s.n} section={s} />)}
                {gated ? (
                  <SeeMore module={m} rest={rest} />
                ) : (
                  <>
                    {rest.map((s) => <SectionBlock key={s.n} section={s} />)}
                    <KeyTakeaways module={m} />
                    <Assignment data={m.assignment} />
                    <div className="po-lesson-foot reveal">
                      <span className="mono">THE MODULES · MODULE {m.n} OF {m.of} · {m.track.toUpperCase()}</span>
                    </div>
                    <ModuleFeedback moduleId={m.id} />
                    {m.free && !user && <FreeReaderCapture source={"module-" + m.n} />}
                    <MarkComplete module={m} />
                  </>
                )}
              </div>
            </div>
          </div>
          <BottomNav current={m.id} nodes={mapNodes} activeId={activeId} frac={railFrac} scrollFrac={frac} />
        </>
      )}
    </main>
  );
}

Object.assign(window, { Lesson });
