/* eslint-disable */
/* Finaz · Cursos finales — shared shell
 * Hash routing, theme provider, progress tracking, glossary system.
 *
 * Expects window.__FZ_COURSE_SLUG and window.__FZ_THEME to be set by the
 * per-course index.html before this file is parsed.
 */

const { useState, useEffect, useMemo, useRef, useCallback, useContext, createContext, Fragment } = React;

/* ─────────────────────────────────────────────────────────────
   Progress / storage
   ───────────────────────────────────────────────────────────── */
const COURSE_SLUG = window.__FZ_COURSE_SLUG;
const STORAGE_KEY = `__fz_progress_${COURSE_SLUG}`;

function useProgress() {
  const [p, setP] = useState(() => {
    try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); }
    catch { return {}; }
  });
  useEffect(() => {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(p)); } catch {}
  }, [p]);
  const mark = useCallback((epId, status, extra = {}) => {
    setP(prev => ({
      ...prev,
      [epId]: { ...(prev[epId] || {}), status, last: Date.now(), ...extra }
    }));
  }, []);
  return [p, mark, setP];
}

/* ─────────────────────────────────────────────────────────────
   Hash router (no deps)
   #/ → home
   #/m/M01 → module
   #/m/M01/e/E03 → episode (default S1 step)
   #/m/M01/e/E03/s/S2 → episode at S2 step
   #/glossary → glossary list
   #/progress → progress dashboard
   ───────────────────────────────────────────────────────────── */
function parseHash() {
  const h = (location.hash || "#/").replace(/^#/, "");
  const parts = h.split("/").filter(Boolean);
  // Layouts: ["m", modId], ["m", modId, "e", epId], ["m", modId, "e", epId, "s", stepId], ["glossary"], ["progress"]
  if (parts[0] === "m" && parts[2] === "e") {
    return { route: "episode", modId: parts[1], epId: parts[3], stepId: parts[5] || "S1_hook" };
  }
  if (parts[0] === "m") return { route: "module", modId: parts[1] };
  if (parts[0] === "glossary") return { route: "glossary" };
  if (parts[0] === "progress") return { route: "progress" };
  return { route: "home" };
}

function useHashRoute() {
  const [route, setRoute] = useState(parseHash());
  useEffect(() => {
    const onHash = () => { setRoute(parseHash()); window.scrollTo({ top: 0, behavior: "auto" }); };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  return route;
}

function go(path) { location.hash = "#" + path; }

/* ─────────────────────────────────────────────────────────────
   Course data context
   ───────────────────────────────────────────────────────────── */
const CourseCtx = createContext(null);

function findEpisode(course, modId, epId) {
  const m = course.modules.find(x => x.id === modId);
  if (!m) return null;
  const e = m.episodes.find(x => x.id === epId);
  return e ? { module: m, episode: e } : null;
}

function moduleProgress(progress, mod) {
  let done = 0;
  for (const ep of mod.episodes) {
    const st = (progress[`${mod.id}.${ep.id}`] || {}).status;
    if (st === "done") done++;
  }
  return { done, total: mod.episodes.length, pct: mod.episodes.length ? Math.round((done / mod.episodes.length) * 100) : 0 };
}

function courseProgress(progress, course) {
  let done = 0, total = 0;
  course.modules.forEach(m => { total += m.episodes.length; m.episodes.forEach(ep => {
    const st = (progress[`${m.id}.${ep.id}`] || {}).status;
    if (st === "done") done++;
  }); });
  return { done, total, pct: total ? Math.round((done / total) * 100) : 0 };
}

/* ─────────────────────────────────────────────────────────────
   Glossary popover system (positions a popover next to a target span)
   ───────────────────────────────────────────────────────────── */
const GlossaryCtx = createContext(null);

function GlossaryProvider({ children, glossary }) {
  const [target, setTarget] = useState(null); // { id, anchorEl }
  const open = useCallback((id, el) => setTarget({ id, anchorEl: el }), []);
  const close = useCallback(() => setTarget(null), []);
  // Close on outside click / Esc / route change
  useEffect(() => {
    if (!target) return;
    const onKey = (e) => { if (e.key === "Escape") close(); };
    const onClick = (e) => {
      if (!target.anchorEl) return;
      const pop = document.getElementById("__fz-gloss-pop");
      if (pop && pop.contains(e.target)) return;
      if (target.anchorEl.contains(e.target)) return;
      close();
    };
    document.addEventListener("keydown", onKey);
    document.addEventListener("pointerdown", onClick);
    window.addEventListener("hashchange", close);
    return () => {
      document.removeEventListener("keydown", onKey);
      document.removeEventListener("pointerdown", onClick);
      window.removeEventListener("hashchange", close);
    };
  }, [target, close]);

  const byId = useMemo(() => {
    const m = {}; (glossary || []).forEach(g => m[g.id] = g); return m;
  }, [glossary]);

  return (
    <GlossaryCtx.Provider value={{ open, close, byId }}>
      {children}
      {target && byId[target.id] && (
        <GlossaryPopover entry={byId[target.id]} anchor={target.anchorEl} onClose={close} />
      )}
    </GlossaryCtx.Provider>
  );
}

function GlossaryPopover({ entry, anchor, onClose }) {
  const popRef = useRef(null);
  const [pos, setPos] = useState({ left: -9999, top: -9999 });
  useEffect(() => {
    if (!anchor || !popRef.current) return;
    const r = anchor.getBoundingClientRect();
    const pw = popRef.current.offsetWidth;
    const ph = popRef.current.offsetHeight;
    let left = r.left + r.width / 2 - pw / 2;
    let top = r.bottom + 8;
    // Clamp horizontally
    left = Math.max(12, Math.min(left, window.innerWidth - pw - 12));
    // Flip if overflows bottom
    if (top + ph + 12 > window.innerHeight) {
      top = Math.max(12, r.top - ph - 8);
    }
    setPos({ left, top });
  }, [anchor]);
  return (
    <div id="__fz-gloss-pop" className="gloss-popover" ref={popRef}
         style={{ left: pos.left, top: pos.top }}>
      <div className="gloss-pop-head">
        <h4 className="gloss-pop-term">{entry.term}</h4>
        <button className="gloss-pop-close" onClick={onClose} aria-label="Cerrar">×</button>
      </div>
      {entry.category && <div className="gloss-pop-cat">{entry.category.replace(/_/g, " ")}</div>}
      <p className="gloss-pop-def" style={{ marginTop: 8 }}>{entry.definition}</p>
      {entry.formula && (
        <div className="gloss-pop-row">
          <span className="k">Fórmula</span>
          <div className="v"><code>{entry.formula}</code></div>
        </div>
      )}
      {entry.example && (
        <div className="gloss-pop-row">
          <span className="k">Ejemplo</span>
          <div className="v">{entry.example}</div>
        </div>
      )}
      {entry.miscon && (
        <div className="gloss-pop-row">
          <span className="k">Cuidado</span>
          <div className="v">{entry.miscon}</div>
        </div>
      )}
    </div>
  );
}

function GlossTerm({ id, children }) {
  const gctx = useContext(GlossaryCtx);
  const ref = useRef(null);
  const has = gctx && gctx.byId[id];
  if (!has) return <span>{children}</span>;
  return (
    <span ref={ref}
          className="gloss"
          data-gloss={id}
          tabIndex={0}
          role="button"
          onMouseEnter={(e) => gctx.open(id, e.currentTarget)}
          onClick={(e) => { e.stopPropagation(); gctx.open(id, e.currentTarget); }}
          onFocus={(e) => gctx.open(id, e.currentTarget)}>
      {children}
    </span>
  );
}

/* Auto-link plain text: wrap occurrences of glossary terms (case-insensitive,
 * whole-word) with <GlossTerm>. Used in screen_blocks and big_question. */
function AutoGloss({ text, triggerIds }) {
  const gctx = useContext(GlossaryCtx);
  if (!gctx || !text) return text || null;
  const triggers = (triggerIds || []).map(id => gctx.byId[id]).filter(Boolean);
  if (!triggers.length) return text;
  // Sort by length desc to match longer terms first
  triggers.sort((a, b) => b.term.length - a.term.length);
  // Build a single regex
  const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const pattern = new RegExp("(" + triggers.map(t => "\\b" + escapeRe(t.term) + "\\b").join("|") + ")", "gi");
  const parts = String(text).split(pattern);
  return parts.map((p, i) => {
    const match = triggers.find(t => t.term.toLowerCase() === p.toLowerCase());
    if (match) return <GlossTerm key={i} id={match.id}>{p}</GlossTerm>;
    return <Fragment key={i}>{p}</Fragment>;
  });
}

/* ─────────────────────────────────────────────────────────────
   TopBar
   ───────────────────────────────────────────────────────────── */
function TopBar({ course, route, progress, onOpenGlossary, onToggleStyle, style }) {
  const cp = courseProgress(progress, course);
  const crumbs = [];
  crumbs.push({ label: course.code, href: "#/" });
  if (route.route === "module" || route.route === "episode") {
    const m = course.modules.find(x => x.id === route.modId);
    if (m) crumbs.push({ label: `${m.id} · ${m.title}`, href: `#/m/${m.id}` });
    if (route.route === "episode") {
      const m2 = course.modules.find(x => x.id === route.modId);
      const e = m2 && m2.episodes.find(x => x.id === route.epId);
      if (e) crumbs.push({ label: `${e.id} · ${e.title}`, href: `#/m/${route.modId}/e/${e.id}` });
    }
  } else if (route.route === "glossary") {
    crumbs.push({ label: "Glosario", href: "#/glossary" });
  }
  return (
    <header className="topbar">
      <div className="topbar-inner">
        <a className="brand-cluster" href="https://cursos2.iort.io/" aria-label="Volver a cursos2.iort.io">
          <span className="brand-mark">F</span>
          <span className="brand-text">Finaz</span>
          <span className="brand-code">{course.code}</span>
        </a>
        <div className="crumbs">
          {crumbs.map((c, i) => (
            <Fragment key={i}>
              <span className="sep">/</span>
              {i === crumbs.length - 1
                ? <span className="now">{c.label}</span>
                : <a href={c.href}>{c.label}</a>}
            </Fragment>
          ))}
        </div>
        <div className="topbar-right">
          <div className="tb-progress" title={`${cp.done}/${cp.total} episodios completados`}>
            <span className="kicker">Mastery</span>
            <div className="tb-progress-bar"><div className="fill" style={{ width: `${cp.pct}%` }}/></div>
            <span className="mono" style={{ fontSize: 11, color: "var(--ink-muted)" }}>{cp.pct}%</span>
          </div>
          {onToggleStyle && (
            <button className="tb-btn" onClick={onToggleStyle} title="Cambiar estilo A/B">
              Estilo {style}
            </button>
          )}
          <button className="tb-btn" onClick={onOpenGlossary} title="Glosario">
            Glosario · {course.glossary.length}
          </button>
        </div>
      </div>
    </header>
  );
}

/* ─────────────────────────────────────────────────────────────
   Course Home
   ───────────────────────────────────────────────────────────── */
function CourseHome({ course, progress }) {
  const cp = courseProgress(progress, course);
  // Find first incomplete episode for "Continuar" CTA
  let cont = null;
  for (const m of course.modules) {
    for (const e of m.episodes) {
      const st = (progress[`${m.id}.${e.id}`] || {}).status;
      if (st !== "done") { cont = { m, e }; break; }
    }
    if (cont) break;
  }
  const isStarted = cp.done > 0;
  return (
    <>
      <section className="hero">
        <span className="hero-eyebrow"><span className="dot" style={{ width: 6, height: 6, borderRadius: 999, background: "var(--accent)", boxShadow: "0 0 8px var(--accent-glow)" }}></span> {course.code} · {course.duration}</span>
        <h1 className="hero-title">{course.title}</h1>
        <p className="hero-sub">{course.subtitle}</p>
        <div className="hero-promise">{course.promise}</div>
        <div className="hero-stats">
          <div className="hero-stat"><span className="n">{course.n_modules}</span><span className="l">Módulos</span></div>
          <div className="hero-stat"><span className="n">{course.n_episodes}</span><span className="l">Episodios</span></div>
          <div className="hero-stat"><span className="n">{course.n_glossary}</span><span className="l">Términos</span></div>
          <div className="hero-stat"><span className="n">{cp.pct}%</span><span className="l">Mastery</span></div>
        </div>
        <div className="hero-cta">
          {cont && (
            <button className="btn btn-primary" onClick={() => go(`/m/${cont.m.id}/e/${cont.e.id}`)}>
              {isStarted ? "Continuar" : "Empezar"} · {cont.m.id}·{cont.e.id} <span className="btn-arrow">→</span>
            </button>
          )}
          <button className="btn btn-secondary" onClick={() => go(`/glossary`)}>Ver glosario completo</button>
        </div>
      </section>

      <section className="section">
        <div className="section-head">
          <h2>Módulos del curso</h2>
          <span className="meta">{course.n_modules} módulos · {course.n_episodes} episodios</span>
        </div>
        <div className="mod-grid">
          {course.modules.map((m, idx) => {
            const mp = moduleProgress(progress, m);
            let status = "next";
            if (mp.done === mp.total && mp.total > 0) status = "done";
            else if (mp.done > 0) status = "now";
            else if (idx === 0 || (idx > 0 && moduleProgress(progress, course.modules[idx - 1]).pct >= 100)) status = idx === 0 ? "now" : "next";
            // First module always unlocked; others unlocked if previous has any progress
            const isUnlocked = idx === 0 || moduleProgress(progress, course.modules[idx - 1]).done > 0;
            const isLock = !isUnlocked && status === "next";
            return (
              <div key={m.id}
                   className={`mod-card${isLock ? " lock" : ""}`}
                   onClick={() => !isLock && go(`/m/${m.id}`)}
                   role="button" tabIndex={isLock ? -1 : 0}>
                <div className="mod-head">
                  <span className="mod-id">{m.id} · {m.episodes.length} ep.</span>
                  <span className={`mod-status ${isLock ? "lock" : status}`}>
                    <span className="dot"></span>
                    {isLock ? "Bloqueado" : status === "done" ? "Completo" : status === "now" ? "En curso" : "Siguiente"}
                  </span>
                </div>
                <h3>{m.title}</h3>
                {m.subtitle && <p>{m.subtitle}</p>}
                <div className="mod-progress">
                  <div className="mod-progress-bar"><div className={`fill ${status}`} style={{ width: `${mp.pct}%` }}/></div>
                  <div className="mod-progress-meta">
                    <span>{mp.done} / {mp.total} ep.</span>
                    <span>{mp.pct}%</span>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </section>
    </>
  );
}

/* ─────────────────────────────────────────────────────────────
   Module Home
   ───────────────────────────────────────────────────────────── */
function ModuleHome({ course, modId, progress }) {
  const mod = course.modules.find(m => m.id === modId);
  if (!mod) return <div>Módulo no encontrado.</div>;
  const mp = moduleProgress(progress, mod);

  const tagFor = (rt) => {
    if (!rt) return "";
    if (rt.includes("GATE")) return "gate";
    if (rt.includes("CALC")) return "calc";
    if (rt.includes("CASE")) return "case";
    return "";
  };

  return (
    <>
      <section className="hero">
        <span className="hero-eyebrow">Módulo {mod.id} · {course.code}</span>
        <h1 className="hero-title">{mod.title}</h1>
        {mod.subtitle && <p className="hero-sub">{mod.subtitle}</p>}
        <div className="hero-stats">
          <div className="hero-stat"><span className="n">{mod.episodes.length}</span><span className="l">Episodios</span></div>
          <div className="hero-stat"><span className="n">{mp.done}</span><span className="l">Completados</span></div>
          <div className="hero-stat"><span className="n">{mp.pct}%</span><span className="l">Mastery módulo</span></div>
        </div>
        <div className="hero-cta">
          <button className="btn btn-secondary" onClick={() => go("/")}>← Inicio del curso</button>
        </div>
      </section>

      <section className="section">
        <div className="section-head">
          <h2>Episodios</h2>
          <span className="meta">{mod.episodes.length} episodios</span>
        </div>
        <div className="ep-list">
          {mod.episodes.map((ep, idx) => {
            const st = (progress[`${mod.id}.${ep.id}`] || {}).status;
            const cls = st === "done" ? "done" : (idx === 0 || progress[`${mod.id}.${mod.episodes[idx - 1]?.id}`]?.status === "done" ? "now" : "next");
            // Unlock: first episode always; others when previous done
            const prev = idx === 0 ? null : mod.episodes[idx - 1];
            const isUnlocked = idx === 0 || (prev && progress[`${mod.id}.${prev.id}`]?.status === "done");
            const isLock = !isUnlocked && st !== "done";
            return (
              <div key={ep.id}
                   className={`ep-row ${cls}${isLock ? " locked" : ""}`}
                   onClick={() => !isLock && go(`/m/${mod.id}/e/${ep.id}`)}>
                <div className="ep-id">{ep.id}</div>
                <div className="ep-info">
                  <h4>{ep.title}</h4>
                  <div className="ep-meta">
                    {ep.resource_type && <span className={`ep-tag ${tagFor(ep.resource_type)}`}>{ep.resource_type}</span>}
                    {ep.duration_min && <span>{ep.duration_min} min</span>}
                    {ep.glossary_triggers && ep.glossary_triggers.length > 0 && <span>{ep.glossary_triggers.length} términos</span>}
                  </div>
                </div>
                <div className={`ep-status-icon ${st === "done" ? "done" : isLock ? "lock" : cls}`}>
                  {st === "done" ? "✓" : isLock ? "🔒" : (cls === "now" ? "▸" : "○")}
                </div>
                <div className="ep-cta">
                  {isLock ? "Bloqueado" : (st === "done" ? "Repasar" : "Abrir")} <span>→</span>
                </div>
              </div>
            );
          })}
        </div>
      </section>
    </>
  );
}

/* ─────────────────────────────────────────────────────────────
   Episode Player (with S1-S4 steps)
   ───────────────────────────────────────────────────────────── */
const STEPS = [
  { id: "S1_hook", label: "Hook", num: "S1" },
  { id: "S2_concept", label: "Concepto", num: "S2" },
  { id: "S3_lab", label: "Lab", num: "S3" },
  { id: "S4_transfer", label: "Memo", num: "S4" },
];

function EpisodePlayer({ course, modId, epId, stepId, progress, mark }) {
  const found = findEpisode(course, modId, epId);
  if (!found) return <div>Episodio no encontrado.</div>;
  const { module: mod, episode: ep } = found;
  const stepIdx = Math.max(0, STEPS.findIndex(s => s.id === stepId));
  const step = STEPS[stepIdx] || STEPS[0];
  const epKey = `${mod.id}.${ep.id}`;
  const epProgress = progress[epKey] || {};
  const completedSteps = epProgress.steps || {};

  const goStep = (s) => go(`/m/${mod.id}/e/${ep.id}/s/${s}`);
  const next = () => {
    if (stepIdx < STEPS.length - 1) goStep(STEPS[stepIdx + 1].id);
    else {
      mark(epKey, "done", { steps: { ...completedSteps, [step.id]: true } });
      // Try advance to next episode in module
      const nextIdx = mod.episodes.findIndex(e => e.id === ep.id) + 1;
      if (nextIdx < mod.episodes.length) {
        go(`/m/${mod.id}/e/${mod.episodes[nextIdx].id}`);
      } else {
        go(`/m/${mod.id}`);
      }
    }
  };
  const prev = () => {
    if (stepIdx > 0) goStep(STEPS[stepIdx - 1].id);
    else go(`/m/${mod.id}`);
  };

  // Mark step done when navigating away
  const markStepDone = useCallback(() => {
    if (!completedSteps[step.id]) {
      mark(epKey, epProgress.status || "now", {
        steps: { ...completedSteps, [step.id]: true }
      });
    }
  }, [step.id, completedSteps, epKey, epProgress.status, mark]);

  return (
    <>
      <section className="hero" style={{ margin: "12px 0 24px" }}>
        <span className="hero-eyebrow">{mod.id} · {ep.id} · {ep.resource_type}</span>
        <h1 className="hero-title" style={{ fontSize: "clamp(28px,4vw,40px)" }}>{ep.title}</h1>
        <p className="hero-sub" style={{ marginTop: 12 }}>{ep.objective}</p>
      </section>

      <div className="episode-shell">
        <aside className="ep-rail">
          <h5>Flujo del episodio</h5>
          <div className="ep-meta-line">{ep.duration_min} min · {ep.resource_type}</div>
          {STEPS.map((s, i) => {
            const done = !!completedSteps[s.id];
            const active = s.id === step.id;
            return (
              <div key={s.id}
                   className={`ep-step${active ? " active" : ""}${done ? " done" : ""}`}
                   onClick={() => goStep(s.id)}
                   role="button" tabIndex={0}>
                <span className="marker">{done ? "✓" : s.num}</span>
                <span className="lbl">{s.label}</span>
              </div>
            );
          })}
          <div style={{ marginTop: 24, paddingTop: 16, borderTop: "1px solid var(--rule)" }}>
            <button className="btn btn-ghost" onClick={() => go(`/m/${mod.id}`)} style={{ padding: "8px 0", width: "100%", justifyContent: "flex-start" }}>
              ← Volver al módulo
            </button>
          </div>
        </aside>

        <main className="ep-main">
          {step.id === "S1_hook" && <StageHook ep={ep} markStepDone={markStepDone} />}
          {step.id === "S2_concept" && <StageConcept ep={ep} markStepDone={markStepDone} />}
          {step.id === "S3_lab" && <StageLab ep={ep} markStepDone={markStepDone} />}
          {step.id === "S4_transfer" && <StageMemo ep={ep} epKey={epKey} markStepDone={markStepDone} progress={progress} mark={mark} />}

          <div className="stage-nav">
            <button className="btn btn-ghost" onClick={prev}>← {stepIdx === 0 ? "Volver al módulo" : `S${stepIdx} · ${STEPS[stepIdx - 1].label}`}</button>
            <button className="btn btn-primary" onClick={() => { markStepDone(); next(); }}>
              {stepIdx === STEPS.length - 1 ? "Completar episodio" : `Continuar → ${STEPS[stepIdx + 1].label}`}
            </button>
          </div>
        </main>
      </div>
    </>
  );
}

/* Stage components — defined in labs.jsx, but stubs here for safety */
window.__FZ_STAGE_COMPONENTS = window.__FZ_STAGE_COMPONENTS || {};

function StageHook({ ep, markStepDone }) {
  // Voice script player: sequential reveal with click-to-advance
  const [idx, setIdx] = useState(0);
  const lines = ep.voice_script || [];
  useEffect(() => { if (idx >= lines.length - 1) markStepDone(); }, [idx, lines.length, markStepDone]);
  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S1 · Hook</h2>
        <span className="ep-kicker">Story panel</span>
      </div>
      <div className="big-q"><AutoGloss text={ep.big_question} triggerIds={ep.glossary_triggers}/></div>

      <div className="voice-script">
        {lines.slice(0, idx + 1).map((l, i) => (
          <div key={i} className={`voice-line${i === idx ? " current" : " played"}`}>
            <div className={`voice-avatar ${l.key}`}>{(l.speaker || "").split(/\s+/).map(w => w[0]).join("").slice(0, 2).toUpperCase()}</div>
            <div className="voice-body">
              <div className="voice-name">{l.speaker}</div>
              <p className="voice-text"><AutoGloss text={l.text} triggerIds={ep.glossary_triggers}/></p>
              {l.cue && <span className="voice-cue">[ {l.cue} ]</span>}
            </div>
          </div>
        ))}
      </div>

      {idx < lines.length - 1 ? (
        <div style={{ marginTop: 20 }}>
          <button className="btn btn-secondary" onClick={() => setIdx(i => Math.min(i + 1, lines.length - 1))}>
            Siguiente intervención ({idx + 1}/{lines.length}) →
          </button>
        </div>
      ) : lines.length > 0 && (
        <div className="tip-ribbon">Has leído todo el guión. Continúa al S2 para ver la regla del episodio en frío.</div>
      )}

      <div className="tip-ribbon" style={{ marginTop: 24 }}>
        <b>Antes de calcular:</b> escribe a lápiz qué esperas encontrar y por qué.
        El sistema registra qué glossary terms consultas en S2.
      </div>
    </div>
  );
}

function StageConcept({ ep, markStepDone }) {
  useEffect(() => { markStepDone(); }, [markStepDone]);
  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S2 · Concepto</h2>
        <span className="ep-kicker">Regla anotada</span>
      </div>
      <div className="objective"><b>Objetivo:</b> {ep.objective}</div>

      {ep.screen_blocks && ep.screen_blocks.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: 14, marginBottom: 18 }}>
          {ep.screen_blocks.map((b, i) => (
            <div key={i} style={{
              padding: "14px 16px",
              background: "var(--bg-elev-1)",
              borderRadius: 8,
              borderLeft: i === 2 ? "3px solid var(--accent)" : "3px solid var(--rule)",
              fontSize: 14.5,
              lineHeight: 1.55,
              color: "var(--ink)"
            }}>
              <AutoGloss text={b} triggerIds={ep.glossary_triggers}/>
            </div>
          ))}
        </div>
      )}

      {ep.glossary_triggers && ep.glossary_triggers.length > 0 && (
        <div style={{ marginTop: 16 }}>
          <span className="kicker">Términos del episodio · pulsa para ver tarjeta</span>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 10 }}>
            {ep.glossary_triggers.map(gid => (
              <InlineGlossChip key={gid} gid={gid}/>
            ))}
          </div>
        </div>
      )}

      {ep.guardrails && ep.guardrails.length > 0 && (
        <div className="tip-ribbon" style={{ marginTop: 24, borderColor: "var(--amber)", background: "var(--amber-soft)", color: "var(--amber)" }}>
          <b>Guardrails del episodio:</b>
          <ul style={{ margin: "8px 0 0", paddingLeft: 18 }}>
            {ep.guardrails.map((g, i) => <li key={i}>{g}</li>)}
          </ul>
        </div>
      )}
    </div>
  );
}

function InlineGlossChip({ gid }) {
  const gctx = useContext(GlossaryCtx);
  const entry = gctx && gctx.byId[gid];
  if (!entry) return null;
  return <GlossTerm id={gid}>{entry.term}</GlossTerm>;
}

/* StageLab and StageMemo defined in labs.jsx */
window.__FZ_REGISTRY = { StageHook, StageConcept };

function StageLab({ ep, markStepDone }) {
  // Polymorphic lab dispatcher
  const Lab = window.FZ_LABS && (
    window.FZ_LABS[ep.resource_type] ||
    window.FZ_LABS[ep.resource_type.split("+")[0]] ||
    window.FZ_LABS.DEFAULT
  );
  return Lab ? <Lab ep={ep} markStepDone={markStepDone}/> : (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">{ep.resource_type}</span>
      </div>
      <div className="objective">Cargando lab…</div>
    </div>
  );
}

function StageMemo({ ep, epKey, markStepDone, progress, mark }) {
  const exercise = ep.exercise || {};
  const tasks = exercise.tasks || [];
  const rubric = exercise.rubric || {};
  const feedback = exercise.feedback_bank || {};
  const saved = (progress[epKey] && progress[epKey].memo) || "";
  const [text, setText] = useState(saved);
  const [submitted, setSubmitted] = useState(false);
  const words = text.trim().split(/\s+/).filter(Boolean).length;
  const target = 150;
  // Forbidden words / weak language linter
  const banned = ["barata", "cara", "estafa", "fraude", "100% seguro", "garantizado", "sin duda"];
  const found = banned.filter(w => new RegExp("\\b" + w + "\\b", "i").test(text));
  const overclaimSignals = /(seguro|nunca|siempre|todos|nadie|exacto)/i.test(text);

  useEffect(() => {
    const t = setTimeout(() => {
      mark(epKey, progress[epKey]?.status || "now", { memo: text });
    }, 600);
    return () => clearTimeout(t);
  }, [text, epKey, mark, progress]);

  const submit = () => {
    setSubmitted(true);
    markStepDone();
  };

  let fbKey = "excellent";
  let fbClass = "correct";
  if (found.length) { fbKey = "overclaim"; fbClass = "partial"; }
  else if (words < 100) { fbKey = "underclaim"; fbClass = "partial"; }
  else if (overclaimSignals) { fbKey = "overclaim"; fbClass = "partial"; }

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S4 · Memo</h2>
        <span className="ep-kicker">Entregable observable</span>
      </div>

      {exercise.prompt && (
        <div className="big-q" style={{ fontSize: 18 }}>
          <AutoGloss text={exercise.prompt} triggerIds={ep.glossary_triggers}/>
        </div>
      )}

      {tasks.length > 0 && (
        <div style={{ marginBottom: 18 }}>
          <span className="kicker">Tareas del entregable</span>
          <ol style={{ paddingLeft: 22, color: "var(--ink)", fontSize: 14, lineHeight: 1.6, marginTop: 8 }}>
            {tasks.map((t, i) => <li key={i}>{t}</li>)}
          </ol>
        </div>
      )}

      <div className="memo">
        <span className="kicker">Tu memo · objetivo {target} palabras · sin “barata”, “cara”, “fraude”…</span>
        <textarea
          className="memo-textarea"
          placeholder="Escribe aquí tu veredicto. Cita 2 drivers cuantitativos. Lenguaje prudente. Indica qué evidencia revisarías después."
          value={text}
          onChange={e => setText(e.target.value)}
        />
        <div className="memo-meta">
          <span className={words < 100 ? "warn" : words > 250 ? "warn" : "ok"}>{words} palabras (objetivo ≥ {target})</span>
          <span className={found.length ? "bad" : "ok"}>{found.length ? `${found.length} palabra(s) prohibida(s): ${found.join(", ")}` : "Lenguaje prudente"}</span>
        </div>
        {found.length > 0 && (
          <div className="memo-linter">
            <b>Linter Finaz:</b> usa expresiones como “premium aparente”, “descuento respecto al teórico Gordon” o “evidencia pendiente”. Evita adjetivos absolutos.
          </div>
        )}
      </div>

      {!submitted ? (
        <div style={{ marginTop: 20 }}>
          <button className="btn btn-primary" disabled={words < 30} onClick={submit}>
            Enviar memo
          </button>
        </div>
      ) : (
        <div className={`lab-feedback ${fbClass}`} style={{ marginTop: 20 }}>
          <h5>{fbClass === "correct" ? "Feedback excelente" : "Feedback parcial"}</h5>
          <p style={{ margin: 0 }}>{feedback[fbKey] || feedback.excellent || "Tu respuesta queda registrada. Revisa los criterios de la rúbrica."}</p>
          {Object.keys(rubric).length > 0 && (
            <div style={{ marginTop: 14 }}>
              <span className="kicker">Rúbrica del ejercicio · 100 puntos</span>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 }}>
                {Object.entries(rubric).map(([k, v]) => (
                  <span key={k} className="ep-tag" style={{ background: "var(--bg-elev-2)", color: "var(--ink-muted)" }}>
                    {k.replace(/_/g, " ")} · {v} pts
                  </span>
                ))}
              </div>
            </div>
          )}
        </div>
      )}

      {ep.success_criteria && (
        <div className="tip-ribbon" style={{ marginTop: 18 }}>
          <b>Mastery requiere:</b> {ep.success_criteria}
        </div>
      )}
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────
   Glossary index page
   ───────────────────────────────────────────────────────────── */
function GlossaryView({ course }) {
  const [q, setQ] = useState("");
  const filtered = useMemo(() => {
    const qq = q.trim().toLowerCase();
    if (!qq) return course.glossary;
    return course.glossary.filter(g =>
      g.term.toLowerCase().includes(qq) ||
      (g.definition || "").toLowerCase().includes(qq) ||
      (g.category || "").toLowerCase().includes(qq)
    );
  }, [q, course.glossary]);
  // group by category
  const groups = {};
  filtered.forEach(g => {
    const cat = g.category || "general";
    groups[cat] = groups[cat] || [];
    groups[cat].push(g);
  });
  const catLabel = (c) => c.replace(/_/g, " ").replace(/\b\w/g, ch => ch.toUpperCase());
  return (
    <>
      <section className="hero">
        <span className="hero-eyebrow">Glosario · {course.glossary.length} términos</span>
        <h1 className="hero-title">{course.code} · Glosario inline</h1>
        <p className="hero-sub">Tarjetas operativas con definición, fórmula, ejemplo y aviso. Pulsa cualquier término en el flujo del episodio para abrir su tarjeta.</p>
        <div style={{ marginTop: 20, maxWidth: 480 }}>
          <input className="gloss-drawer-search" placeholder="Buscar término, definición o categoría…"
                 value={q} onChange={e => setQ(e.target.value)} autoFocus/>
        </div>
        <div className="hero-cta" style={{ marginTop: 18 }}>
          <button className="btn btn-secondary" onClick={() => go("/")}>← Inicio del curso</button>
        </div>
      </section>

      {Object.keys(groups).sort().map(cat => (
        <section className="section" key={cat}>
          <div className="section-head">
            <h2>{catLabel(cat)}</h2>
            <span className="meta">{groups[cat].length} términos</span>
          </div>
          <div className="mod-grid">
            {groups[cat].map(g => (
              <div key={g.id} className="mod-card" style={{ cursor: "default" }}>
                <div className="mod-head">
                  <span className="mod-id" style={{ fontSize: 10 }}>{g.id.replace("GLOSS.AF.", "")}</span>
                  {g.formula && <span className="mod-status next"><span className="dot"></span>FÓRMULA</span>}
                </div>
                <h3 style={{ fontSize: 18 }}>{g.term}</h3>
                <p style={{ fontSize: 13 }}>{g.definition}</p>
                {g.formula && (
                  <div style={{ marginTop: 4, padding: "8px 10px", background: "var(--bg-elev-2)", borderRadius: 6, fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--ink)" }}>
                    {g.formula}
                  </div>
                )}
                {g.example && (
                  <p style={{ fontSize: 12.5, color: "var(--ink-quiet)", marginTop: 6 }}><b>Ejemplo:</b> {g.example}</p>
                )}
              </div>
            ))}
          </div>
        </section>
      ))}

      {filtered.length === 0 && <div className="tip-ribbon">Sin resultados para “{q}”. Prueba otra palabra.</div>}
    </>
  );
}

/* ─────────────────────────────────────────────────────────────
   App root
   ───────────────────────────────────────────────────────────── */
function App({ course }) {
  const route = useHashRoute();
  const [progress, mark] = useProgress();
  const [styleAB, setStyleAB] = useState(() => {
    try { return localStorage.getItem("__fz_style_ab") || "A"; } catch { return "A"; }
  });

  // For ab-editorial theme, write data-style to <html>
  useEffect(() => {
    if (window.__FZ_THEME === "ab-editorial") {
      document.documentElement.setAttribute("data-style", styleAB);
      try { localStorage.setItem("__fz_style_ab", styleAB); } catch {}
    }
  }, [styleAB]);

  const showABToggle = window.__FZ_THEME === "ab-editorial";

  return (
    <GlossaryProvider glossary={course.glossary}>
      <div className="app-shell">
        <TopBar course={course} route={route} progress={progress}
                onOpenGlossary={() => go("/glossary")}
                onToggleStyle={showABToggle ? () => setStyleAB(s => s === "A" ? "B" : "A") : null}
                style={styleAB}/>
        <div className="app-content">
          {route.route === "home" && <CourseHome course={course} progress={progress}/>}
          {route.route === "module" && <ModuleHome course={course} modId={route.modId} progress={progress}/>}
          {route.route === "episode" && <EpisodePlayer course={course} modId={route.modId} epId={route.epId} stepId={route.stepId} progress={progress} mark={mark}/>}
          {route.route === "glossary" && <GlossaryView course={course}/>}
        </div>
        <footer className="site-foot">
          <span>Finaz · {course.code} · {course.n_modules} módulos · {course.n_episodes} episodios · {course.n_glossary} términos</span>
          <span>cursos2.iort.io</span>
        </footer>
      </div>
    </GlossaryProvider>
  );
}

/* Boot: load course JSON and mount */
(async function boot() {
  document.documentElement.setAttribute("data-theme", window.__FZ_THEME || "editorial-light");
  try {
    const res = await fetch(`../_final/data/${COURSE_SLUG}.json`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const course = await res.json();
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App course={course}/>);
  } catch (e) {
    document.getElementById("root").innerHTML = `<div style="padding:60px 28px;font:500 14px var(--font-sans);color:var(--ink-muted);max-width:600px;margin:0 auto">
      <h1 style="font-family:var(--font-serif);font-size:28px;color:var(--ink);margin-bottom:8px">No se pudo cargar el curso</h1>
      <p>Error: ${e.message}</p>
      <p><a href="https://cursos2.iort.io/" style="color:var(--accent)">← Volver a cursos2.iort.io</a></p>
    </div>`;
    console.error(e);
  }
})();
