/* geometrylab.jsx — GeometryTutor: the geometry workbench (three modules). Built on
   window.BenchKit (loaded first), so this file is just the FIGURES + their field
   computations. The GeometryFig dispatcher picks the sub-figure from spec.module; each
   figure's computeFields() mirrors server/benches/geometry.js simulate() EXACTLY (same
   660×540 unit space, same O / triangle / aim-point / tolerances) so the client and
   server agree on every graded field. The construction is computed from the free point;
   the learner drives a geometric observable to a target only the right model reaches. */
(function () {
  const { useState, useMemo, useEffect, useRef } = React;
  const K = window.BenchKit, C = K.C, SVG_BG = K.SVG_BG, btn = K.btn, fmt = K.fmt;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter;

  /* ── fixed scaffold (identical to the server bench) ── */
  const O = { x: 330, y: 270 }, QAIM = { x: 348, y: 278 };
  const TRI = { A: { x: 250, y: 70 }, B: { x: 120, y: 450 }, C: { x: 560, y: 430 } };
  const TR = 170, PR = 130; // thales / pop circle radii

  /* ── geometry kernel (mirrors the server kernel) ── */
  const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
  const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
  const mul = (a, s) => ({ x: a.x * s, y: a.y * s });
  const dot = (a, b) => a.x * b.x + a.y * b.y;
  const len = (a) => Math.hypot(a.x, a.y);
  const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
  const unit = (a) => { const l = Math.hypot(a.x, a.y) || 1; return { x: a.x / l, y: a.y / l }; };
  const perp = (a) => ({ x: -a.y, y: a.x });
  const r1 = (x) => Math.round(x * 10) / 10;
  const r2 = (x) => Math.round(x * 100) / 100;
  function angleAt(V, P, Q) { const a = sub(P, V), b = sub(Q, V); const c = dot(a, b) / ((len(a) * len(b)) || 1); return Math.acos(Math.max(-1, Math.min(1, c))) * 180 / Math.PI; }
  function lineCircle(P0, d, Oc, r) {
    const f = sub(P0, Oc); const a = dot(d, d), b = 2 * dot(f, d), c = dot(f, f) - r * r;
    const disc = b * b - 4 * a * c; if (disc < 0) return [];
    const s = Math.sqrt(disc); const t1 = (-b - s) / (2 * a), t2 = (-b + s) / (2 * a);
    return [add(P0, mul(d, t1)), add(P0, mul(d, t2))];
  }
  function tangents(P, Oc, r) { // [upper, lower] tangent points by screen-y (drawing only)
    const dd = dist(P, Oc); if (dd <= r) return [];
    const u = unit(sub(P, Oc)), n = perp(u); const cosA = r / dd, sinA = Math.sqrt(Math.max(0, 1 - cosA * cosA));
    const T1 = add(Oc, add(mul(u, r * cosA), mul(n, r * sinA)));
    const T2 = add(Oc, add(mul(u, r * cosA), mul(n, -r * sinA)));
    return T1.y < T2.y ? [T1, T2] : [T2, T1];
  }
  function lineLine(A1, A2, B1, B2) { const d1 = sub(A2, A1), d2 = sub(B2, B1); const den = d1.x * d2.y - d1.y * d2.x; if (Math.abs(den) < 1e-9) return null; const t = ((B1.x - A1.x) * d2.y - (B1.y - A1.y) * d2.x) / den; return add(A1, mul(d1, t)); }
  // arc path for an angle mark at vertex V between rays to P and Q
  function arc(V, P, Q, rr) {
    let a1 = Math.atan2(P.y - V.y, P.x - V.x), a2 = Math.atan2(Q.y - V.y, Q.x - V.x);
    let diff = a2 - a1; while (diff <= -Math.PI) diff += 2 * Math.PI; while (diff > Math.PI) diff -= 2 * Math.PI;
    const end = a1 + diff, sweep = diff > 0 ? 1 : 0;
    const s = { x: V.x + rr * Math.cos(a1), y: V.y + rr * Math.sin(a1) }, e = { x: V.x + rr * Math.cos(end), y: V.y + rr * Math.sin(end) };
    return `M ${s.x} ${s.y} A ${rr} ${rr} 0 0 ${sweep} ${e.x} ${e.y}`;
  }

  /* ── field computations — EXACTLY the server's per-module simulate() ── */
  function fieldsThales(P, r) {
    const A = { x: O.x - r, y: O.y }, B = { x: O.x + r, y: O.y };
    const distOP = dist(P, O), angleAPB = angleAt(P, A, B);
    const onTol = Math.max(4, r * 0.04), sideTol = Math.max(3, r * 0.03);
    return { angleAPB: r1(angleAPB), distOP: r1(distOP), radius: r1(r), onCircle: Math.abs(distOP - r) <= onTol, isRight: Math.abs(angleAPB - 90) <= 2.5, inside: distOP < r - sideTol, outside: distOP > r + sideTol, pUpper: P.y < O.y };
  }
  function fieldsPop(P, r) {
    const distPO = dist(P, O), power = distPO * distPO - r * r;
    const isExternal = distPO > r + 0.5, tangentLen = power > 0 ? Math.sqrt(power) : 0;
    const hits = lineCircle(P, unit(sub(QAIM, P)), O, r); let PA = 0, PB = 0, product = 0;
    if (hits.length === 2) { let da = dist(P, hits[0]), db = dist(P, hits[1]); if (da > db) { const t = da; da = db; db = t; } PA = da; PB = db; product = PA * PB; }
    const invariantHolds = isExternal && product > 0 && Math.abs(power - product) / Math.max(power, 1) < 0.02;
    return { distPO: r1(distPO), radius: r1(r), power: Math.round(power), tangentLen: r1(tangentLen), isExternal, secPA: r1(PA), secPB: r1(PB), product: Math.round(product), invariantHolds };
  }
  function fieldsBisector(t) {
    const { A, B, C: Cc } = TRI; const D = add(B, mul(sub(Cc, B), t));
    const AB = dist(A, B), AC = dist(A, Cc), BD = dist(B, D), DC = dist(D, Cc);
    const ratioBD_DC = DC > 1e-6 ? BD / DC : 999, ratioAB_AC = AC > 1e-6 ? AB / AC : 999;
    const angleBAD = angleAt(A, B, D), angleDAC = angleAt(A, D, Cc);
    return { AB: r1(AB), AC: r1(AC), BD: r1(BD), DC: r1(DC), ratioBD_DC: r2(ratioBD_DC), ratioAB_AC: r2(ratioAB_AC), angleBAD: r1(angleBAD), angleDAC: r1(angleDAC), isBisector: Math.abs(angleBAD - angleDAC) <= 1.5, ratiosMatch: Math.abs(ratioBD_DC - ratioAB_AC) <= 0.06 * ratioAB_AC + 0.02, D };
  }

  /* ── shared figure chrome ── */
  const VB = "0 0 660 540";
  function svgPt(ref, e) { const r = ref.current.getBoundingClientRect(); return { x: (e.clientX - r.left) / r.width * 660, y: (e.clientY - r.top) / r.height * 540 }; }
  function Grid() {
    const lines = [];
    for (let x = 0; x <= 660; x += 30) lines.push(<line key={`x${x}`} x1={x} y1={0} x2={x} y2={540} stroke="#163b50" strokeWidth={x % 150 === 0 ? 0.9 : 0.5} opacity={x % 150 === 0 ? 0.6 : 0.35} />);
    for (let y = 0; y <= 540; y += 30) lines.push(<line key={`y${y}`} x1={0} y1={y} x2={660} y2={y} stroke="#163b50" strokeWidth={y % 150 === 0 ? 0.9 : 0.5} opacity={y % 150 === 0 ? 0.6 : 0.35} />);
    return <g>{lines}</g>;
  }
  function Pt({ p, label, color, dx = 8, dy = -8 }) {
    return <g><circle cx={p.x} cy={p.y} r={4} fill={color || C.ink} stroke={SVG_BG} strokeWidth="1.5" />{label ? <text x={p.x + dx} y={p.y + dy} fill={color || "#f4f1e8"} fontSize="16" fontStyle="italic" fontWeight="600" fontFamily="'IBM Plex Mono',monospace">{label}</text> : null}</g>;
  }
  const Stage = (ref, onDown, children) => (
    <svg ref={ref} viewBox={VB} onPointerDown={onDown} style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8, touchAction: "none", cursor: "crosshair" }}>{children}</svg>
  );
  function RevealBtn({ on, onClick }) {
    return <button onClick={onClick} style={{ ...btn(on ? C.gold : C.panel2, on ? "#241600" : C.mute), border: `1px solid ${on ? C.gold : C.line}` }}>◳ {on ? "hide" : "reveal"} construction</button>;
  }

  /* ===================== Module 1: Thales / inscribed angle ===================== */
  function ThalesFig({ task, setTask, tasks, report, event, done }) {
    const r = TR;
    const [P, setP] = useState({ x: 330, y: 180 });
    const [drag, setDrag] = useState(false), [show, setShow] = useState(false);
    const ref = useRef(null);
    const A = { x: O.x - r, y: O.y }, B = { x: O.x + r, y: O.y };
    const fld = useMemo(() => fieldsThales(P, r), [P]);
    useEffect(() => { report(fld); }, [P]); // eslint-disable-line

    useEffect(() => {
      if (!drag) return;
      const move = (e) => { const p = svgPt(ref, e); setP({ x: Math.max(20, Math.min(640, p.x)), y: Math.max(20, Math.min(520, p.y)) }); };
      const up = () => { setDrag(false); event("adjusted", "Moved P", { response: `∠APB ${fmt(fieldsThales(P, r).angleAPB)}°` }, C.blue); };
      window.addEventListener("pointermove", move); window.addEventListener("pointerup", up);
      return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
    }, [drag, P]); // eslint-disable-line
    const t = tasks.find((x) => x.id === task) || tasks[0];
    const where = fld.onCircle ? "on the circle" : fld.inside ? "inside" : "outside";

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        {Stage(ref, null, <>
          <Grid />
          <circle cx={O.x} cy={O.y} r={r} fill="none" stroke={C.faint} strokeWidth="1.6" />
          <line x1={A.x} y1={A.y} x2={B.x} y2={B.y} stroke={C.ink} strokeWidth="2" />
          <line x1={A.x} y1={A.y} x2={P.x} y2={P.y} stroke={C.ink} strokeWidth="2" />
          <line x1={B.x} y1={B.y} x2={P.x} y2={P.y} stroke={C.ink} strokeWidth="2" />
          <path d={arc(P, A, B, 24)} fill="none" stroke={fld.isRight ? C.gold : C.crimson} strokeWidth="2.2" />
          {fld.isRight && <text x={P.x} y={P.y + 34} textAnchor="middle" fill={C.gold} fontSize="13" fontFamily="'IBM Plex Mono',monospace">90°</text>}
          {show && <>
            <line x1={O.x} y1={O.y} x2={P.x} y2={P.y} stroke={C.crimson} strokeWidth="2.2" strokeDasharray="6 5" />
            <path d={arc(A, B, P, 26)} fill="none" stroke={C.teal} strokeWidth="2" />
            <path d={arc(B, P, A, 26)} fill="none" stroke="#8a6fb0" strokeWidth="2" />
            <text x={A.x + 30} y={A.y - 12} fill={C.teal} fontSize="14" fontFamily="'IBM Plex Mono',monospace">α</text>
            <text x={B.x - 38} y={B.y - 12} fill="#8a6fb0" fontSize="14" fontFamily="'IBM Plex Mono',monospace">β</text>
          </>}
          <Pt p={O} label="O" color={C.faint} dx={-6} dy={18} />
          <Pt p={A} label="A" color={C.ink} dx={-20} dy={6} /><Pt p={B} label="B" color={C.ink} dx={8} dy={6} />
          <g style={{ cursor: "grab" }} onPointerDown={() => setDrag(true)}><circle cx={P.x} cy={P.y} r={11} fill={C.gold} opacity={drag ? 0.95 : 0.6} /><Pt p={P} label="P" color={C.gold} dx={-6} dy={-12} /></g>
        </>)}
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.isRight ? C.gold : C.line}`, color: fld.isRight ? C.gold : C.mute, fontSize: 12.5 }}>
          P is <b>{where}</b> · ∠APB = {fmt(fld.angleAPB)}° — {fld.isRight ? "right angle (the angle in a semicircle)." : fld.inside ? "obtuse (vertex inside)." : "acute (vertex outside)."} Drag P.
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 10 }}><RevealBtn on={show} onClick={() => { setShow((s) => !s); if (!show) event("revealed", "Construction: radius OP + base angles", {}, C.gold); }} /></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="∠APB" v={fld.angleAPB} unit="°" color={fld.isRight ? C.gold : C.crimson} />
          <StatMeter label="|OP|" v={fld.distOP} color={C.blue} />
          <StatMeter label="radius r" v={fld.radius} color={C.mute} />
          <StatMeter label="vertex" v={fld.onCircle ? "on ✓" : fld.inside ? "inside" : "outside"} color={fld.onCircle ? C.teal : C.amber} />
        </div>
      </>
    );
  }

  /* ===================== Module 2: Power of a point ===================== */
  function PopFig({ task, setTask, tasks, report, event, done }) {
    const r = PR;
    const [P, setP] = useState({ x: 150, y: 160 });
    const [drag, setDrag] = useState(false), [show, setShow] = useState(false);
    const ref = useRef(null);
    const fld = useMemo(() => fieldsPop(P, r), [P]);
    useEffect(() => { report(fld); }, [P]); // eslint-disable-line

    useEffect(() => {
      if (!drag) return;
      const move = (e) => { let p = svgPt(ref, e); const d = dist(p, O); if (d < r + 26) p = add(O, mul(unit(sub(p, O)), r + 26)); setP({ x: Math.max(20, Math.min(640, p.x)), y: Math.max(20, Math.min(520, p.y)) }); };
      const up = () => { setDrag(false); const f = fieldsPop(P, r); event("adjusted", "Moved P", { response: `PT² ${Math.round(f.tangentLen * f.tangentLen)} = PA·PB ${f.product}` }, C.blue); };
      window.addEventListener("pointermove", move); window.addEventListener("pointerup", up);
      return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
    }, [drag, P]); // eslint-disable-line

    const T = tangents(P, O, r)[0] || { x: O.x, y: O.y - r };
    const hits = lineCircle(P, unit(sub(QAIM, P)), O, r);
    let Apt = hits[0], Bpt = hits[1];
    if (Apt && Bpt && dist(P, Apt) > dist(P, Bpt)) { const tmp = Apt; Apt = Bpt; Bpt = tmp; }
    const secEnd = Bpt ? add(Bpt, mul(unit(sub(Bpt, P)), 28)) : QAIM;
    const t = tasks.find((x) => x.id === task) || tasks[0];

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        {Stage(ref, null, <>
          <Grid />
          <circle cx={O.x} cy={O.y} r={r} fill="none" stroke={C.faint} strokeWidth="1.6" />
          <Pt p={O} label="O" color={C.faint} dx={8} dy={6} />
          {fld.isExternal && <line x1={P.x} y1={P.y} x2={T.x} y2={T.y} stroke={C.ink} strokeWidth="2" />}
          {Apt && Bpt && <line x1={P.x} y1={P.y} x2={secEnd.x} y2={secEnd.y} stroke={C.ink} strokeWidth="2" />}
          {show && Apt && Bpt && <>
            <line x1={T.x} y1={T.y} x2={Apt.x} y2={Apt.y} stroke={C.crimson} strokeWidth="2.2" strokeDasharray="5 4" />
            <line x1={T.x} y1={T.y} x2={Bpt.x} y2={Bpt.y} stroke={C.teal} strokeWidth="2.2" strokeDasharray="5 4" />
            <path d={arc(T, P, Apt, 18)} fill="none" stroke={C.crimson} strokeWidth="2" />
            <path d={arc(Bpt, T, Apt, 18)} fill="none" stroke={C.crimson} strokeWidth="2" />
          </>}
          {fld.isExternal && <Pt p={T} label="T" color={C.gold} dx={6} dy={-8} />}
          {Apt && <Pt p={Apt} label="A" color={C.ink} dx={-6} dy={-10} />}
          {Bpt && <Pt p={Bpt} label="B" color={C.ink} dx={8} dy={-6} />}
          <g style={{ cursor: "grab" }} onPointerDown={() => setDrag(true)}><circle cx={P.x} cy={P.y} r={11} fill={C.crimson} opacity={drag ? 0.95 : 0.6} /><Pt p={P} label="P" color={C.crimson} dx={-22} dy={-6} /></g>
        </>)}
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.isExternal ? C.line : C.crimson}`, color: fld.isExternal ? C.mute : C.crimson, fontSize: 12.5 }}>
          {fld.isExternal ? `PT² = ${Math.round(fld.tangentLen * fld.tangentLen)}, PA·PB = ${fld.product} — ${fld.invariantHolds ? "equal ✓ (power of the point)." : "…"}` : "P is inside the circle — no real tangent exists. Pull it out."} Drag P.
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 10 }}><RevealBtn on={show} onClick={() => { setShow((s) => !s); if (!show) event("revealed", "Construction: chords TA, TB + tangent–chord angle", {}, C.gold); }} /></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="PT" v={fld.tangentLen} color={C.gold} />
          <StatMeter label="PT²" v={Math.round(fld.tangentLen * fld.tangentLen)} d={0} color={C.teal} />
          <StatMeter label="PA·PB" v={fld.product} d={0} color={C.teal} />
          <StatMeter label="power" v={fld.power} d={0} color={C.blue} />
          <StatMeter label="point" v={fld.isExternal ? "outside ✓" : "inside"} color={fld.isExternal ? C.teal : C.crimson} />
        </div>
      </>
    );
  }

  /* ===================== Module 3: Angle-bisector ratio ===================== */
  function BisectorFig({ task, setTask, tasks, report, event, done }) {
    const { A, B, C: Cc } = TRI;
    const [t, setT] = useState(0.3);
    const [drag, setDrag] = useState(false), [show, setShow] = useState(false);
    const ref = useRef(null);
    const fld = useMemo(() => fieldsBisector(t), [t]);
    const D = fld.D;
    useEffect(() => { report(fld); }, [t]); // eslint-disable-line

    useEffect(() => {
      if (!drag) return;
      const move = (e) => { const p = svgPt(ref, e); const bc = sub(Cc, B); const tt = Math.max(0.05, Math.min(0.95, dot(sub(p, B), bc) / dot(bc, bc))); setT(tt); };
      const up = () => { setDrag(false); const f = fieldsBisector(t); event("adjusted", "Moved D along BC", { response: `BD/DC ${f.ratioBD_DC}` }, C.blue); };
      window.addEventListener("pointermove", move); window.addEventListener("pointerup", up);
      return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
    }, [drag, t]); // eslint-disable-line

    // E = intersection of (line through C parallel to AD) with line BA — proof construction
    const E = lineLine(Cc, add(Cc, sub(D, A)), B, A);
    const tk = tasks.find((x) => x.id === task) || tasks[0];

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={tk && tk.goal} />
        {Stage(ref, null, <>
          <Grid />
          <line x1={A.x} y1={A.y} x2={B.x} y2={B.y} stroke={C.ink} strokeWidth="2" />
          <line x1={B.x} y1={B.y} x2={Cc.x} y2={Cc.y} stroke={C.ink} strokeWidth="2" />
          <line x1={Cc.x} y1={Cc.y} x2={A.x} y2={A.y} stroke={C.ink} strokeWidth="2" />
          <line x1={A.x} y1={A.y} x2={D.x} y2={D.y} stroke={fld.isBisector ? C.gold : C.crimson} strokeWidth="2.2" />
          <path d={arc(A, B, D, 30)} fill="none" stroke={C.teal} strokeWidth="2" />
          <path d={arc(A, D, Cc, 30)} fill="none" stroke="#8a6fb0" strokeWidth="2" />
          {show && E && <>
            <line x1={Cc.x} y1={Cc.y} x2={E.x} y2={E.y} stroke={C.crimson} strokeWidth="2.2" strokeDasharray="6 5" />
            <line x1={A.x} y1={A.y} x2={E.x} y2={E.y} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="3 5" />
            <Pt p={E} label="E" color={C.crimson} dx={-6} dy={-12} />
          </>}
          <Pt p={A} label="A" color={C.ink} dx={-6} dy={-12} />
          <Pt p={B} label="B" color={C.ink} dx={-22} dy={8} />
          <Pt p={Cc} label="C" color={C.ink} dx={10} dy={8} />
          <g style={{ cursor: "grab" }} onPointerDown={() => setDrag(true)}><circle cx={D.x} cy={D.y} r={11} fill={fld.isBisector ? C.gold : C.crimson} opacity={drag ? 0.95 : 0.6} /><Pt p={D} label="D" color={fld.isBisector ? C.gold : C.crimson} dx={-2} dy={22} /></g>
        </>)}
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.isBisector ? C.gold : C.line}`, color: fld.isBisector ? C.gold : C.mute, fontSize: 12.5 }}>
          ∠BAD = {fmt(fld.angleBAD)}°, ∠DAC = {fmt(fld.angleDAC)}° {fld.isBisector ? "— equal ✓, AD bisects ∠A." : "— slide D until equal."} · BD/DC = {fld.ratioBD_DC}, AB/AC = {fld.ratioAB_AC}{fld.ratiosMatch ? " (match ✓)" : ""}. Drag D.
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 10 }}><RevealBtn on={show} onClick={() => { setShow((s) => !s); if (!show) event("revealed", "Construction: CE ∥ AD, △AEC isosceles", {}, C.gold); }} /></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="∠BAD" v={fld.angleBAD} unit="°" color={C.teal} />
          <StatMeter label="∠DAC" v={fld.angleDAC} unit="°" color="#a98fd0" />
          <StatMeter label="BD/DC" v={fld.ratioBD_DC} d={2} color={C.gold} />
          <StatMeter label="AB/AC" v={fld.ratioAB_AC} d={2} color={C.blue} />
        </div>
      </>
    );
  }

  const FIGS = { thales: ThalesFig, pop: PopFig, bisector: BisectorFig };
  const MODULE_LABEL = { thales: "Semicircle & inscribed angle", pop: "Power of a point", bisector: "Angle-bisector ratio" };

  function GeometryFig(props) {
    const module = FIGS[props.spec && props.spec.module] ? props.spec.module : "thales";
    const Fig = FIGS[module];
    return <Fig {...props} />;
  }

  window.GeometryTutor = K.makeTutor(GeometryFig, {
    benchId: "geometry",
    moduleLabel: (spec) => MODULE_LABEL[spec && spec.module] || "Geometry workbench",
  });
})();
