/* eslint-disable */
/* Finaz · Labs polimórficos para S3 — basados en resource_type
 * Each lab uses content from ep.exercise and the episode's own data.
 * Labs are registered on window.FZ_LABS keyed by resource_type prefix.
 */

const { useState: useStateLab, useEffect: useEffectLab, useMemo: useMemoLab, useRef: useRefLab, useContext: useContextLab } = React;

const FZ_LABS = window.FZ_LABS = window.FZ_LABS || {};

/* Synthetic data sets used across labs — pedagogically reasonable, no real tickers */
const SYNTH = {
  twoCompanies: {
    a: { name: "Vermillion Foods", ticker: "VRMF · sintético", pe: 14.0, g: 0.07, roe: 0.22, ke: 0.09, payout: 0.30, cashConv: 0.95, recurrence: 0.85, oneOffs: 4 },
    b: { name: "Calderón Industrial", ticker: "CLDR · sintético", pe: 14.0, g: 0.02, roe: 0.08, ke: 0.11, payout: 0.60, cashConv: 0.72, recurrence: 0.55, oneOffs: 11 },
  },
  threeQualities: [
    { id: "alpha", name: "Aurora Components", eps: 4.2, cashConv: 0.97, recurrence: 0.91, oneOffs: 2, transparency: 9 },
    { id: "beta",  name: "Bastión Servicios", eps: 4.2, cashConv: 0.78, recurrence: 0.65, oneOffs: 6, transparency: 6 },
    { id: "gamma", name: "Cíclica Manufactura", eps: 4.2, cashConv: 0.55, recurrence: 0.35, oneOffs: 11, transparency: 4 },
  ],
  twelveAdjustments: [
    { id: 1, label: "Plusvalía venta filial × (1-t)", bin: "one-off-after-tax" },
    { id: 2, label: "Restructuring trimestre puntual", bin: "one-off" },
    { id: 3, label: "Restructuring recurrente 3 años", bin: "recurrente" },
    { id: 4, label: "SBC 4% revenue", bin: "recurrente" },
    { id: 5, label: "SBC 22% revenue", bin: "asimetrico" },
    { id: 6, label: "Goodwill impairment única", bin: "one-off" },
    { id: 7, label: "Litigio · provisión nueva", bin: "asimetrico" },
    { id: 8, label: "Coste seguros recurrente", bin: "recurrente" },
    { id: 9, label: "Plusvalía bruta antes de impuestos", bin: "asimetrico" },
    { id: 10, label: "Cambio criterio contable", bin: "one-off-after-tax" },
    { id: 11, label: "Pérdida one-off neta", bin: "one-off-after-tax" },
    { id: 12, label: "Devaluación inventario única", bin: "one-off" },
  ],
  bins: [
    { key: "recurrente", label: "Recurrente", desc: "Forma parte del run-rate normal del negocio." },
    { key: "one-off", label: "One-off (bruto)", desc: "Excepcional, antes de impuestos." },
    { key: "one-off-after-tax", label: "One-off (after-tax)", desc: "Excepcional, neto de impuestos." },
    { key: "asimetrico", label: "Asimétrico", desc: "Pide tratamiento distinto al one-off estándar." },
  ],
};

/* ─────────────────────────────────────────────────────────────
   LAB: CASE-X — dos empresas con mismo múltiplo, drivers distintos
   ───────────────────────────────────────────────────────────── */
function CaseXLab({ ep, markStepDone }) {
  const [picked, setPicked] = useStateLab(null);
  const [submitted, setSubmitted] = useStateLab(false);
  const { a, b } = SYNTH.twoCompanies;

  // P/E teórico Gordon: (1 - g/ROE) / (Ke - g)
  const peGordon = (g, roe, ke) => {
    if (ke - g <= 0) return Infinity;
    return (1 - g / roe) / (ke - g);
  };
  const peA = peGordon(a.g, a.roe, a.ke);
  const peB = peGordon(b.g, b.roe, b.ke);
  const premiumA = a.pe / peA - 1;
  const premiumB = b.pe / peB - 1;
  // The "right" answer: A deserves premium because its theoretical P/E is much higher than its observed P/E.
  const correctId = peA > peB ? "a" : "b";
  const correct = picked === correctId;

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">CASE-X · árbol de bifurcación</span>
      </div>

      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag">CASE-X</span>
          <h3>Dos empresas, mismo P/E. ¿Cuál merece premium?</h3>
        </div>
        <p className="lab-prompt">
          Ambas cotizan a P/E 14×. Pulsa la tarjeta de la que crees que merece pagar un premium.
          Decide a partir de los drivers fundamentales (<GlossTerm id="GLOSS.AF.FUND_DRIVER">g, ROE, Ke</GlossTerm>),
          no a partir del múltiplo aislado.
        </p>

        <div className="case-x">
          {[a, b].map((co, i) => {
            const id = i === 0 ? "a" : "b";
            const pe = i === 0 ? peA : peB;
            const prem = i === 0 ? premiumA : premiumB;
            return (
              <div key={id} className={`case-card${picked === id ? " selected" : ""}`}
                   onClick={() => !submitted && setPicked(id)}>
                <h4>{co.name}</h4>
                <div className="ticker">{co.ticker}</div>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", padding: "8px 0", borderBottom: "1px solid var(--rule-quiet)" }}>
                  <span className="kicker">P/E observado</span>
                  <span style={{ font: "600 28px var(--font-mono)", color: "var(--ink-strong)" }}>{co.pe.toFixed(1)}<span style={{ fontSize: 14, color: "var(--ink-muted)" }}>×</span></span>
                </div>
                <div className="drivers">
                  <div className="driver pos"><div className="k">Crecimiento g</div><div className="v">{(co.g * 100).toFixed(0)}%</div></div>
                  <div className="driver pos"><div className="k">ROE</div><div className="v">{(co.roe * 100).toFixed(0)}%</div></div>
                  <div className="driver"><div className="k">Ke (riesgo)</div><div className="v">{(co.ke * 100).toFixed(0)}%</div></div>
                  <div className="driver"><div className="k">Payout</div><div className="v">{(co.payout * 100).toFixed(0)}%</div></div>
                </div>
                {submitted && (
                  <div style={{ marginTop: 12, padding: "10px 12px", background: "var(--bg-elev-2)", borderRadius: 6 }}>
                    <div style={{ fontSize: 11, color: "var(--ink-quiet)", fontFamily: "var(--font-mono)", textTransform: "uppercase", letterSpacing: 0.06 }}>P/E teórico Gordon</div>
                    <div style={{ font: "600 18px var(--font-mono)", color: prem < 0 ? "var(--green)" : "var(--red)" }}>
                      {pe.toFixed(1)}×  {prem >= 0 ? "·" : ""} {Math.abs(prem * 100).toFixed(0)}% {prem >= 0 ? "premium" : "descuento"}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>

        <div className="lab-actions">
          {!submitted && <button className="btn btn-primary" disabled={!picked} onClick={() => { setSubmitted(true); markStepDone(); }}>Calcular P/E teórico y comparar</button>}
          {submitted && <button className="btn btn-secondary" onClick={() => { setPicked(null); setSubmitted(false); }}>Reintentar</button>}
        </div>

        {submitted && (
          <div className={`lab-feedback ${correct ? "correct" : "partial"}`}>
            <h5>{correct ? "Veredicto consistente con los drivers" : "Reconsidera"}</h5>
            <p style={{ margin: 0 }}>
              {correct
                ? `Bien. ${a.name} (g 12% / ROE 25% / Ke 9%) tiene un P/E teórico Gordon de ${peA.toFixed(1)}× — mucho mayor que su P/E observado de 14×. El mercado le descuenta. ${b.name}, en cambio, con g 3% / ROE 8% / Ke 11% justifica solo ${peB.toFixed(1)}× — el mercado le paga premium sobre su teórico.`
                : `Mira los drivers: g/ROE para una y g/ROE para la otra. El múltiplo aislado dice lo mismo (14×), pero los drivers dicen historias opuestas. ${a.name} crece más, con ROE mayor y Ke menor — su P/E teórico Gordon es ${peA.toFixed(1)}×. ${b.name} apenas justifica ${peB.toFixed(1)}×.`}
            </p>
            <p style={{ marginTop: 10, color: "var(--ink-muted)", fontSize: 13 }}>
              <b>Norma Finaz:</b> ningún múltiplo se interpreta sin sus drivers fundamentales (g, ROE, riesgo). Si P/E observable supera P/E teórico, el mercado paga premium; si está por debajo, descuenta.
            </p>
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["CASE-X"] = CaseXLab;

/* ─────────────────────────────────────────────────────────────
   LAB: CALC — calculadora P/E teórico Gordon + comparación
   ───────────────────────────────────────────────────────────── */
function CalcLab({ ep, markStepDone }) {
  const [g, setG] = useStateLab(0.06);
  const [roe, setRoe] = useStateLab(0.18);
  const [ke, setKe] = useStateLab(0.09);
  const [peObs, setPeObs] = useStateLab(15);
  useEffectLab(() => { markStepDone(); }, []);
  let pe = (ke - g > 0) ? (1 - g / roe) / (ke - g) : Infinity;
  const diff = peObs / pe - 1;

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">CALC · calculadora interactiva</span>
      </div>

      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag">CALC</span>
          <h3>P/E teórico Gordon · ¿paga premium o descuento?</h3>
        </div>
        <p className="lab-prompt">
          Mueve los drivers y compara el P/E observado con el P/E teórico Gordon.
          La fórmula: <code style={{ fontFamily: "var(--font-mono)", background: "var(--bg-elev-2)", padding: "2px 6px", borderRadius: 4 }}>P/E* = (1 − g/ROE) / (Ke − g)</code>.
        </p>

        <div className="calc-grid">
          <div className="calc-inputs">
            <label>
              <span className="label-text">Crecimiento sostenible g · {(g * 100).toFixed(1)}%</span>
              <input type="range" min="0" max="0.20" step="0.005" value={g} onChange={e => setG(+e.target.value)}/>
            </label>
            <label>
              <span className="label-text">ROE · {(roe * 100).toFixed(0)}%</span>
              <input type="range" min="0.05" max="0.40" step="0.01" value={roe} onChange={e => setRoe(+e.target.value)}/>
            </label>
            <label>
              <span className="label-text">Ke (coste del equity) · {(ke * 100).toFixed(1)}%</span>
              <input type="range" min="0.05" max="0.15" step="0.005" value={ke} onChange={e => setKe(+e.target.value)}/>
            </label>
            <label>
              <span className="label-text">P/E observado del mercado</span>
              <input type="number" min="3" max="40" step="0.5" value={peObs} onChange={e => setPeObs(+e.target.value)}/>
            </label>
          </div>

          <div className="calc-result">
            <div className="formula">
              <code>P/E* = (1 − {g.toFixed(2)}/{roe.toFixed(2)}) / ({ke.toFixed(3)} − {g.toFixed(3)})</code>
            </div>
            <div>
              <span className="kicker">P/E teórico justificado</span>
              <div className="answer">{Number.isFinite(pe) ? pe.toFixed(1) : "∞"}<span className="answer-unit">×</span></div>
            </div>
            <div className="compare">
              <b>Observado vs teórico:</b> {peObs.toFixed(1)}× / {Number.isFinite(pe) ? pe.toFixed(1) : "∞"}× = {Number.isFinite(diff) ? (
                <span style={{ color: diff > 0 ? "var(--red)" : "var(--green)", fontFamily: "var(--font-mono)", fontWeight: 600 }}>
                  {diff > 0 ? "+" : ""}{(diff * 100).toFixed(0)}% {diff > 0 ? "premium" : "descuento"}
                </span>
              ) : "n/a"}
              <br/><br/>
              {Number.isFinite(pe) && Math.abs(diff) > 0.05 && (
                <>
                  {diff > 0
                    ? <>El mercado paga premium. Antes de aceptar, defiende qué driver justifica el extra: mayor recurrencia, menor riesgo no capturado por β, opcionalidad real.</>
                    : <>El mercado descuenta. Antes de comprar, busca qué señal te falta: ¿quality de earnings dudosa, riesgo no en β, g insostenible?</>
                  }
                </>
              )}
              {Number.isFinite(pe) && Math.abs(diff) <= 0.05 && (
                <>Observado y teórico están alineados. El precio es consistente con tus drivers — quedan los <GlossTerm id="GLOSS.AF.FUND_DRIVER">drivers</GlossTerm> a verificar.</>
              )}
              {!Number.isFinite(pe) && <>Ke ≤ g rompe el modelo Gordon (la serie diverge). Reduce g o sube Ke.</>}
            </div>
          </div>
        </div>

        <div className="tip-ribbon" style={{ marginTop: 16 }}>
          <b>Casos típicos:</b> g 5% / ROE 18% / Ke 9% → P/E* ≈ 18.5× (alta calidad).
          g 2% / ROE 8% / Ke 11% → P/E* ≈ 8.4× (baja calidad).
        </div>
      </div>
    </div>
  );
}
FZ_LABS["CALC"] = CalcLab;
FZ_LABS["CALC+CASE"] = CalcLab;
FZ_LABS["CALC+NARR"] = CalcLab;

/* ─────────────────────────────────────────────────────────────
   LAB: CASE — ranking de 3 empresas por calidad
   ───────────────────────────────────────────────────────────── */
function CaseLab({ ep, markStepDone }) {
  const [order, setOrder] = useStateLab(SYNTH.threeQualities.map(c => c.id));
  const [submitted, setSubmitted] = useStateLab(false);
  // Quality score: cashConv * 0.4 + recurrence * 0.4 + transparency/10 * 0.2
  const score = (c) => c.cashConv * 0.4 + c.recurrence * 0.4 + (c.transparency / 10) * 0.2;
  const correctOrder = [...SYNTH.threeQualities].sort((a, b) => score(b) - score(a)).map(c => c.id);
  const isCorrect = JSON.stringify(order) === JSON.stringify(correctOrder);
  const move = (id, dir) => {
    const idx = order.indexOf(id);
    if (idx < 0) return;
    const tgt = idx + dir;
    if (tgt < 0 || tgt >= order.length) return;
    const next = order.slice();
    [next[idx], next[tgt]] = [next[tgt], next[idx]];
    setOrder(next);
  };
  const byId = Object.fromEntries(SYNTH.threeQualities.map(c => [c.id, c]));
  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">CASE · ordenar por calidad</span>
      </div>

      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag">CASE</span>
          <h3>Mismo EPS, tres calidades · ordena sin mirar precio</h3>
        </div>
        <p className="lab-prompt">
          Tres empresas con EPS reportado idéntico (4,20 €). Usa cash conversion, recurrencia y transparencia para ordenarlas de mayor a menor <em>calidad del beneficio</em>.
        </p>

        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {order.map((id, i) => {
            const co = byId[id];
            const correctIdx = correctOrder.indexOf(id);
            const correctPos = submitted && correctIdx === i;
            return (
              <div key={id} style={{
                display: "grid",
                gridTemplateColumns: "auto 1fr auto",
                gap: 14,
                alignItems: "center",
                padding: "14px 18px",
                background: "var(--bg-card)",
                border: "1px solid " + (submitted ? (correctPos ? "var(--green-border)" : "var(--rule-strong)") : "var(--rule)"),
                borderRadius: 10,
              }}>
                <div style={{
                  width: 30, height: 30, borderRadius: 999,
                  background: submitted ? (correctPos ? "var(--green)" : "var(--red-soft)") : "var(--bg-elev-2)",
                  color: submitted ? (correctPos ? "var(--accent-on)" : "var(--red)") : "var(--ink)",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  font: "600 14px var(--font-mono)"
                }}>{i + 1}</div>
                <div>
                  <div style={{ font: "500 15px var(--font-serif)", color: "var(--ink-strong)", letterSpacing: "-0.01em" }}>{co.name}</div>
                  <div style={{ font: "500 11.5px var(--font-mono)", color: "var(--ink-muted)", marginTop: 3, display: "flex", gap: 14, flexWrap: "wrap" }}>
                    <span>EPS {co.eps.toFixed(2)} €</span>
                    <span>Cash conv {(co.cashConv * 100).toFixed(0)}%</span>
                    <span>Recurrencia {(co.recurrence * 100).toFixed(0)}%</span>
                    <span>Transparencia {co.transparency}/10</span>
                  </div>
                </div>
                <div style={{ display: "flex", gap: 4 }}>
                  <button className="btn btn-ghost" style={{ padding: "6px 10px" }} disabled={i === 0 || submitted} onClick={() => move(id, -1)}>↑</button>
                  <button className="btn btn-ghost" style={{ padding: "6px 10px" }} disabled={i === order.length - 1 || submitted} onClick={() => move(id, 1)}>↓</button>
                </div>
              </div>
            );
          })}
        </div>

        <div className="lab-actions">
          {!submitted && <button className="btn btn-primary" onClick={() => { setSubmitted(true); markStepDone(); }}>Comparar con scoring de calidad</button>}
          {submitted && <button className="btn btn-secondary" onClick={() => { setOrder(SYNTH.threeQualities.map(c => c.id)); setSubmitted(false); }}>Reordenar</button>}
        </div>

        {submitted && (
          <div className={`lab-feedback ${isCorrect ? "correct" : "partial"}`}>
            <h5>{isCorrect ? "Ranking consistente" : "Reordena"}</h5>
            <p style={{ margin: 0 }}>
              Quality score = 0.4·cashConv + 0.4·recurrencia + 0.2·transparencia/10.
              El orden correcto es <b>{correctOrder.map(id => byId[id].name).join(" → ")}</b>.
              Empresas con mismo EPS pueden tener calidades opuestas: lo que hace al beneficio recurrente es cash, base de clientes y transparencia de notas.
            </p>
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["CASE"] = CaseLab;
FZ_LABS["MAP+CASE"] = CaseLab;

/* ─────────────────────────────────────────────────────────────
   LAB: VIDEO+Q-MAP — drag-drop classifier (12 adjustments → 4 bins)
   ───────────────────────────────────────────────────────────── */
function QMapLab({ ep, markStepDone }) {
  const [pool, setPool] = useStateLab(() => SYNTH.twelveAdjustments.map(a => a.id));
  const [placed, setPlaced] = useStateLab({}); // id → binKey
  const [submitted, setSubmitted] = useStateLab(false);
  const [draggedId, setDraggedId] = useStateLab(null);
  const [overBin, setOverBin] = useStateLab(null);
  const byId = Object.fromEntries(SYNTH.twelveAdjustments.map(a => [a.id, a]));

  const handleDragStart = (e, id) => {
    setDraggedId(id);
    e.dataTransfer.effectAllowed = "move";
    try { e.dataTransfer.setData("text/plain", String(id)); } catch {}
  };
  const handleDragOver = (e, binKey) => { e.preventDefault(); setOverBin(binKey); };
  const handleDragLeave = () => setOverBin(null);
  const handleDrop = (e, binKey) => {
    e.preventDefault();
    const id = Number(e.dataTransfer.getData("text/plain")) || draggedId;
    if (!id) return;
    setPool(p => p.filter(x => x !== id));
    setPlaced(p => ({ ...p, [id]: binKey }));
    // Also remove from any other bin if already placed
    setOverBin(null);
    setDraggedId(null);
  };
  const handleDropPool = (e) => {
    e.preventDefault();
    const id = Number(e.dataTransfer.getData("text/plain")) || draggedId;
    if (!id) return;
    setPlaced(p => {
      const np = { ...p }; delete np[id]; return np;
    });
    setPool(p => p.includes(id) ? p : [...p, id]);
    setOverBin(null);
    setDraggedId(null);
  };

  const checks = SYNTH.twelveAdjustments.map(a => ({ id: a.id, ok: placed[a.id] === a.bin }));
  const correctCount = checks.filter(c => c.ok).length;
  const totalPlaced = Object.keys(placed).length;
  const pct = totalPlaced === 0 ? 0 : Math.round((correctCount / SYNTH.twelveAdjustments.length) * 100);
  const passing = pct >= 85;

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">Q-MAP · clasificación drag &amp; drop</span>
      </div>

      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag">Q-MAP</span>
          <h3>12 ajustes a earnings · arrástralos al cubo correcto</h3>
        </div>
        <p className="lab-prompt">
          Para construir <GlossTerm id="GLOSS.AF.EARNINGS_CLEAN_M01">earnings depurados</GlossTerm> hay que decidir
          el tratamiento de cada ajuste. Mastery requiere ≥ 85% de aciertos.
        </p>

        <div className="qmap-pool" onDragOver={(e) => { e.preventDefault(); setOverBin("__pool__"); }} onDrop={handleDropPool}>
          <span className="kicker" style={{ width: "100%", marginBottom: 4 }}>
            Pool de ajustes ({pool.length} sin clasificar)
          </span>
          {pool.length === 0 && <span style={{ fontSize: 12.5, color: "var(--ink-quiet)" }}>Todos clasificados. Pulsa “Comprobar”.</span>}
          {pool.map(id => (
            <div key={id} className={`qmap-chip${draggedId === id ? " dragging" : ""}`}
                 draggable onDragStart={e => handleDragStart(e, id)} onDragEnd={() => setDraggedId(null)}>
              {byId[id].label}
            </div>
          ))}
        </div>

        <div className="qmap">
          {SYNTH.bins.map(bin => {
            const itemsInBin = SYNTH.twelveAdjustments.filter(a => placed[a.id] === bin.key);
            return (
              <div key={bin.key}
                   className={`qmap-bin${overBin === bin.key ? " over" : ""}`}
                   onDragOver={e => handleDragOver(e, bin.key)} onDragLeave={handleDragLeave}
                   onDrop={e => handleDrop(e, bin.key)}>
                <h5>{bin.label} · {itemsInBin.length} <span style={{ color: "var(--ink-quiet)", fontWeight: 400 }}>· {bin.desc}</span></h5>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                  {itemsInBin.map(a => {
                    const isOk = submitted && placed[a.id] === a.bin;
                    const isBad = submitted && placed[a.id] !== a.bin;
                    return (
                      <div key={a.id} className={`qmap-chip${isOk ? " correct" : ""}${isBad ? " wrong" : ""}`}
                           draggable={!submitted} onDragStart={e => handleDragStart(e, a.id)} onDragEnd={() => setDraggedId(null)}>
                        {a.label}
                      </div>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>

        <div className="lab-actions">
          {!submitted && (
            <button className="btn btn-primary" disabled={pool.length > 0} onClick={() => { setSubmitted(true); markStepDone(); }}>
              {pool.length > 0 ? `Falta clasificar ${pool.length}` : "Comprobar clasificación"}
            </button>
          )}
          {submitted && <button className="btn btn-secondary" onClick={() => { setPool(SYNTH.twelveAdjustments.map(a => a.id)); setPlaced({}); setSubmitted(false); }}>Reintentar</button>}
        </div>

        {submitted && (
          <div className={`lab-feedback ${passing ? "correct" : "partial"}`}>
            <h5>{passing ? "Mastery superado" : "Calibra tu criterio"} · {pct}% aciertos ({correctCount} / {SYNTH.twelveAdjustments.length})</h5>
            <p style={{ margin: 0 }}>
              Los ajustes <em>recurrentes</em> son parte del beneficio normal: forman parte del run-rate.
              Los <em>one-off</em> brutos se restan antes de impuestos; los <em>after-tax</em> ya vienen netos.
              Los <em>asimétricos</em> piden tratamiento distinto (ej. SBC abusivo se trata como coste real, no como non-cash).
            </p>
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["VIDEO+Q-MAP"] = QMapLab;
FZ_LABS["Q-MAP"] = QMapLab;

/* ─────────────────────────────────────────────────────────────
   LAB: GATE — veredicto multi-criterio (sí / no / ambiguo por criterio)
   ───────────────────────────────────────────────────────────── */
function GateLab({ ep, markStepDone }) {
  const criteria = [
    { id: "drivers", label: "¿Tu veredicto cita ≥ 2 drivers cuantitativos (g, ROE, riesgo)?" },
    { id: "evidence", label: "¿Has separado dato observable de hipótesis?" },
    { id: "language", label: "¿Tu memo evita lenguaje absoluto (barata, cara, fraude)?" },
    { id: "transfer", label: "¿Transfieres el aprendizaje a un sector distinto?" },
    { id: "pending", label: "¿Indicas explícitamente qué evidencia pendiente revisarías?" },
  ];
  const [picks, setPicks] = useStateLab({});
  const [submitted, setSubmitted] = useStateLab(false);
  const score = Object.values(picks).reduce((acc, v) => acc + (v === "yes" ? 1 : v === "amb" ? 0.5 : 0), 0);
  const pct = Math.round((score / criteria.length) * 100);
  const pass = pct >= 70;

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">GATE · veredicto del módulo</span>
      </div>

      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag gate" style={{ background: "var(--amber-soft)", color: "var(--amber)" }}>GATE</span>
          <h3>Auto-evaluación de tu veredicto del módulo</h3>
        </div>
        <p className="lab-prompt">
          Evalúa honestamente tu propio entregable contra los 5 criterios. Mastery ≥ 70%.
          “Ambiguo” cuenta 0.5; sé estricto contigo.
        </p>

        <div className="gate-rows">
          {criteria.map(c => (
            <div key={c.id} className="gate-row">
              <span className="crit">{c.label}</span>
              {["yes", "amb", "no"].map(v => {
                const isSel = picks[c.id] === v;
                return (
                  <button key={v}
                          className={isSel ? `sel ${v === "no" ? "no" : v === "amb" ? "amb" : ""}` : ""}
                          onClick={() => !submitted && setPicks(p => ({ ...p, [c.id]: v }))}>
                    {v === "yes" ? "Sí" : v === "amb" ? "Parcial" : "No"}
                  </button>
                );
              })}
            </div>
          ))}
        </div>

        <div className="lab-actions">
          {!submitted && (
            <button className="btn btn-primary" disabled={Object.keys(picks).length < criteria.length} onClick={() => { setSubmitted(true); markStepDone(); }}>
              Cerrar veredicto
            </button>
          )}
          {submitted && <button className="btn btn-secondary" onClick={() => { setPicks({}); setSubmitted(false); }}>Reabrir</button>}
        </div>

        {submitted && (
          <div className={`lab-feedback ${pass ? "correct" : "partial"}`}>
            <h5>{pass ? "Veredicto aprobado" : "Pendiente de remediación"} · {pct}% ({score}/{criteria.length})</h5>
            <p style={{ margin: 0 }}>
              {pass
                ? "Tu veredicto cumple los criterios mínimos. Programa una recall en 7 y 30 días."
                : "Revisa los criterios marcados como “No” o “Parcial”. La diferencia entre un memo profesional y opinión es la separación de dato/hipótesis y el lenguaje."}
            </p>
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["GATE"] = GateLab;
FZ_LABS["GATE+PEER"] = GateLab;

/* ─────────────────────────────────────────────────────────────
   LAB: DBG — error spotting (debug)
   ───────────────────────────────────────────────────────────── */
function DbgLab({ ep, markStepDone }) {
  const stmt = [
    { id: 1, line: "Revenue: 1.000 M€", err: false },
    { id: 2, line: "COGS: 600 M€", err: false },
    { id: 3, line: "SG&A: 150 M€ (incluye SBC 220 M€)", err: true, why: "SBC 220 M€ supera SG&A entero: imposible — SBC mal contabilizado o doble counting." },
    { id: 4, line: "Operating Income: 250 M€", err: false },
    { id: 5, line: "Interest expense: 20 M€", err: false },
    { id: 6, line: "Tax rate: 18%  → Tax 41,4 M€", err: false },
    { id: 7, line: "Net Income: 198,6 M€", err: false },
    { id: 8, line: "EPS reportado: 4,20 € · acciones diluidas 47 M", err: false },
    { id: 9, line: "EPS clean (Finaz): 4,20 € · sin ajuste por SBC abusivo", err: true, why: "Si SBC abusivo (>10% revenue), EPS clean debe restarlo. No puede coincidir con reportado." },
    { id: 10, line: "Operating margin: 25%", err: false },
    { id: 11, line: "ROE: 14% (NI / Equity medio)", err: false },
    { id: 12, line: "Free Cash Flow: 245 M€  (≈ NI + D&A − Capex − ΔWC)", err: false },
  ];
  const [picked, setPicked] = useStateLab(new Set());
  const [submitted, setSubmitted] = useStateLab(false);
  const toggle = (id) => {
    if (submitted) return;
    setPicked(p => { const np = new Set(p); if (np.has(id)) np.delete(id); else np.add(id); return np; });
  };
  const correctIds = stmt.filter(s => s.err).map(s => s.id);
  const right = correctIds.every(id => picked.has(id));
  const falsePos = [...picked].filter(id => !correctIds.includes(id));
  const allOk = right && falsePos.length === 0;

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">DBG · detectar incoherencias</span>
      </div>
      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag" style={{ background: "var(--red-soft)", color: "var(--red)" }}>DBG</span>
          <h3>P&amp;L sintético · marca las líneas con incoherencias</h3>
        </div>
        <p className="lab-prompt">
          Lee atentamente y selecciona las líneas que no cuadran. Pulsa “Comprobar” cuando creas haber marcado todas.
        </p>

        <div style={{ display: "flex", flexDirection: "column", gap: 4, background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 10, padding: 10 }}>
          {stmt.map(s => {
            const sel = picked.has(s.id);
            const isErr = s.err;
            const after = submitted ? (isErr && sel ? "correct" : isErr ? "missed" : sel ? "falsepos" : "ok") : null;
            const bg = after === "correct" ? "var(--green-soft)" :
                       after === "missed" ? "var(--amber-soft)" :
                       after === "falsepos" ? "var(--red-soft)" :
                       sel ? "var(--accent-soft)" : "transparent";
            const color = after === "correct" ? "var(--green)" :
                          after === "missed" ? "var(--amber)" :
                          after === "falsepos" ? "var(--red)" : "var(--ink)";
            return (
              <div key={s.id} onClick={() => toggle(s.id)}
                   style={{ padding: "10px 14px", borderRadius: 6, cursor: submitted ? "default" : "pointer",
                            background: bg, color, fontFamily: "var(--font-mono)", fontSize: 13.5,
                            display: "flex", justifyContent: "space-between", gap: 12, alignItems: "center",
                            border: sel ? "1px solid var(--accent)" : "1px solid transparent" }}>
                <span>{s.line}</span>
                {submitted && isErr && <span style={{ fontSize: 11, color: "var(--ink-quiet)", maxWidth: 260, textAlign: "right" }}>{s.why}</span>}
              </div>
            );
          })}
        </div>

        <div className="lab-actions">
          {!submitted && <button className="btn btn-primary" disabled={picked.size === 0} onClick={() => { setSubmitted(true); markStepDone(); }}>Comprobar</button>}
          {submitted && <button className="btn btn-secondary" onClick={() => { setPicked(new Set()); setSubmitted(false); }}>Reintentar</button>}
        </div>

        {submitted && (
          <div className={`lab-feedback ${allOk ? "correct" : "partial"}`}>
            <h5>{allOk ? "Diagnóstico correcto" : "Revisa señales"}</h5>
            <p style={{ margin: 0 }}>
              Encontraste {[...picked].filter(id => correctIds.includes(id)).length} / {correctIds.length} incoherencias reales.
              {falsePos.length > 0 && ` Falsos positivos: ${falsePos.length}.`}
            </p>
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["DBG"] = DbgLab;

/* ─────────────────────────────────────────────────────────────
   DEFAULT lab — generic exercise viewer for resource_types not specialized
   ───────────────────────────────────────────────────────────── */
function DefaultLab({ ep, markStepDone }) {
  const [open, setOpen] = useStateLab({});
  const ex = ep.exercise || {};
  const tasks = ex.tasks || [];
  const [done, setDone] = useStateLab(new Array(tasks.length).fill(false));
  useEffectLab(() => { if (done.every(Boolean) && tasks.length) markStepDone(); }, [done, tasks.length, markStepDone]);

  return (
    <div className="ep-stage">
      <div className="ep-stage-head">
        <h2>S3 · Lab</h2>
        <span className="ep-kicker">{ep.resource_type || "Práctica guiada"}</span>
      </div>
      <div className="lab-shell">
        <div className="lab-head">
          <span className="lab-tag">{ep.resource_type || "Lab"}</span>
          <h3>{ex.prompt || "Práctica del episodio"}</h3>
        </div>
        {ex.prompt && <p className="lab-prompt">Resuelve los pasos uno a uno. Marca cada paso cuando lo completes.</p>}

        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {tasks.map((t, i) => (
            <label key={i} style={{
              display: "grid",
              gridTemplateColumns: "auto 1fr",
              gap: 12, alignItems: "flex-start",
              padding: "12px 14px", background: "var(--bg-card)",
              border: "1px solid " + (done[i] ? "var(--green-border)" : "var(--rule)"),
              borderRadius: 10, cursor: "pointer",
              transition: "border-color var(--motion)"
            }}>
              <input type="checkbox" checked={done[i]} onChange={() => setDone(d => { const nd = d.slice(); nd[i] = !nd[i]; return nd; })}
                     style={{ marginTop: 3, width: 18, height: 18, accentColor: "var(--accent)" }}/>
              <span style={{ fontSize: 14, lineHeight: 1.55, color: done[i] ? "var(--ink-quiet)" : "var(--ink)", textDecoration: done[i] ? "line-through" : "none" }}>
                <AutoGloss text={t} triggerIds={ep.glossary_triggers}/>
              </span>
            </label>
          ))}
        </div>

        {ep.interaction_flow && ep.interaction_flow.length > 0 && (
          <div style={{ marginTop: 22 }}>
            <span className="kicker">Flujo guiado del episodio</span>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 8 }}>
              {ep.interaction_flow.map((s, i) => (
                <div key={i} style={{ padding: "10px 14px", background: "var(--bg-elev-1)", borderRadius: 8, fontSize: 13, lineHeight: 1.5 }}>
                  <div style={{ font: "500 11px var(--font-mono)", color: "var(--ink-quiet)", marginBottom: 3, textTransform: "uppercase", letterSpacing: 0.06 }}>{s.id} · {s.ui}</div>
                  <div style={{ color: "var(--ink)" }}>{s.micro || s.action}</div>
                  {s.feedback && <div style={{ color: "var(--ink-muted)", fontSize: 12, marginTop: 4, fontStyle: "italic" }}>Feedback: {s.feedback}</div>}
                </div>
              ))}
            </div>
          </div>
        )}

        {ex.feedback_bank && Object.keys(ex.feedback_bank).length > 0 && (
          <div className="tip-ribbon" style={{ marginTop: 18 }}>
            <b>Si dudas, recuerda:</b> {ex.feedback_bank.glossary_hint || ex.feedback_bank.excellent || "Conecta dato observable → hipótesis → evidencia pendiente."}
          </div>
        )}
      </div>
    </div>
  );
}
FZ_LABS["DEFAULT"] = DefaultLab;

/* Specialized aliases */
FZ_LABS["MAP"] = DefaultLab;
FZ_LABS["MAP+PROJ"] = DefaultLab;
FZ_LABS["SIM"] = CalcLab; // simulator → calculator
FZ_LABS["CASE-X+SIM"] = CaseXLab;
