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

/* ===================== i18n helpers (hash-based, team-i18n) =====================
 * window.I18N / window.I18NUtils / window.i18nHash come from the three scripts
 * loaded before this file (public/js/shared/*). Static visible text is wrapped
 * with <T> → <span data-i18n="hash">…</span> (the dynamic translator keeps it
 * translated across React re-renders). Attributes use tt(); strings with values
 * use tf() with {placeholders} (translate the whole phrase, never concatenate). */
const tt = (s) => (window.I18NUtils ? window.I18NUtils.t(s) : String(s == null ? "" : s));
const tf = (tmpl, vars) => tt(tmpl).replace(/\{(\w+)\}/g, (m, k) => (vars && vars[k] != null ? vars[k] : m));
function T({ children }) {
  const s = Array.isArray(children) ? children.join("") : String(children == null ? "" : children);
  const h = window.i18nHash ? window.i18nHash(s) : "";
  return <span data-i18n={h}>{tt(s)}</span>;
}
// Re-render the subtree when the language (and thus the registry) changes.
function useI18nVersion() {
  const [, setV] = useState(0);
  useEffect(() => {
    const cb = () => setV((v) => v + 1);
    document.addEventListener("i18n:changed", cb);
    return () => document.removeEventListener("i18n:changed", cb);
  }, []);
}
const LANGS = [
  ["en", "English"], ["zh", "中文"], ["es", "Español"], ["de", "Deutsch"], ["fr", "Français"],
  ["it", "Italiano"], ["pt", "Português"], ["ja", "日本語"], ["ko", "한국어"], ["th", "ไทย"],
  ["vi", "Tiếng Việt"], ["ru", "Русский"], ["hi", "हिन्दी"], ["ar", "العربية"], ["he", "עברית"],
  ["fa", "فارسی"], ["ur", "اردو"],
];
function LanguagePicker() {
  const [lang, setLang] = useState((window.I18N && window.I18N.lang) || "en");
  function change(e) { const l = e.target.value; setLang(l); if (window.I18N) window.I18N.setLang(l); }
  return (
    <select className="lang-select" value={lang} onChange={change} title={tt("Language")} aria-label={tt("Language")}>
      {LANGS.map(([v, label]) => <option key={v} value={v}>{label}</option>)}
    </select>
  );
}

/* ===================== palette (SVG strokes + dynamic colors) ===================== */
const C = {
  bg: "#0d1117", panel: "#161b22", panel2: "#1c232c", line: "#2b333f",
  ink: "#e6edf3", mute: "#8b97a6", faint: "#5b6675",
  teal: "#54f2b2", amber: "#ffb454", crimson: "#ff5d6c", blue: "#6cb6ff", gold: "#e3b341",
};

/* ===================== domain constants (fixed physics) ===================== */
const LED_VF = 2.0, LED_RD = 10, LED_SAFE = 20, LED_BURN = 40, R_RATING = 0.25;
const PALETTE = [
  { type: "resistor", label: "Resistor", hint: "Limits current (Ω)" },
  { type: "led", label: "LED", hint: "Lights when forward-biased" },
  { type: "wire", label: "Wire", hint: "Ideal conductor (~0 Ω)" },
];
const W_PRIMARY = 0.9, W_SECONDARY = 0.6, W_PENALTY = 0.4;

/* ===================== api helper ===================== */
async function api(path, { method = "GET", body } = {}) {
  const res = await fetch(`/api${path}`, {
    method, credentials: "same-origin",
    headers: body ? { "Content-Type": "application/json" } : {},
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
  return data;
}

/* ===================== physics (ported from the mockup) ===================== */
function simulate(V, slotA, slotB) {
  const slots = [slotA, slotB];
  const closed = slots.every(Boolean);
  const hasLED = slots.some((c) => c && c.type === "led");
  const hasResistor = slots.some((c) => c && c.type === "resistor");
  const damaged = slots.some((c) => c && c.type === "led" && c.damaged);
  const base = { closed, hasLED, hasResistor, damaged, Ima: 0, R: 0, Vf: 0, Vr: 0, Pr: 0, status: "open", short: false, brightness: 0, willBurn: false };
  if (!closed) return { ...base, status: "open" };
  if (damaged) return { ...base, status: "burnt" };
  let R = 0, Vf = 0;
  slots.forEach((c) => {
    if (!c) return;
    if (c.type === "resistor") R += c.R;
    else if (c.type === "wire") R += 0.01;
    else if (c.type === "led") { R += LED_RD; Vf += LED_VF; }
  });
  const drive = V - Vf;
  let Ima = drive <= 0 ? 0 : (drive / R) * 1000;
  const short = Ima > 1000;
  Ima = Math.min(Ima, 8000);
  const I = Ima / 1000;
  const Rres = slots.reduce((a, c) => a + (c && c.type === "resistor" ? c.R : 0), 0);
  const Vr = I * Rres, Pr = I * I * Rres;
  let status = "off", brightness = 0;
  if (!hasLED) status = Ima > 0.5 ? "flow" : "off";
  else if (Ima < 1) status = "off";
  else if (Ima > LED_SAFE) { status = "overdrive"; brightness = 1; }
  else { status = "lit"; brightness = Math.max(0.12, Math.min(1, Ima / LED_SAFE)); }
  return { ...base, Ima, R, Vf, Vr, Pr, short, status, brightness, willBurn: hasLED && Ima > LED_BURN };
}

/* ===================== declarative check evaluator ===================== */
function evalCheck(sim, c) {
  const v = sim[c.field];
  switch (c.op) {
    case "truthy": return !!v;
    case "falsy": return !v;
    case "eq": return v === c.value;
    case "gt": return v > c.value;
    case "gte": return v >= c.value;
    case "lt": return v < c.value;
    case "lte": return v <= c.value;
    case "between": return v >= c.min && v <= c.max;
    case "approx": return Math.abs(v - c.value) <= (c.tol == null ? 0 : c.tol);
    default: return false;
  }
}
const taskMet = (sim, task) => task.checks.length > 0 && task.checks.every((c) => evalCheck(sim, c));
const misconceptionFired = (sim, m) => m.when.length > 0 && m.when.every((c) => evalCheck(sim, c));

/* ===================== small UI atoms ===================== */
function H({ children }) { return <div className="h nr">{children}</div>; }
function Sub({ children }) { return <div className="sub">{children}</div>; }
function timeStr() { const d = new Date(); return d.toLocaleTimeString([], { hour12: false }); }
function barColor(v) { return v >= 0.85 ? C.teal : v >= 0.5 ? C.gold : v > 0 ? C.crimson : C.line; }
function meterColor(sim) { if (sim.status === "overdrive" || sim.status === "burnt") return C.crimson; if (sim.Ima > 0.5) return C.teal; return C.mute; }

function Meter({ label, v, unit, digits, color }) {
  return (
    <div className="meter">
      <div className="meter__label"><T>{label}</T></div>
      <div className="meter__value" style={{ color }}>{v.toFixed(digits)}<span className="meter__unit"> {unit}</span></div>
    </div>
  );
}
function Gauge({ label, value, suffix, color }) {
  return (
    <div className="gauge">
      <div className="gauge__label"><T>{label}</T></div>
      <div className="gauge__value" style={{ color }}>{value}{suffix}</div>
    </div>
  );
}
function MiniSym({ type }) {
  if (type === "resistor") return <svg width="34" height="12" className="vmid"><path d="M0 6 L6 6 L9 1 L15 11 L21 1 L27 11 L30 6 L34 6" fill="none" stroke={C.gold} strokeWidth="1.6" /></svg>;
  if (type === "led") return <svg width="34" height="14" className="vmid"><polygon points="8,2 8,12 18,7" fill="none" stroke={C.teal} strokeWidth="1.6" /><line x1="18" y1="2" x2="18" y2="12" stroke={C.teal} strokeWidth="1.6" /><line x1="0" y1="7" x2="8" y2="7" stroke={C.faint} strokeWidth="1.6" /><line x1="18" y1="7" x2="34" y2="7" stroke={C.faint} strokeWidth="1.6" /></svg>;
  return <svg width="34" height="12" className="vmid"><line x1="0" y1="6" x2="34" y2="6" stroke={C.faint} strokeWidth="1.8" /></svg>;
}
function StatusBar({ sim }) {
  const map = {
    open: { node: <T>Open circuit — no complete path. Fill both slots.</T>, c: C.faint },
    off: { node: <T>Closed, but no light. Check forward voltage / current.</T>, c: C.mute },
    flow: { node: <T>Current flows, but there's no LED in the loop.</T>, c: C.blue },
    lit: { node: tf("LED lit · {mA} mA (safe ≤ {safe} mA).", { mA: sim.Ima.toFixed(1), safe: LED_SAFE }), c: C.teal },
    overdrive: { node: tf("OVERDRIVE · {mA} mA exceeds {safe} mA — heading for burnout.", { mA: sim.Ima.toFixed(0), safe: LED_SAFE }), c: C.crimson },
    burnt: { node: <T>LED destroyed. Replace it and add current limiting.</T>, c: C.crimson },
  };
  const s = map[sim.status] || map.off;
  return (
    <div className="statusbar" style={{ borderColor: s.c, color: s.c }}>
      {sim.short ? tt("⚠ Near short-circuit — almost no resistance. ") : ""}{s.node}
    </div>
  );
}

/* ===================== schematic ===================== */
function Battery({ x, y, v }) {
  return (
    <g transform={`rotate(90 ${x} ${y})`}>
      <line x1={x - 26} y1={y} x2={x - 8} y2={y} stroke={C.faint} strokeWidth="2.5" />
      <line x1={x - 8} y1={y - 16} x2={x - 8} y2={y + 16} stroke={C.amber} strokeWidth="3" />
      <line x1={x + 2} y1={y - 8} x2={x + 2} y2={y + 8} stroke={C.amber} strokeWidth="3" />
      <line x1={x + 12} y1={y - 16} x2={x + 12} y2={y + 16} stroke={C.amber} strokeWidth="3" />
      <line x1={x + 22} y1={y - 8} x2={x + 22} y2={y + 8} stroke={C.amber} strokeWidth="3" />
      <line x1={x + 22} y1={y} x2={x + 30} y2={y} stroke={C.faint} strokeWidth="2.5" />
      <text x={x} y={y + 40} textAnchor="middle" fill={C.amber} fontSize="13" transform={`rotate(-90 ${x} ${y + 40})`}>{v}V</text>
    </g>
  );
}
function ResistorSym({ x, y, R }) {
  const zig = "M-44 0 L-30 0 L-26 -10 L-18 10 L-10 -10 L-2 10 L6 -10 L14 10 L22 -10 L30 0 L44 0";
  return (
    <g transform={`translate(${x} ${y})`}>
      <path d={zig} fill="none" stroke={C.gold} strokeWidth="2.5" />
      <text x="0" y="24" textAnchor="middle" fill={C.gold} fontSize="12">{R} Ω</text>
    </g>
  );
}
function LedSym({ x, y, sim, damaged }) {
  const lit = !damaged && sim.brightness > 0 && sim.hasLED;
  const over = sim.status === "overdrive";
  const col = damaged ? C.crimson : over ? C.crimson : C.teal;
  return (
    <g transform={`translate(${x} ${y})`}>
      {lit && <circle cx="0" cy="0" r={26 + sim.brightness * 22} fill="url(#glow)" opacity={sim.brightness} style={{ animation: over ? "flick .25s infinite" : "none" }} />}
      <line x1="-44" y1="0" x2="-12" y2="0" stroke={C.faint} strokeWidth="2.5" />
      <polygon points="-12,-12 -12,12 10,0" fill={lit ? "#ffe08a" : "#243"} stroke={col} strokeWidth="2" />
      <line x1="10" y1="-13" x2="10" y2="13" stroke={col} strokeWidth="2.5" />
      <line x1="10" y1="0" x2="44" y2="0" stroke={C.faint} strokeWidth="2.5" />
      <g stroke={lit ? C.gold : C.faint} strokeWidth="1.6">
        <line x1="2" y1="-18" x2="12" y2="-28" /><polygon points="12,-28 7,-25 11,-23" fill={lit ? C.gold : C.faint} stroke="none" />
        <line x1="10" y1="-16" x2="20" y2="-26" /><polygon points="20,-26 15,-23 19,-21" fill={lit ? C.gold : C.faint} stroke="none" />
      </g>
      {damaged && <text x="0" y="6" textAnchor="middle" fill={C.crimson} fontSize="22">✕</text>}
      <text x="0" y="30" textAnchor="middle" fill={col} fontSize="11">{damaged ? tt("burnt") : "LED"}</text>
    </g>
  );
}
function Slot({ k, x, y, horizontal, slot, sim, selected, onSelect }) {
  const w = 116, h = 64;
  return (
    <g transform={horizontal ? "" : `rotate(90 ${x} ${y})`}>
      <rect x={x - w / 2} y={y - h / 2} width={w} height={h} fill="#0a0d12" rx="6"
        stroke={selected ? C.blue : "transparent"} strokeWidth="2" strokeDasharray={slot ? "0" : "5 4"}
        style={{ cursor: "pointer" }} onClick={() => onSelect(k)} />
      {!slot && (<>
        <rect x={x - w / 2} y={y - h / 2} width={w} height={h} fill="none" rx="6" stroke={selected ? C.blue : C.faint} strokeWidth="1.5" strokeDasharray="5 4" />
        <text x={x} y={y - 4} textAnchor="middle" fill={selected ? C.blue : C.faint} fontSize="13">{tf("Slot {slot}", { slot: k })}</text>
        <text x={x} y={y + 14} textAnchor="middle" fill={C.faint} fontSize="10">{tt("empty · tap +")}</text>
      </>)}
      {slot && slot.type === "resistor" && <ResistorSym x={x} y={y} R={slot.R} />}
      {slot && slot.type === "wire" && (<>
        <line x1={x - w / 2} y1={y} x2={x + w / 2} y2={y} stroke={C.faint} strokeWidth="2.5" />
        <text x={x} y={y + 22} textAnchor="middle" fill={C.faint} fontSize="10">{tt("wire")}</text>
      </>)}
      {slot && slot.type === "led" && <LedSym x={x} y={y} sim={sim} damaged={slot.damaged} />}
    </g>
  );
}
// AI-generated, per-problem concept diagram (illustrative; sanitized server-side).
function ConceptDiagram({ svg, concept }) {
  if (!svg) return null;
  return (
    <div className="concept-diagram">
      <H><T>Concept diagram</T> <span className="h dim">· <T>AI-drawn for</T> “{concept}”</span></H>
      <div className="concept-diagram__svg" dangerouslySetInnerHTML={{ __html: svg }} />
      <div className="muted-note"><T>Illustration only — build the circuit on the interactive bench below to be assessed.</T></div>
    </div>
  );
}
function Schematic({ sim, voltage, slots, selected, onSelect }) {
  const L = 120, R = 480, T = 90, B = 300, MIDY = 195;
  const dur = sim.Ima > 0.5 && sim.status !== "burnt" ? Math.max(0.5, Math.min(6, 90 / sim.Ima)) : 0;
  const flowing = dur > 0, dots = 9;
  return (
    <svg viewBox="0 0 600 380" className="schematic">
      <defs>
        <radialGradient id="glow" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#ffe08a" stopOpacity="0.95" />
          <stop offset="100%" stopColor="#ffe08a" stopOpacity="0" />
        </radialGradient>
        <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
          <path d="M20 0H0V20" fill="none" stroke="#141a22" strokeWidth="1" />
        </pattern>
        <path id="loopPath" d={`M${L},${T} L${R},${T} L${R},${B} L${L},${B} Z`} fill="none" />
      </defs>
      <rect x="0" y="0" width="600" height="380" fill="url(#grid)" />
      <path d={`M${L},${T} L${R},${T} L${R},${B} L${L},${B} Z`} fill="none" stroke={flowing ? C.teal : C.faint} strokeWidth="2.5" opacity={flowing ? 0.9 : 0.55} />
      {flowing && (
        <g key={`flow-${Math.round(sim.Ima)}`}>
          {Array.from({ length: dots }).map((_, i) => (
            <circle key={i} r="3.4" fill={C.teal}>
              <animateMotion dur={`${dur}s`} repeatCount="indefinite" begin={`-${(i / dots) * dur}s`}>
                <mpath href="#loopPath" />
              </animateMotion>
            </circle>
          ))}
        </g>
      )}
      <Battery x={L} y={MIDY} v={voltage} />
      <Slot k="A" x={300} y={T} horizontal slot={slots.A} sim={sim} selected={selected === "A"} onSelect={onSelect} />
      <Slot k="B" x={R} y={MIDY} slot={slots.B} sim={sim} selected={selected === "B"} onSelect={onSelect} />
      <text x="300" y="350" textAnchor="middle" fill={C.faint} fontSize="11">{tt("● = conventional current (+ → −) · bottom & left rails = wire")}</text>
    </svg>
  );
}

/* ===================== CircuitLab (spec-driven) ===================== */
function CircuitLab({ tutor, role, onExit }) {
  useI18nVersion();
  const spec = tutor.spec;
  const objectives = spec.objectives;
  const tasks = spec.tasks;
  const misc = spec.misconceptions || [];
  const persist = role === "learner"; // only learners write progress

  const [voltage, setVoltage] = useState(spec.supplyDefault || 9);
  const [slots, setSlots] = useState({ A: { type: "wire" }, B: null });
  const [selected, setSelected] = useState("B");
  const [taskId, setTaskId] = useState(tasks[0] && tasks[0].id);
  const [mastery, setMastery] = useState(() => Object.fromEntries(objectives.map((o) => [o.id, 0])));
  const [evidence, setEvidence] = useState([]);
  const [showXapi, setShowXapi] = useState(false);
  const [showHints, setShowHints] = useState(false);

  const sim = useMemo(() => simulate(voltage, slots.A, slots.B), [voltage, slots]);
  const task = tasks.find((t) => t.id === taskId) || tasks[0];

  const credited = useRef(new Set());
  const miscActive = useRef({});
  const pending = useRef([]);   // unsent evidence
  const syncTimer = useRef(null);
  const completedRef = useRef(new Set());

  const scheduleSync = useCallback((nextMastery, nextCompleted) => {
    if (!persist) return;
    if (syncTimer.current) clearTimeout(syncTimer.current);
    syncTimer.current = setTimeout(async () => {
      const events = pending.current; pending.current = [];
      try {
        await api(`/tutors/${tutor.id}/events`, { method: "POST", body: { events, mastery: nextMastery, completedTasks: [...nextCompleted] } });
      } catch (e) { pending.current = events.concat(pending.current); /* retry next time */ }
    }, 700);
  }, [persist, tutor.id]);

  function pushEvent(verb, object, result, color, masterySnap) {
    const stmt = {
      actor: { name: "learner", account: { name: "demo" } },
      verb: { id: `https://w3id.org/xapi/dod/verbs/${verb}`, display: { en: verb } },
      object: { id: `https://circuitlab.edu/xapi/activities/${object.id}`, definition: { name: { en: object.name }, type: "http://adlnet.gov/expapi/activities/interaction" } },
      result, context: { extensions: { tutorId: tutor.id, taskId, voltage, slotA: slots.A && slots.A.type || null, slotB: slots.B && slots.B.type || null } },
      timestamp: new Date().toISOString(),
    };
    const ev = { id: Math.random().toString(36).slice(2), verb, text: object.name, result, color, stmt, t: timeStr() };
    setEvidence((e) => [ev, ...e].slice(0, 40));
    pending.current = [ev, ...pending.current];
    scheduleSync(masterySnap || mastery, completedRef.current);
  }

  // task-credit
  useEffect(() => {
    if (!task) return;
    if (taskMet(sim, task) && !credited.current.has(task.id)) {
      credited.current.add(task.id);
      completedRef.current.add(task.id);
      setMastery((m) => {
        const n = { ...m };
        if (n[task.primary] != null) n[task.primary] = Math.max(n[task.primary], W_PRIMARY);
        (task.secondary || []).forEach((o) => { if (n[o] != null) n[o] = Math.max(n[o], W_SECONDARY); });
        pushEvent("mastered", { id: task.id, name: `Goal met: ${task.title}` }, { completion: true, success: true, response: `${sim.Ima.toFixed(1)} mA` }, C.teal, n);
        return n;
      });
    }
  }, [sim, taskId]); // eslint-disable-line

  // misconceptions
  useEffect(() => {
    misc.forEach((m) => {
      const fired = misconceptionFired(sim, m);
      if (fired && !miscActive.current[m.id]) {
        miscActive.current[m.id] = true;
        setMastery((mm) => {
          const n = { ...mm };
          if (n[m.penalizes] != null) n[m.penalizes] = Math.min(n[m.penalizes], W_PENALTY);
          pushEvent("misconceived", { id: m.id, name: m.name }, { success: false, response: `${sim.Ima.toFixed(0)} mA` }, C.crimson, n);
          return n;
        });
      } else if (!fired && miscActive.current[m.id]) {
        miscActive.current[m.id] = false;
      }
    });
  }, [sim]); // eslint-disable-line

  // destructive overcurrent
  useEffect(() => {
    if (sim.willBurn) {
      setSlots((s) => {
        const n = { ...s };
        ["A", "B"].forEach((k) => { if (n[k] && n[k].type === "led" && !n[k].damaged) n[k] = { ...n[k], damaged: true }; });
        return n;
      });
      pushEvent("failed", { id: "burnout", name: "LED destroyed by overcurrent" }, { success: false, response: `${Math.round(sim.Ima)} mA > ${LED_BURN} mA` }, C.crimson);
    }
  }, [sim.willBurn]); // eslint-disable-line

  function place(type) {
    if (!selected) return;
    const comp = type === "resistor" ? { type, R: 470 } : { type };
    setSlots((s) => ({ ...s, [selected]: comp }));
    pushEvent("placed", { id: `slot-${selected}`, name: `Placed ${type} in slot ${selected}` }, { response: type }, C.blue);
  }
  function clearSlot(k) { setSlots((s) => ({ ...s, [k]: null })); pushEvent("removed", { id: `slot-${k}`, name: `Cleared slot ${k}` }, {}, C.faint); }
  function commitV() { pushEvent("adjusted", { id: "supply", name: `Set supply to ${voltage} V` }, { response: `${voltage} V` }, C.amber); }
  function commitR(k, val) { pushEvent("adjusted", { id: `R-${k}`, name: `Set slot ${k} resistor to ${val} Ω` }, { response: `${val} Ω` }, C.amber); }
  function resetLab() {
    credited.current = new Set(); miscActive.current = {}; completedRef.current = new Set();
    setSlots({ A: { type: "wire" }, B: null }); setVoltage(spec.supplyDefault || 9); setSelected("B");
    setMastery(Object.fromEntries(objectives.map((o) => [o.id, 0]))); setEvidence([]);
  }

  const latest = evidence[0];
  const mvals = objectives.map((o) => mastery[o.id] || 0);
  const overall = Math.round((mvals.reduce((a, b) => a + b, 0) / (objectives.length || 1)) * 100);

  return (
    <div className="lab" data-help="circuit-lab">
      {/* header */}
      <div className="lab__header">
        <div>
          <div className="lab__title nr">
            {spec.title} <span className="t-teal">·</span> <span className="brand__sub"><T>interaction-as-assessment</T></span>
          </div>
          <div className="lab__summary">{spec.summary}</div>
        </div>
        <div className="row" style={{ gap: 16 }}>
          <Gauge label="MASTERY" value={overall} suffix="%" color={C.teal} />
          <button onClick={resetLab} className="btn btn-reset"><T>Reset</T></button>
          <button onClick={onExit} className="btn btn-muted"><T>← Back</T></button>
        </div>
      </div>

      {/* task strip */}
      <div className="lab__tasks">
        {tasks.map((t) => {
          const active = t.id === taskId, done = credited.current.has(t.id);
          return (
            <button key={t.id} onClick={() => setTaskId(t.id)} className={`btn task-btn${active ? " is-active" : ""}${done ? " is-done" : ""}`}>
              {done ? "✓ " : ""}{t.id}: {t.title}
            </button>
          );
        })}
        {role !== "learner" && <span className="t-amber" style={{ fontSize: 11 }}><T>preview mode — progress not recorded</T></span>}
      </div>
      <div className="lab__goal">▸ {task && task.goal}</div>

      {/* main grid */}
      <div className="lab__grid">
        {/* LEFT: build */}
        <div className="lab__col">
          <H><T>Bench</T></H>
          <Sub><T>Supply voltage</T></Sub>
          <div className="range-row">
            <input type="range" min={0} max={12} step={0.5} value={voltage} onChange={(e) => setVoltage(parseFloat(e.target.value))} onPointerUp={commitV} className="grow" />
            <span className="range-val">{voltage.toFixed(1)} V</span>
          </div>
          <Sub><T>Target slot</T></Sub>
          <div className="row" style={{ gap: 8, marginBottom: 14 }}>
            {["A", "B"].map((k) => (
              <button key={k} onClick={() => setSelected(k)} className={`btn toggle grow${selected === k ? " is-active" : ""}`}>
                {slots[k] ? tf("Slot {slot} · {type}", { slot: k, type: slots[k].type }) : tf("Slot {slot} · empty", { slot: k })}
              </button>
            ))}
          </div>
          <Sub>{tf("Add component → slot {slot}", { slot: selected })}</Sub>
          <div className="stack" style={{ marginBottom: 14 }}>
            {PALETTE.map((p) => (
              <button key={p.type} onClick={() => place(p.type)} className="btn btn-outline btn-start palette-row">
                <span><MiniSym type={p.type} /> &nbsp;<T>{p.label}</T></span>
                <span className="muted-note"><T>{p.hint}</T></span>
              </button>
            ))}
            <button onClick={() => selected && clearSlot(selected)} className="btn btn-clear">{tf("Clear slot {slot}", { slot: selected })}</button>
          </div>
          {["A", "B"].map((k) => slots[k] && slots[k].type === "resistor" ? (
            <div key={k} style={{ marginBottom: 12 }}>
              <Sub>{tf("Slot {slot} resistance", { slot: k })}</Sub>
              <div className="row">
                <input type="range" min={10} max={2200} step={10} value={slots[k].R}
                  onChange={(e) => setSlots((s) => ({ ...s, [k]: { ...s[k], R: parseInt(e.target.value) } }))}
                  onPointerUp={(e) => commitR(k, parseInt(e.target.value))} className="grow" />
                <span className="range-val range-val--wide">{slots[k].R} Ω</span>
              </div>
            </div>
          ) : null)}
          {sim.damaged && (
            <button onClick={() => setSlots((s) => { const n = { ...s }; ["A", "B"].forEach((k) => { if (n[k] && n[k].type === "led") n[k] = { type: "led" }; }); return n; })} className="btn btn-replace"><T>⟳ Replace burnt-out LED</T></button>
          )}
          {spec.hints && spec.hints.length > 0 && (
            <div style={{ marginTop: 16 }}>
              <button onClick={() => setShowHints((h) => !h)} className="btn btn-muted btn-outline btn-block">
                {showHints ? tf("Hide hints ({n})", { n: spec.hints.length }) : tf("Show hints ({n})", { n: spec.hints.length })}
              </button>
              {showHints && <ul className="hints">{spec.hints.map((h, i) => <li key={i}>{h}</li>)}</ul>}
            </div>
          )}
        </div>

        {/* CENTER: schematic */}
        <div className="lab__col lab__col--flex">
          <ConceptDiagram svg={spec.diagram} concept={tutor.concept} />
          <H><T>Interactive bench</T> <span className="h dim"><T>· tap a slot, then add a part</T></span></H>
          <Schematic sim={sim} voltage={voltage} slots={slots} selected={selected} onSelect={setSelected} />
          <StatusBar sim={sim} />
          <div className="meter-grid">
            <Meter label="Current" v={sim.Ima} unit="mA" digits={1} color={meterColor(sim)} />
            <Meter label="Supply" v={voltage} unit="V" digits={1} color={C.amber} />
            <Meter label="V·R" v={sim.Vr} unit="V" digits={2} color={C.blue} />
            <Meter label="P·R" v={sim.Pr} unit="W" digits={3} color={sim.Pr > R_RATING ? C.crimson : C.mute} />
          </div>
        </div>

        {/* RIGHT: assessment */}
        <div className="lab__col lab__col--flex lab__col--assess">
          <H><T>Assessment</T> <span className="h dim"><T>· live</T></span></H>
          <Sub><T>Objective mastery</T></Sub>
          <div className="bars">
            {objectives.map((o) => (
              <div key={o.id} title={o.desc}>
                <div className="bar-head">
                  <span>{o.id} · {o.label}</span><span className="t-mute">{Math.round((mastery[o.id] || 0) * 100)}%</span>
                </div>
                <div className="bar-track">
                  <div className="bar-fill" style={{ width: `${(mastery[o.id] || 0) * 100}%`, background: barColor(mastery[o.id] || 0) }} />
                </div>
              </div>
            ))}
          </div>
          <div className="between">
            <Sub><T>Evidence stream</T></Sub>
            <button onClick={() => setShowXapi((x) => !x)} className="btn btn-muted btn-outline btn-tiny">{showXapi ? tt("hide xAPI") : tt("view xAPI")}</button>
          </div>
          {showXapi && latest && (
            <pre className="scroll xapi">{JSON.stringify(latest.stmt, null, 2)}</pre>
          )}
          <div className="scroll evidence">
            {evidence.length === 0 && <div className="evidence__empty"><T>Interact with the circuit — evidence appears here.</T></div>}
            {evidence.map((e) => (
              <div key={e.id} className="evidence__row">
                <span className="evidence__verb" style={{ color: e.color }}>{e.verb}</span>
                <span className="grow">{e.text}{e.result && e.result.response ? <span className="t-faint"> · {e.result.response}</span> : null}</span>
                <span className="evidence__time">{e.t}</span>
              </div>
            ))}
          </div>
          <div className="footnote">
            {persist
              ? <T>Every interaction is captured as xAPI evidence, mapped to objectives, and scored by a transparent rubric and synced to your progress record.</T>
              : <T>Every interaction is captured as xAPI evidence, mapped to objectives, and scored by a transparent rubric.</T>}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ===================== shared chrome ===================== */
const VIEW_AS = [["admin", "Admin"], ["educator", "Teacher"], ["learner", "Student"]];
const ROLE_LABEL = { admin: "Admin", educator: "Teacher", learner: "Student" };
function TopBar({ user, provider, subject, viewAs, onViewAs, onLogout, children }) {
  const roleLabel = ROLE_LABEL[user.role] || user.role;
  const [mail, setMail] = useState(null); // null | 'sending' | 'sent' | error string
  async function testEmail() {
    setMail("sending");
    try { await api("/email/test", { method: "POST" }); setMail("sent"); setTimeout(() => setMail(null), 4000); }
    catch (e) { setMail(e.message); setTimeout(() => setMail(null), 5000); }
  }
  const canEmail = provider && provider !== "dev";
  const SUBJECT_LABELS = { statistics: "Statistics", electronics: "Electronics", optics: "Optics", anova: "ANOVA", scatter: "Regression", geometry: "Geometry", gaslaws: "Gas Laws", titration: "Titration & pH", equilibrium: "Equilibrium", punnett: "Punnett Square", population: "Population Growth", hardyweinberg: "Hardy–Weinberg", kepler: "Kepler Orbits", halflife: "Radioactive Dating", seasons: "Seasons", logicgates: "Logic Gates", bigo: "Big-O Growth", binary: "Binary Numbers" };
  const subjectLabel = SUBJECT_LABELS[subject] || "Tutors";
  return (
    <div className="topbar">
      <div className="brand nr">STEM&nbsp;Bench <span className="t-teal">·</span> <span className="brand__sub"><T>{subjectLabel}</T></span>
        {subject && <a href="/" className="muted-note" style={{ marginInlineStart: 8, fontSize: 11 }} title={tt("Switch subject")}>⇄ <T>switch</T></a>}</div>
      <div className="topbar__right">
        {children}
        <LanguagePicker />
        {onViewAs && (
          <div className="seg" title={tt("As admin you can use the app as a teacher or student")}>
            {VIEW_AS.map(([v, label]) => (
              <button key={v} onClick={() => onViewAs(v)} className={`seg__btn${viewAs === v ? " is-active" : ""}`}>
                <T>{label}</T>
              </button>
            ))}
          </div>
        )}
        {canEmail && (
          <button onClick={testEmail} title={tt("Send a confirmation email to yourself via the gateway SMTP proxy")}
            className={`btn${mail === "sent" ? " btn-primary" : " btn-muted"}`}>
            {mail === "sending" ? tt("Sending…") : mail === "sent" ? tt("Email sent ✓") : mail && mail !== "sent" ? tt("Email failed") : tt("Email me a test")}
          </button>
        )}
        <span className="usermeta">{user.name} · <span className="t-blue">{tt(roleLabel)}</span>{viewAs && viewAs !== user.role ? <span className="t-amber"> → {tf("as {role}", { role: tt(ROLE_LABEL[viewAs]) })}</span> : null}{provider && provider !== "dev" ? <span className="t-faint"> · {provider}</span> : null}</span>
        <button onClick={onLogout} className="btn btn-muted"><T>Sign out</T></button>
      </div>
    </div>
  );
}
function Card({ children, className, ...rest }) {
  // `rest` forwards passthrough attrs like data-help (team-help analytics anchor) to the DOM.
  return <div className={`card${className ? " " + className : ""}`} {...rest}>{children}</div>;
}
function Pill({ children, color }) {
  return <span className="pill" style={color ? { borderColor: color, color } : undefined}>{children}</span>;
}

/* ===================== login ===================== */
const PROVIDER_LABEL = { google: "Google", microsoft: "Microsoft", github: "GitHub" };

// Email + password auth via the gateway SMTP proxy: login / register (->verify code).
function EmailAuth({ onLogin }) {
  const [mode, setMode] = useState("login"); // login | register | verify
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");
  const [code, setCode] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [msg, setMsg] = useState(null);
  async function submit() {
    setBusy(true); setErr(null); setMsg(null);
    try {
      if (mode === "login") { const { user } = await api("/auth/email/login", { method: "POST", body: { email: email.trim(), password } }); onLogin(user); return; }
      if (mode === "register") {
        const r = await api("/auth/email/register", { method: "POST", body: { email: email.trim(), password, name: name.trim() || undefined } });
        if (r.user) { onLogin(r.user); return; }   // already in SMTP → signed in automatically, no verify step
        setMsg(r.message || tt("Verification code sent — check your email.")); setMode("verify"); return;
      }
      const { user } = await api("/auth/email/verify", { method: "POST", body: { email: email.trim(), code: code.trim() } }); onLogin(user);
    } catch (e) {
      // Existing SMTP account but the password didn't authenticate → drop into sign-in.
      if (mode === "register" && /already registered/i.test(e.message || "")) setMode("login");
      setErr(e.message);
    } finally { setBusy(false); }
  }
  async function resend() { setErr(null); setMsg(null); try { const r = await api("/auth/email/resend", { method: "POST", body: { email: email.trim() } }); setMsg(r.message || tt("Code resent.")); } catch (e) { setErr(e.message); } }
  const submitLabel = busy ? "…" : mode === "login" ? tt("Sign in") : mode === "register" ? tt("Create account") : tt("Verify & enter");
  return (
    <div>
      {err && <div className="err" style={{ marginBottom: 8 }}>{err}</div>}
      {msg && <div className="ok">{msg}</div>}
      {mode !== "verify" && <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder={tt("email")} className="field" />}
      {mode === "register" && <input value={name} onChange={(e) => setName(e.target.value)} placeholder={tt("name (optional)")} className="field" />}
      {mode !== "verify" && <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder={tt("password")} onKeyDown={(e) => e.key === "Enter" && submit()} className="field" />}
      {mode === "verify" && (<>
        <div className="helptext">{tf("Enter the 6-digit code sent to {email}.", { email })}</div>
        <input value={code} onChange={(e) => setCode(e.target.value)} placeholder="123456" onKeyDown={(e) => e.key === "Enter" && submit()} className="field" />
      </>)}
      <button onClick={submit} disabled={busy} className="btn btn-primary btn-block">{submitLabel}</button>
      <div className="auth-foot">
        {mode === "login" && <a onClick={() => { setMode("register"); setErr(null); setMsg(null); }} className="link"><T>Create an account</T></a>}
        {mode === "register" && <a onClick={() => { setMode("login"); setErr(null); setMsg(null); }} className="link"><T>Have an account? Sign in</T></a>}
        {mode === "verify" && <a onClick={resend} className="link"><T>Resend code</T></a>}
        {mode === "verify" && <a onClick={() => { setMode("login"); setErr(null); setMsg(null); }} className="link--mute"><T>Back</T></a>}
      </div>
    </div>
  );
}

function Login({ config, authError, onLogin }) {
  useI18nVersion();
  const [role, setRole] = useState("educator");
  const [name, setName] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(authError || null);
  const roles = [
    { v: "admin", label: "Admin", desc: "Oversee users, tutors & usage" },
    { v: "educator", label: "Teacher", desc: "Author tutors from a concept" },
    { v: "learner", label: "Student", desc: "Learn by building circuits" },
  ];
  async function go() {
    setBusy(true); setErr(null);
    try { const { user } = await api("/auth/login", { method: "POST", body: { role, name: name.trim() || undefined } }); onLogin(user); }
    catch (e) { setErr(e.message); } finally { setBusy(false); }
  }
  const oauth = config && config.oauth;
  const emailAuth = config && config.emailAuth;
  return (
    <div className="center-screen">
      <Card className="login-card">
        <div className="flex-end"><LanguagePicker /></div>
        <div className="brand brand--lg nr">STEM&nbsp;Bench</div>
        <div className="helptext"><T>Interactive simulation &amp; tutoring across STEM. Sign in to continue.</T></div>
        {err && <div className="err-box">{err}</div>}

        {oauth && (
          <div style={{ marginBottom: dev(config) ? 18 : 0 }}>
            <Sub><T>Sign in with</T></Sub>
            <div className="stack">
              {(config.providers || []).map((p) => (
                <a key={p} href={`/api/auth/oauth/start?provider=${p}`} className="btn btn-outline provider-link">
                  {tf("Continue with {provider}", { provider: PROVIDER_LABEL[p] || p })}
                </a>
              ))}
            </div>
            <div className="muted-note" style={{ marginTop: 8 }}><T>Your role is assigned automatically from your email (admin / teacher / student).</T></div>
          </div>
        )}

        {emailAuth && (
          <div style={{ marginTop: oauth ? 14 : 0 }}>
            {oauth && <div className="divider"><T>or use email & password</T></div>}
            <EmailAuth onLogin={onLogin} />
          </div>
        )}

        {dev(config) && (<>
          {(oauth || emailAuth) && <div className="divider"><T>or dev sign-in</T></div>}
          <Sub><T>Role</T></Sub>
          <div className="stack" style={{ marginBottom: 16 }}>
            {roles.map((r) => (
              <button key={r.v} onClick={() => setRole(r.v)} className={`btn role-btn${role === r.v ? " is-active" : ""}`}>
                <span className="btn-bold"><T>{r.label}</T></span><span style={{ fontSize: 11, color: role === r.v ? C.bg : C.faint }}><T>{r.desc}</T></span>
              </button>
            ))}
          </div>
          <Sub><T>Display name (optional)</T></Sub>
          <input value={name} onChange={(e) => setName(e.target.value)} placeholder={tt("e.g. Ms. Rivera")} className="field" />
          <button onClick={go} disabled={busy} className="btn btn-primary btn-block">{busy ? tt("Signing in…") : tt("Enter (dev)")}</button>
        </>)}
      </Card>
    </div>
  );
}
function dev(config) { return config && config.devLogin; }

/* ===================== landing ===================== */
// Public marketing/intro page for logged-out visitors. Frames STEM Bench as a pilot,
// explains the interaction-as-assessment "bench" idea, showcases the live benches, and
// reveals the existing <Login> card on "Sign in" (OAuth / email / dev — all reused).
function BenchShowcase() {
  // Mirrors the SUBJECTS catalog (defined below; resolved at render time).
  const items = (typeof SUBJECTS !== "undefined" ? SUBJECTS : []);
  return (
    <div className="bench-grid">
      {items.map((s) => (
        <div key={s.id} className="bench-card" style={{ borderTop: `3px solid ${s.color}` }}>
          <div className="bench-card__name" style={{ color: s.color }}><T>{s.label}</T></div>
          <div className="bench-card__desc"><T>{s.desc}</T></div>
        </div>
      ))}
      <div className="bench-card bench-card--soon">
        <div className="bench-card__name" style={{ color: C.faint }}>＋ <T>More benches</T></div>
        <div className="bench-card__desc"><T>New STEM benches are being built — optics, mechanics, chemistry, CS and more.</T></div>
      </div>
    </div>
  );
}

function Landing({ config, authError, onLogin }) {
  useI18nVersion();
  const [signin, setSignin] = useState(!!authError); // OAuth errors return here → open sign-in
  if (signin) {
    return (
      <div>
        <div className="landing-back"><a onClick={() => setSignin(false)} className="link">← <T>Back to home</T></a></div>
        <Login config={config} authError={authError} onLogin={onLogin} />
      </div>
    );
  }
  const features = [
    { t: "Manipulate a real figure", d: "Drive an interactive simulation — a circuit, a lens, a scatterplot — not a multiple-choice quiz." },
    { t: "Graded as you go", d: "Every action is scored live against learning objectives. Interaction is the assessment." },
    { t: "Authored by AI, over fixed physics", d: "Educators type a concept; the tutor — objectives, tasks, misconception traps — is generated over a bench that stays honest and gradeable." },
  ];
  return (
    <div className="landing">
      <header className="landing-nav">
        <div className="landing-brand nr">STEM&nbsp;Bench <span className="pilot-badge"><T>PILOT</T></span></div>
        <div className="row" style={{ gap: 12, alignItems: "center" }}>
          <LanguagePicker />
          <button onClick={() => setSignin(true)} className="btn btn-primary"><T>Sign in</T></button>
        </div>
      </header>

      <section className="landing-hero">
        <div className="pilot-tag"><T>Pilot project · actively expanding</T></div>
        <h1 className="landing-title nr">STEM Bench</h1>
        <div className="landing-tagline"><T>Interactive Simulation &amp; Tutoring</T></div>
        <p className="landing-lede">
          <T>A learning platform built on “benches” — fixed, manipulable simulations where students demonstrate understanding by doing, and every interaction is graded in real time. No quizzes.</T>
        </p>
        <div className="row" style={{ gap: 12, justifyContent: "center", flexWrap: "wrap" }}>
          <button onClick={() => setSignin(true)} className="btn btn-primary btn-lg"><T>Sign in to start</T></button>
          <a href="#benches" className="btn btn-outline btn-lg"><T>Explore the benches</T></a>
        </div>
      </section>

      <section className="landing-section">
        <div className="feature-grid">
          {features.map((f) => (
            <div key={f.t} className="feature-card">
              <div className="feature-card__title"><T>{f.t}</T></div>
              <div className="feature-card__desc"><T>{f.d}</T></div>
            </div>
          ))}
        </div>
      </section>

      <section id="benches" className="landing-section">
        <H><T>Benches in this pilot</T></H>
        <div className="helptext"><T>Each bench is a self-contained interactive lab for a STEM domain. This is a growing pilot — more benches are on the way.</T></div>
        <BenchShowcase />
      </section>

      <footer className="landing-footer">
        <span><T>STEM Bench — a pilot in interaction-as-assessment learning.</T></span>
        <button onClick={() => setSignin(true)} className="link"><T>Sign in</T></button>
      </footer>
    </div>
  );
}

/* ===================== teacher ===================== */
function TutorRow({ t, children }) {
  const statusColor = t.status === "published" ? C.teal : C.amber;
  return (
    <Card className="card-mb-sm">
      <div className="tutor-row">
        <div className="tutor-row__main">
          <div className="tutor-row__title">
            <span className="tutor-name nr">{t.title || t.concept}</span>
            <Pill color={statusColor}>{tt(t.status)}</Pill>
            <Pill color={t.generator === "llm" ? C.blue : C.faint}>{t.generator === "llm" ? "LLM" : tt("template")}</Pill>
          </div>
          <div className="helptext" style={{ marginBottom: 0, marginTop: 4 }}>{t.summary}</div>
          <div className="muted-note" style={{ marginTop: 4 }}>{tf("concept: {concept} · {owner}", { concept: t.concept, owner: t.ownerName })}</div>
        </div>
        <div className="tutor-row__actions">{children}</div>
      </div>
    </Card>
  );
}

/* ===================== concept catalog (authoring aid) ===================== */
const FIT_META = {
  core: { color: "#54f2b2", label: "core" },
  related: { color: "#6cb6ff", label: "related" },
  stretch: { color: "#ffb454", label: "stretch" },
};
function FitBadge({ fit }) {
  const m = FIT_META[fit] || FIT_META.related;
  return <span className="fit" title={fit} style={{ color: m.color }}>{m.label}</span>;
}
function ConceptChip({ c, onPick }) {
  return (
    <button onClick={() => onPick(c)} title={c.why || tf('Use "{label}" as the concept', { label: c.label })} className="chip">
      <FitBadge fit={c.fit} />
      <span>{c.label}</span>
    </button>
  );
}
function ConceptCatalog({ onPick, onClose, subject = "electronics" }) {
  const [cat, setCat] = useState(null);          // { categories, legend }
  const [open, setOpen] = useState(null);        // open category id
  const [extra, setExtra] = useState({});        // { [catId]: { concepts, source } }
  const [busy, setBusy] = useState(null);        // catId being suggested
  const [err, setErr] = useState(null);

  useEffect(() => { api(`/concepts?subject=${subject}`).then(setCat).catch((e) => setErr(e.message)); }, [subject]);
  async function suggest(category) {
    setBusy(category.id); setErr(null);
    try { const out = await api("/concepts/suggest", { method: "POST", body: { categoryId: category.id, topic: category.name, subject } }); setExtra((p) => ({ ...p, [category.id]: out })); }
    catch (e) { setErr(e.message); } finally { setBusy(null); }
  }

  return (
    <div onClick={(e) => e.stopPropagation()}>
      <div className="between" style={{ alignItems: "center" }}>
        <H><T>Browse the concept catalog</T></H>
        <button onClick={onClose} className="btn btn-muted"><T>Close</T></button>
      </div>
      <div className="helptext"><T>Not sure what to call it? Pick a category, choose a concept (it fills the box and closes this), or let AI suggest more. Tags show how well the fixed series-circuit lab can teach each one.</T></div>
      {!cat && !err && <div className="muted-note" style={{ fontSize: 13 }}><T>Loading…</T></div>}
      {err && <div className="err" style={{ marginBottom: 8 }}>{err}</div>}
      {cat && (<>
      <div className="legend">
        {Object.entries(cat.legend).map(([k, v]) => (
          <span key={k} className="legend__item"><FitBadge fit={k} /> {v}</span>
        ))}
      </div>
      {cat.categories.map((category) => {
        const isOpen = open === category.id;
        const ai = extra[category.id];
        return (
          <div key={category.id} className="cat">
            <button onClick={() => setOpen(isOpen ? null : category.id)} className="cat__head">
              <span><span className="cat__caret">{isOpen ? "▾" : "▸"}</span><span className="cat__name nr">{category.name}</span><span className="cat__count">· {category.concepts.length}</span></span>
            </button>
            {isOpen && (
              <div className="cat__body">
                <div className="cat__blurb">{category.blurb}</div>
                <div className="chip-grid">
                  {category.concepts.map((c) => <ConceptChip key={c.label} c={c} onPick={onPick} />)}
                </div>
                <div className="cat__ai">
                  <button onClick={() => suggest(category)} disabled={busy === category.id} className="btn btn-ai">
                    {busy === category.id ? tt("Asking AI…") : `✨ ${tt("AI suggest more")}`}
                  </button>
                  {ai && <span className="muted-note">{ai.source === "llm" ? tt("AI-suggested") : tt("from catalog")}</span>}
                </div>
                {ai && (
                  <div className="chip-grid" style={{ marginTop: 9 }}>
                    {ai.concepts.map((c, i) => <ConceptChip key={`ai-${i}-${c.label}`} c={c} onPick={onPick} />)}
                  </div>
                )}
              </div>
            )}
          </div>
        );
      })}
      </>)}
    </div>
  );
}

// Informative wait while a tutor is generated (spec via the gateway, then the gpt-4o
// SVG diagram). Steps mirror the real server pipeline; the last one holds until done.
const GEN_STEPS = [
  "Designing objectives and a task progression…",
  "Choosing component values and misconception traps…",
  "Rating how well the fixed lab can teach it…",
  "Drawing the circuit schematic with gpt-4o… (the slow step)",
  "Finalizing and saving the tutor…",
];
function GenProgress({ concept, level }) {
  const [step, setStep] = useState(0);
  const [elapsed, setElapsed] = useState(0);
  useEffect(() => {
    const t = setInterval(() => setElapsed((e) => e + 1), 1000);
    const s = setInterval(() => setStep((i) => Math.min(i + 1, GEN_STEPS.length - 1)), 2600);
    return () => { clearInterval(t); clearInterval(s); };
  }, []);
  const pct = Math.round(((step + 1) / GEN_STEPS.length) * 100);
  return (
    <div className="gen">
      <div className="gen__head">
        <span className="spinner" aria-hidden="true" />
        <span className="gen__title">{tf("Generating “{concept}” · {level}", { concept, level: tt(level) })}</span>
        <span className="gen__time">{tf("{s}s", { s: elapsed })}</span>
      </div>
      <div className="gen__bar"><div className="gen__bar-fill" style={{ width: `${pct}%` }} /></div>
      <div className="gen__steps">
        {GEN_STEPS.map((s, i) => (
          <div key={i} className={`gen__step${i < step ? " is-done" : i === step ? " is-active" : ""}`}>
            <span className="gen__dot">{i < step ? "✓" : i === step ? "•" : "○"}</span> {tt(s)}
          </div>
        ))}
      </div>
      <div className="muted-note" style={{ marginBlockStart: 6 }}><T>This usually takes 5–15 seconds. The diagram is drawn by a larger model, so it’s the slowest part.</T></div>
    </div>
  );
}

function TeacherDashboard({ user, subject = "electronics" }) {
  useI18nVersion();
  const [concept, setConcept] = useState("");
  const [level, setLevel] = useState("beginner");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [tutors, setTutors] = useState([]);
  const [preview, setPreview] = useState(null);
  const [roster, setRoster] = useState(null); // {tutor, rows}
  const [showCatalog, setShowCatalog] = useState(false);

  const load = useCallback(async () => { const { tutors } = await api(`/tutors?subject=${subject}`); setTutors(tutors); }, [subject]);
  useEffect(() => { load(); }, [load]);

  async function create() {
    if (!concept.trim()) return;
    setBusy(true); setErr(null);
    try { await api("/tutors", { method: "POST", body: { concept: concept.trim(), level, subject } }); setConcept(""); await load(); }
    catch (e) { setErr(e.message); } finally { setBusy(false); }
  }
  function pickConcept(c) {
    setConcept(c.label);
    if (c.suggestedLevel) setLevel(c.suggestedLevel);
    setShowCatalog(false);
  }
  async function act(id, path, body) { await api(`/tutors/${id}${path}`, { method: "POST", body }); await load(); }
  async function openPreview(id) { const { tutor } = await api(`/tutors/${id}`); setPreview(tutor); }
  async function openRoster(t) { const { roster } = await api(`/tutors/${t.id}/progress`); setRoster({ tutor: t, rows: roster }); }

  if (preview) return <TutorPlayer base={preview} role="educator" onExit={() => setPreview(null)} />;

  return (
    <div className="page">
      <Card className="card-mb" data-help="author-tutor">
        <H><T>Author a tutor</T></H>
        <div className="helptext">{{
          statistics: <T>Enter any statistics concept or skill. The LLM picks the right interactive figure (distribution, sampling, or hypothesis test) and generates objectives, graded tasks, and misconception traps over it.</T>,
          optics: <T>Enter any geometric-optics concept. The LLM generates a hands-on tutor — objectives, graded tasks, and misconception traps — over the converging-lens optical bench.</T>,
          anova: <T>Enter any concept about comparing several group means. The LLM generates a hands-on tutor — objectives, graded tasks, and misconception traps — over the one-way ANOVA bench.</T>,
          scatter: <T>Enter any correlation or regression concept. The LLM generates a hands-on tutor — objectives, graded tasks, and misconception traps — over the scatter / regression bench.</T>,
          geometry: <T>Enter any plane-geometry concept. The LLM picks the right figure (semicircle/inscribed angle, power of a point, or angle bisector) and generates objectives, graded tasks, and misconception traps over it.</T>,
        }[subject] || <T>Enter any electronics concept or skill. The LLM generates a hands-on tutor — objectives, a graded task progression, and misconception traps — over the series-circuit lab.</T>}</div>
        <Sub><T>Concept / skill</T></Sub>
        <div className="row" style={{ gap: 8, alignItems: "flex-start" }}>
          <input value={concept} onChange={(e) => setConcept(e.target.value)} placeholder={{ statistics: tt("e.g. Why the sampling distribution narrows as n grows"), optics: tt("e.g. When a converging lens forms a virtual image"), anova: tt("e.g. Why within-group spread hides a real difference"), scatter: tt("e.g. How one outlier swings the regression line"), geometry: tt("e.g. Why the angle in a semicircle is always a right angle") }[subject] || tt("e.g. Choosing a series resistor for an LED")}
            onKeyDown={(e) => e.key === "Enter" && create()} className="field" style={{ flex: 1, marginBottom: 0 }} />
          <button type="button" onClick={() => setShowCatalog(true)} className="btn btn-outline" title={tt("Browse the concept catalog")}>📚 <T>Browse catalog</T></button>
        </div>
        <div className="row" style={{ gap: 12, marginBlockStart: 12 }}>
          <select value={level} onChange={(e) => setLevel(e.target.value)} className="select">
            <option value="beginner">{tt("Beginner")}</option><option value="intermediate">{tt("Intermediate")}</option><option value="advanced">{tt("Advanced")}</option>
          </select>
          <button onClick={create} disabled={busy} className="btn btn-primary">{busy ? tt("Generating…") : tt("Generate tutor")}</button>
          {err && <span className="err">{err}</span>}
        </div>
        {busy && <GenProgress concept={concept} level={level} />}
      </Card>

      {showCatalog && (
        <div className="modal-overlay" onClick={() => setShowCatalog(false)}>
          <Card className="modal modal--wide">
            <ConceptCatalog subject={subject} onPick={pickConcept} onClose={() => setShowCatalog(false)} />
          </Card>
        </div>
      )}

      <H><T>Your tutors</T> <span className="dim">· {tutors.length}</span></H>
      {tutors.length === 0 && <div className="muted-note" style={{ fontSize: 13 }}><T>No tutors yet — author one above.</T></div>}
      {tutors.map((t) => (
        <TutorRow key={t.id} t={t}>
          <button onClick={() => openPreview(t.id)} className="btn btn-secondary"><T>Preview</T></button>
          <button onClick={() => openRoster(t)} className="btn btn-secondary"><T>Roster</T></button>
          {t.status !== "published"
            ? <button onClick={() => act(t.id, "/publish")} className="btn btn-primary"><T>Publish</T></button>
            : <button onClick={() => act(t.id, "/publish", { status: "draft" })} className="btn btn-warn"><T>Unpublish</T></button>}
          <button onClick={() => act(t.id, "/regenerate")} className="btn btn-muted"><T>Regenerate</T></button>
          <button onClick={() => api(`/tutors/${t.id}`, { method: "DELETE" }).then(load)} className="btn btn-danger"><T>Delete</T></button>
        </TutorRow>
      ))}

      {roster && (
        <div className="modal-overlay" onClick={() => setRoster(null)}>
          <Card className="modal">
            <div onClick={(e) => e.stopPropagation()}>
              <div className="between" style={{ alignItems: "center" }}>
                <H><T>Roster</T> · {roster.tutor.title || roster.tutor.concept}</H>
                <button onClick={() => setRoster(null)} className="btn btn-muted"><T>Close</T></button>
              </div>
              {roster.rows.length === 0 && <div className="muted-note" style={{ fontSize: 13 }}><T>No student activity yet.</T></div>}
              {roster.rows.map((r) => (
                <div key={r.userId} className="roster-row">
                  <div><div style={{ fontSize: 13 }}>{r.userName}</div><div className="muted-note">{tf("{n} tasks · {m} events", { n: r.completedTasks.length, m: r.events })}</div></div>
                  <Gauge label="MASTERY" value={r.overall} suffix="%" color={barColor(r.overall / 100)} />
                </div>
              ))}
            </div>
          </Card>
        </div>
      )}
    </div>
  );
}

/* ===================== student ===================== */
// Wraps the lab with a difficulty switcher. The teacher's authored level is the default (•);
// other levels are generated on demand from the same concept (GET /tutors/:id/play?level=).
const DIFFICULTY = [["beginner", "Beginner"], ["intermediate", "Intermediate"], ["advanced", "Advanced"]];
function TutorPlayer({ base, role, onExit }) {
  useI18nVersion();
  const [level, setLevel] = useState(base.level || "beginner");
  const [spec, setSpec] = useState(base.spec);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState(null);
  const baseLevel = base.level || "beginner";
  const [difficulty, setDifficulty] = useState(0);   // 0 = teacher's base problem
  const [problemId, setProblemId] = useState(null);  // non-null = a generated library problem
  const [library, setLibrary] = useState([]);
  const [showLib, setShowLib] = useState(false);
  const LVL_NUM = { beginner: 1, intermediate: 2, advanced: 3 };
  async function refreshLibrary() { try { const r = await api(`/tutors/${base.id}/problems`); setLibrary(r.problems || []); } catch (e) { /* ignore */ } }
  useEffect(() => { refreshLibrary(); }, []); // eslint-disable-line
  async function pick(lv) {
    if (lv === level || loading) return;
    setLoading(true); setErr(null);
    try { const r = await api(`/tutors/${base.id}/play?level=${lv}`); setSpec(r.spec); setLevel(lv); setDifficulty(0); setProblemId(null); }
    catch (e) { setErr(e.message); } finally { setLoading(false); }
  }
  // Generate a fresh problem one tier easier (delta −1) or harder (delta +1) than the one open.
  async function generate(delta) {
    if (loading) return;
    setLoading(true); setErr(null);
    const cur = difficulty || LVL_NUM[level] || 2;
    const next = Math.max(1, Math.min(8, cur + delta));
    try {
      const r = await api(`/tutors/${base.id}/problems`, { method: "POST", body: { difficulty: next } });
      setSpec(r.problem.spec); setDifficulty(r.problem.difficulty); setProblemId(r.problem.id);
      setLibrary((l) => [...l.filter((p) => p.id !== r.problem.id), r.problem]);
    } catch (e) { setErr(e.message); } finally { setLoading(false); }
  }
  function loadProblem(p) { setSpec(p.spec); setDifficulty(p.difficulty); setProblemId(p.id); setShowLib(false); }
  function openBase() { setSpec(base.spec); setLevel(baseLevel); setDifficulty(0); setProblemId(null); setShowLib(false); }
  return (
    <div>
      <div className="difficulty-bar">
        <span className="sub" style={{ margin: 0 }}><T>Difficulty</T></span>
        <div className="seg">
          {DIFFICULTY.map(([v, label]) => (
            <button key={v} onClick={() => pick(v)} disabled={loading} className={`seg__btn${level === v && !problemId ? " is-active" : ""}`}>
              <T>{label}</T>{v === baseLevel ? " •" : ""}
            </button>
          ))}
        </div>
        <button onClick={() => generate(-1)} disabled={loading} className="btn btn-outline btn-tiny" title={tt("Generate a fresh, easier problem for this concept")}>🧊 <T>Create an easier problem</T></button>
        <button onClick={() => generate(1)} disabled={loading} className="btn btn-outline btn-tiny" title={tt("Generate a fresh, harder problem for this concept")}>🔥 <T>Create a harder problem</T></button>
        {difficulty > 0 && <span className="muted-note" title={tt("difficulty tier")}><span className="t-amber">{"★".repeat(Math.min(5, difficulty))}</span> <T>tier</T> {difficulty}</span>}
        <div style={{ position: "relative" }}>
          <button onClick={() => { setShowLib((s) => !s); if (!showLib) refreshLibrary(); }} disabled={loading} className="btn btn-outline btn-tiny">📚 <T>Versions</T> ({library.length + 1})</button>
          {showLib && (
            <div className="card" style={{ position: "absolute", zIndex: 80, top: "112%", insetInlineStart: 0, width: 340, maxHeight: 340, overflowY: "auto", padding: 8 }}>
              <div className="muted-note" style={{ padding: "2px 6px 6px" }}><T>All versions of this concept, by difficulty:</T></div>
              <button onClick={openBase} className={`btn btn-start btn-block${!problemId ? " is-active" : ""}`} style={{ textAlign: "start", marginBottom: 4 }}>
                <span><span className="t-teal">●</span> <span style={{ fontSize: 12 }}>{base.spec.title}</span></span>
                <span className="muted-note" style={{ display: "block", fontSize: 10 }}><T>teacher default</T> · {baseLevel}</span>
              </button>
              {library.slice().sort((a, b) => a.difficulty - b.difficulty).map((p) => (
                <button key={p.id} onClick={() => loadProblem(p)} className={`btn btn-start btn-block${problemId === p.id ? " is-active" : ""}`} style={{ textAlign: "start", marginBottom: 4 }}>
                  <span><span className="t-amber">{"★".repeat(Math.min(5, p.difficulty))}{p.difficulty > 5 ? `+${p.difficulty - 5}` : ""}</span> <span style={{ fontSize: 12 }}>{p.title}</span></span>
                  <span className="muted-note" style={{ display: "block", fontSize: 10 }}><T>tier</T> {p.difficulty}{p.byName ? ` · by ${p.byName}` : ""}</span>
                </button>
              ))}
            </div>
          )}
        </div>
        {loading && <span className="muted-note"><T>generating…</T></span>}
        {err && <span className="t-crimson" style={{ fontSize: 11 }}>{err}</span>}
        <span className="muted-note">• <T>teacher default</T></span>
      </div>
      {(() => {
        const t = { ...base, spec, level, difficulty, problemId };
        if (spec.fit === "stretch") return <StudyView key={problemId || level} tutor={t} onExit={onExit} />;
        // Frontend bench registry: each lab file registers window.<Bench>Tutor; default = CircuitLab.
        const BENCH_LABS = { electronics: window.ElectronicsTutor, statistics: window.StatTutor, optics: window.OpticsTutor, anova: window.AnovaTutor, scatter: window.ScatterTutor, geometry: window.GeometryTutor, gaslaws: window.GasLawsTutor, titration: window.TitrationTutor, equilibrium: window.EquilibriumTutor, punnett: window.PunnettTutor, population: window.PopulationTutor, hardyweinberg: window.HardyWeinbergTutor, kepler: window.KeplerTutor, halflife: window.HalfLifeTutor, seasons: window.SeasonsTutor, logicgates: window.LogicGatesTutor, bigo: window.BigOTutor, binary: window.BinaryTutor };
        const Lab = BENCH_LABS[base.subject] || window.ElectronicsTutor || CircuitLab;
        return <Lab key={problemId || level} tutor={t} role={role} onExit={onExit} />;
      })()}
    </div>
  );
}

// Non-graded view for concepts the fixed lab cannot simulate (fit === "stretch"):
// the AI concept diagram + objectives + key ideas, clearly marked as conceptual.
function StudyView({ tutor, onExit }) {
  useI18nVersion();
  const spec = tutor.spec;
  return (
    <div className="lab">
      <div className="lab__header">
        <div>
          <div className="lab__title nr">{spec.title} <span className="t-amber" style={{ fontSize: 13 }}>· <T>conceptual</T></span></div>
          <div className="lab__summary">{spec.summary}</div>
        </div>
        <div className="row" style={{ gap: 16 }}>
          <button onClick={onExit} className="btn btn-muted"><T>← Back</T></button>
        </div>
      </div>
      <div className="lab__goal">{{
        statistics: <T>This concept can’t be demonstrated on the statistics figures, so it’s a guided explanation — not graded.</T>,
        optics: <T>This concept can’t be demonstrated on the converging-lens bench, so it’s a guided explanation — not graded.</T>,
        anova: <T>This concept can’t be demonstrated on the ANOVA bench, so it’s a guided explanation — not graded.</T>,
        scatter: <T>This concept can’t be demonstrated on the scatter / regression bench, so it’s a guided explanation — not graded.</T>,
      }[tutor.subject] || <T>This concept can’t be built on the series-circuit bench, so it’s a guided explanation — not lab-graded.</T>}</div>
      <div className="study">
        <ConceptDiagram svg={spec.diagram} concept={tutor.concept} />
        <H><T>What you’ll learn</T></H>
        <ul className="study__list">{spec.objectives.map((o) => <li key={o.id}><b>{o.label}</b> — {o.desc}</li>)}</ul>
        {spec.hints && spec.hints.length > 0 && (<>
          <H><T>Key ideas</T></H>
          <ul className="study__list">{spec.hints.map((h, i) => <li key={i}>{h}</li>)}</ul>
        </>)}
      </div>
    </div>
  );
}

function StudentDashboard({ user, subject = "electronics" }) {
  useI18nVersion();
  const [tutors, setTutors] = useState([]);
  const [active, setActive] = useState(null);
  // Server already scopes learners to published; filtering also gives admins a true student view.
  useEffect(() => { api(`/tutors?subject=${subject}`).then(({ tutors }) => setTutors(tutors.filter((t) => t.status === "published"))); }, [subject]);
  async function open(id) { const { tutor } = await api(`/tutors/${id}`); setActive(tutor); }
  if (active) return <TutorPlayer base={active} role="learner" onExit={() => setActive(null)} />;
  return (
    <div className="page">
      <H><T>Available tutors</T> <span className="dim">· {tutors.length}</span></H>
      {tutors.length === 0 && <div className="muted-note" style={{ fontSize: 13 }}><T>No published tutors yet. Ask your teacher to publish one.</T></div>}
      <div className="tutor-grid">
        {tutors.map((t) => (
          <Card key={t.id}>
            <div className="row" style={{ gap: 8, alignItems: "center", marginBottom: 4 }}>
              <span className="tutor-name nr">{t.title || t.concept}</span>
              <Pill color={t.gradeable ? C.teal : C.amber}>{t.gradeable ? <T>Interactive</T> : <T>Conceptual</T>}</Pill>
            </div>
            <div className="helptext" style={{ minHeight: 38, marginBottom: 0 }}>{t.summary}</div>
            <div className="muted-note" style={{ margin: "8px 0" }}>{tf("by {owner} · {level}", { owner: t.ownerName, level: t.level })}</div>
            <button onClick={() => open(t.id)} className="btn btn-primary btn-block">{t.gradeable ? <T>Start learning</T> : <T>Study concept</T>}</button>
          </Card>
        ))}
      </div>
    </div>
  );
}

/* ===================== admin ===================== */
// Admin-only meta-authoring: design a whole BENCH (the gradeable microworld tutors are
// built on) with the strongest model. Returns a design + code scaffold for review — the
// generated bench is NEVER hot-loaded; a developer reviews the scaffold and commits it.
function BenchWizard() {
  useI18nVersion();
  const [brief, setBrief] = useState("");
  const [field, setField] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [draft, setDraft] = useState(null);
  const [tab, setTab] = useState("server");
  async function generate() {
    setBusy(true); setErr(null); setDraft(null);
    try { setDraft(await api("/admin/benches/design", { method: "POST", body: { brief, field } })); }
    catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  }
  function download(name, text) {
    const a = document.createElement("a");
    a.href = URL.createObjectURL(new Blob([text], { type: "text/plain" }));
    a.download = name; a.click(); URL.revokeObjectURL(a.href);
  }
  const d = draft && draft.design;
  const LIT = d ? [["small state · broad coverage", d.litmus.smallStateBroadCoverage], ["observable is the concept", d.litmus.observableIsConcept], ["causal transparency", d.litmus.causalTransparency], ["misconception surfaces", d.litmus.misconceptionSurfaces], ["generative", d.litmus.generative]] : [];
  return (
    <Card className="card-mb">
      <Sub><T>Bench Wizard</T> <span className="dim">· <T>design a new gradeable bench with the strongest model</T></span></Sub>
      <div className="helptext"><T>Describe a STEM concept or domain. The wizard designs the fixed microworld, its state fields, tasks and catalog, plus a code scaffold to review. The generated bench is a reviewable draft — a developer commits it; it is never auto-deployed.</T></div>
      <textarea className="field" rows={3} value={brief} placeholder="e.g. Thin-lens optics: object/focal distance → image distance & magnification" onChange={(e) => setBrief(e.target.value)} />
      <div className="row" style={{ gap: 8 }}>
        <input className="field" style={{ flex: 1 }} value={field} placeholder="STEM field (optional), e.g. physics.optics" onChange={(e) => setField(e.target.value)} />
        <button className="btn btn-primary" disabled={busy || !brief.trim()} onClick={generate}>{busy ? <T>Designing…</T> : <T>Design bench</T>}</button>
      </div>
      {err && <div className="t-amber" style={{ marginTop: 8 }}>⚠ {err}</div>}
      {d && (
        <div style={{ marginTop: 14 }}>
          <div className="kv"><b>{d.label}</b> <Pill color={C.blue}>{d.field}</Pill> <span className="t-faint">· {d.id}</span> <Pill color={draft.model === "gateway-default" ? C.amber : C.teal}>{draft.model}</Pill></div>
          <div className="lab__summary" style={{ marginTop: 4 }}>{d.microworld}</div>
          <div className="row" style={{ gap: 6, flexWrap: "wrap", marginTop: 8 }}>
            {LIT.map(([k, v]) => <Pill key={k} color={v ? C.teal : C.crimson}>{v ? "✓" : "✗"} {k}</Pill>)}
          </div>
          <H><T>State fields</T> <span className="dim">· {d.fields.length}</span></H>
          <ul className="study__list">{d.fields.map((f) => <li key={f.name}><code>{f.name}</code> <span className="t-faint">({f.type}{f.type === "number" && (f.min != null || f.max != null) ? ` ${f.min ?? "?"}..${f.max ?? "?"}` : ""})</span> — {f.desc}</li>)}</ul>
          <H><T>Sample tasks</T></H>
          <ul className="study__list">{d.tasks.map((t) => <li key={t.id}><b>{t.title}</b> — {t.goal}</li>)}</ul>
          <H><T>Scaffold</T> <span className="dim">· <T>review before committing</T></span></H>
          <div className="row" style={{ gap: 8 }}>
            <button className={`btn ${tab === "server" ? "btn-secondary" : "btn-muted"}`} onClick={() => setTab("server")}>server/benches/{d.id}.js</button>
            <button className={`btn ${tab === "client" ? "btn-secondary" : "btn-muted"}`} onClick={() => setTab("client")}>{d.id} component</button>
            <button className="btn btn-muted" onClick={() => download(tab === "server" ? `${d.id}.js` : `${d.id}-lab.jsx`, tab === "server" ? draft.scaffold.serverModule : draft.scaffold.componentStub)}><T>Download</T></button>
          </div>
          <pre style={{ maxHeight: 320, overflow: "auto", marginTop: 8, background: "var(--bg)", border: "1px solid var(--line)", borderRadius: 6, padding: 12, fontSize: 12, fontFamily: "monospace", whiteSpace: "pre", color: "var(--ink)" }}>{tab === "server" ? draft.scaffold.serverModule : draft.scaffold.componentStub}</pre>
        </div>
      )}
    </Card>
  );
}

function AdminConsole({ user, subject = "electronics" }) {
  useI18nVersion();
  const [ov, setOv] = useState(null);
  const [users, setUsers] = useState([]);
  const [tutors, setTutors] = useState([]);
  const [preview, setPreview] = useState(null);
  useEffect(() => {
    api("/admin/overview").then(({ counts, runtime }) => setOv({ counts, runtime }));
    api("/admin/users").then(({ users }) => setUsers(users));
    api(`/tutors?subject=${subject}`).then(({ tutors }) => setTutors(tutors));
  }, [subject]);
  async function openPreview(id) { const { tutor } = await api(`/tutors/${id}`); setPreview(tutor); }
  if (preview) return <TutorPlayer base={preview} role="admin" onExit={() => setPreview(null)} />;
  return (
    <div className="page">
      <H><T>System overview</T></H>
      {ov && (
        <div className="stat-grid">
          <Card><Gauge label="TUTORS" value={ov.counts.tutors} suffix="" color={C.teal} /></Card>
          <Card><Gauge label="PUBLISHED" value={ov.counts.published} suffix="" color={C.blue} /></Card>
          <Card><Gauge label="USERS" value={ov.counts.users} suffix="" color={C.amber} /></Card>
          <Card><Gauge label="ATTEMPTS" value={ov.counts.attempts} suffix="" color={C.gold} /></Card>
          <Card className="span2">
            <Sub><T>Runtime</T></Sub>
            <div className="kv"><T>persistence:</T> <span className="t-blue">{ov.runtime.persistence}</span></div>
            <div className="kv"><T>llm:</T> <span className="t-blue">{ov.runtime.llm}</span></div>
          </Card>
        </div>
      )}
      <H><T>Bench authoring</T></H>
      <BenchWizard />
      <H><T>Users</T> <span className="dim">· {users.length}</span></H>
      <Card className="card-mb card--flush">
        {users.map((u) => (
          <div key={u.id} className="user-row">
            <span>{u.name} <span className="t-faint">· {u.email}</span></span>
            <Pill color={u.role === "admin" ? C.crimson : u.role === "educator" ? C.blue : C.teal}>{u.role}</Pill>
          </div>
        ))}
      </Card>
      <H><T>All tutors</T> <span className="dim">· {tutors.length}</span></H>
      {tutors.map((t) => (
        <TutorRow key={t.id} t={t}><button onClick={() => openPreview(t.id)} className="btn btn-secondary"><T>Preview</T></button></TutorRow>
      ))}
    </div>
  );
}

/* ===================== root ===================== */
function App() {
  useI18nVersion();
  const [user, setUser] = useState(undefined); // undefined = loading
  const [provider, setProvider] = useState(null);
  const [config, setConfig] = useState(null);
  const [authError, setAuthError] = useState(() => new URLSearchParams(window.location.search).get("authError"));
  useEffect(() => {
    // strip authError/token from the URL after reading it
    if (window.history.replaceState && (window.location.search.includes("authError") || window.location.search.includes("token"))) {
      window.history.replaceState({}, "", window.location.pathname);
    }
    api("/auth/config").then(setConfig).catch(() => setConfig({ oauth: false, devLogin: true }));
    api("/auth/me").then(({ user, provider }) => { setUser(user); setProvider(provider); }).catch(() => setUser(null));
  }, []);
  const [viewAs, setViewAs] = useState(null); // admin only: which role's view to render
  async function logout() { await api("/auth/logout", { method: "POST" }); setUser(null); setProvider(null); setViewAs(null); }
  if (user === undefined || config === null) return <div className="loading"><T>Loading…</T></div>;
  if (!user) return <Landing config={config} authError={authError} onLogin={setUser} />;
  // Path → subject (one entry per registered bench); anything else → subject picker.
  const path = window.location.pathname;
  const SUBJECT_PATHS = ["electronics", "statistics", "optics", "anova", "scatter", "geometry", "gaslaws", "titration", "equilibrium", "punnett", "population", "hardyweinberg", "kepler", "halflife", "seasons", "logicgates", "bigo", "binary"];
  const subject = SUBJECT_PATHS.find((s) => path.indexOf(`/${s}`) === 0) || null;
  // Admins can use the app as a teacher or student; everyone else is fixed to their role.
  const isAdmin = user.role === "admin";
  const effectiveRole = isAdmin ? (viewAs || "admin") : user.role;
  const Dash = effectiveRole === "admin" ? AdminConsole : effectiveRole === "educator" ? TeacherDashboard : StudentDashboard;
  return (
    <div className="app">
      <TopBar user={user} provider={provider} subject={subject} viewAs={isAdmin ? effectiveRole : null} onViewAs={isAdmin ? setViewAs : null} onLogout={logout} />
      {subject
        ? <Dash key={effectiveRole + subject} user={user} subject={subject} />
        : <SubjectPicker />}
    </div>
  );
}

// Each bench's STEM `cat` (category) mirrors server registry areaOf(field) — the catalog
// groups benches into Mathematics / Physics / … (Chemistry, Biology, CS reserved for future).
const SUBJECTS = [
  { id: "electronics", label: "Electronics", cat: "Physics", desc: "Build a series DC circuit — Ohm’s law, LEDs, current limiting.", color: C.teal, href: "/electronics" },
  { id: "optics", label: "Optics", cat: "Physics", desc: "Drag an object on a lens bench — real/virtual images, magnification.", color: C.amber, href: "/optics" },
  { id: "statistics", label: "Statistics", cat: "Mathematics", desc: "Manipulate distributions, sampling & hypothesis tests.", color: C.blue, href: "/statistics" },
  { id: "anova", label: "ANOVA", cat: "Mathematics", desc: "Drag group means — between/within variance, the F-ratio & p-value.", color: C.crimson, href: "/anova" },
  { id: "scatter", label: "Regression", cat: "Mathematics", desc: "Drag points on a scatterplot — correlation, the least-squares line & R².", color: C.gold, href: "/scatter" },
  { id: "geometry", label: "Geometry", cat: "Mathematics", desc: "Drag a point or slider — inscribed angles, power of a point, the bisector ratio.", color: C.teal, href: "/geometry" },
  { id: "gaslaws", label: "Gas Laws", cat: "Chemistry", desc: "Tune a gas piston — PV = nRT, Boyle’s & Gay-Lussac’s laws.", color: C.blue, href: "/gaslaws" },
  { id: "titration", label: "Titration & pH", cat: "Chemistry", desc: "Add base to an acid — the pH curve and the equivalence point.", color: C.crimson, href: "/titration" },
  { id: "equilibrium", label: "Equilibrium", cat: "Chemistry", desc: "Set concentrations — the reaction quotient Q vs. the constant K.", color: C.gold, href: "/equilibrium" },
  { id: "punnett", label: "Punnett Square", cat: "Biology", desc: "Cross two genotypes — Mendelian genotype & phenotype ratios.", color: C.teal, href: "/punnett" },
  { id: "population", label: "Population Growth", cat: "Biology", desc: "Logistic growth — r, carrying capacity K and dN/dt.", color: C.amber, href: "/population" },
  { id: "hardyweinberg", label: "Hardy–Weinberg", cat: "Biology", desc: "Allele frequency p — genotype frequencies p², 2pq, q².", color: C.blue, href: "/hardyweinberg" },
  { id: "kepler", label: "Kepler Orbits", cat: "Earth & Space", desc: "Orbit size & star mass — Kepler’s third law T² = a³/M.", color: C.gold, href: "/kepler" },
  { id: "halflife", label: "Radioactive Dating", cat: "Earth & Space", desc: "Half-life decay — fraction remaining and radiometric age.", color: C.crimson, href: "/halflife" },
  { id: "seasons", label: "Seasons", cat: "Earth & Space", desc: "Latitude, tilt & day — solar declination, Sun angle, insolation.", color: C.amber, href: "/seasons" },
  { id: "logicgates", label: "Logic Gates", cat: "Computer Science", desc: "Toggle inputs & gate — build the Boolean truth table.", color: C.teal, href: "/logicgates" },
  { id: "bigo", label: "Big-O Growth", cat: "Computer Science", desc: "Algorithm & input size — O(n), O(log n), O(n log n), O(n²).", color: C.blue, href: "/bigo" },
  { id: "binary", label: "Binary Numbers", cat: "Computer Science", desc: "Flip 8 bits — place value, decimal & hex representation.", color: C.gold, href: "/binary" },
];
// Category display order (areas with no benches yet are simply absent from the picker).
const SUBJECT_CATS = ["Mathematics", "Physics", "Chemistry", "Biology", "Earth & Space", "Computer Science"];
function subjectsByCat() {
  const groups = SUBJECT_CATS.map((cat) => ({ cat, items: SUBJECTS.filter((s) => s.cat === cat) })).filter((g) => g.items.length);
  const known = new Set(SUBJECT_CATS);
  for (const s of SUBJECTS) if (!known.has(s.cat)) { known.add(s.cat); groups.push({ cat: s.cat, items: SUBJECTS.filter((x) => x.cat === s.cat) }); }
  return groups;
}
function SubjectPicker() {
  return (
    <div className="page">
      <H><T>Choose a subject</T></H>
      <div className="helptext"><T>Benches are grouped by field. Each is its own tutor library, sharing your account and progress on this server.</T></div>
      {subjectsByCat().map((g) => (
        <div key={g.cat} style={{ marginTop: 18 }}>
          <div className="sub nr" style={{ textTransform: "uppercase", letterSpacing: ".08em", fontSize: 12, marginBottom: 8 }}><T>{g.cat}</T></div>
          <div className="tutor-grid">
            {g.items.map((s) => (
              <a key={s.id} href={s.href} style={{ textDecoration: "none" }}>
                <Card>
                  <div className="tutor-name nr" style={{ marginBottom: 4 }}><span style={{ color: s.color }}>●</span> <T>{s.label}</T></div>
                  <div className="helptext" style={{ minHeight: 38, marginBottom: 8 }}><T>{s.desc}</T></div>
                  <span className="btn btn-primary btn-block" style={{ textAlign: "center", display: "block" }}><T>Open</T></span>
                </Card>
              </a>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
