/* lagrangelab.jsx — LagrangeTutor: the Lagrange multipliers bench. Built on window.BenchKit
   (harness) + window.BenchFields (shared field math), so this file is just the FIGURE.
   computeFields = window.BenchFields.lagrange — the SAME function server/benches/lagrange.js
   calls, so client and server fields are identical by construction. The learner slides a point
   around the constraint circle x²+y²=r² to maximize f = xy; the level curves of f (hyperbolas)
   and the gradient arrows ∇f = (y,x) and ∇g = (2x,2y) make the Lagrange condition ∇f = λ∇g
   visible — at the optimum the gradients are parallel and a level curve is tangent to the circle. */
(function () {
  const { useState, useMemo, useEffect } = React;
  const K = window.BenchKit, C = K.C, SVG_BG = K.SVG_BG, fmt = K.fmt;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter;
  const compute = (s) => window.BenchFields.lagrange(s);

  function Slider({ label, value, set, min, max, step, unit, color, onUp }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 9 }}>
        <span style={{ fontSize: 12, color: C.mute, width: 116 }}><K.T>{label}</K.T></span>
        <input type="range" min={min} max={max} step={step} value={value} onChange={(e) => set(parseFloat(e.target.value))} onPointerUp={onUp} style={{ flex: 1 }} />
        <span style={{ color: color || C.amber, width: 64, textAlign: "right" }}>{value}{unit ? ` ${unit}` : ""}</span>
      </div>
    );
  }

  function LagrangeFig({ task, setTask, tasks, report, event, done }) {
    const [r, setR] = useState(2);
    const [t, setT] = useState(45);
    const fld = useMemo(() => compute({ r, t }), [r, t]);
    useEffect(() => { report(fld); }, [r, t]); // eslint-disable-line

    // world→screen: centered axes, px-per-unit S
    const cx0 = 175, cy0 = 150, S = 38;
    const sx = (wx) => cx0 + wx * S, sy = (wy) => cy0 - wy * S;

    // constraint circle of radius r
    const circR = r * S;

    // a couple of level curves of f = xy (hyperbolas) for reference: xy = c, y = c/x
    const hyper = (c) => {
      const pts = [];
      for (let i = 0; i <= 60; i++) {
        const wx = 0.2 + (i / 60) * 3.6;       // first-quadrant branch
        const wy = c / wx;
        if (Math.abs(wy) <= 3.2) pts.push(`${sx(wx).toFixed(1)},${sy(wy).toFixed(1)}`);
      }
      const neg = [];
      for (let i = 0; i <= 60; i++) {
        const wx = -(0.2 + (i / 60) * 3.6);    // third-quadrant branch
        const wy = c / wx;
        if (Math.abs(wy) <= 3.2) neg.push(`${sx(wx).toFixed(1)},${sy(wy).toFixed(1)}`);
      }
      return { pos: pts.join(" "), neg: neg.join(" ") };
    };
    const levels = [0.8, 2].map((c) => hyper(c));

    // the point on the constraint
    const px = sx(fld.xPt), py = sy(fld.yPt);
    // gradient arrows from the point: ∇f = (y,x), ∇g = (2x,2y), scaled to screen
    const gfScale = 22, ggScale = 12;
    const fx2 = px + fld.yPt * gfScale, fy2 = py - fld.xPt * gfScale;            // ∇f = (y,x)
    const gx2 = px + (2 * fld.xPt) * ggScale, gy2 = py - (2 * fld.yPt) * ggScale; // ∇g = (2x,2y)
    const opt = fld.atOptimum;
    const gfColor = opt ? C.teal : C.amber;

    const tk = tasks.find((q) => q.id === task) || tasks[0];

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={tk && tk.goal} />
        <svg viewBox="0 0 600 300" style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8 }}>
          <defs>
            <marker id="lag-f" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill={gfColor} /></marker>
            <marker id="lag-g" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill={C.blue} /></marker>
          </defs>
          {/* axes */}
          <line x1={cx0 - 3.4 * S} y1={cy0} x2={cx0 + 3.4 * S} y2={cy0} stroke={C.faint} strokeWidth="1" />
          <line x1={cx0} y1={cy0 - 3.4 * S} x2={cx0} y2={cy0 + 3.4 * S} stroke={C.faint} strokeWidth="1" />
          {/* level curves of f = xy (hyperbolas) */}
          {levels.map((lv, i) => (
            <g key={i}>
              <polyline points={lv.pos} fill="none" stroke={C.line} strokeWidth="1" strokeOpacity="0.7" strokeDasharray="2 3" />
              <polyline points={lv.neg} fill="none" stroke={C.line} strokeWidth="1" strokeOpacity="0.7" strokeDasharray="2 3" />
            </g>
          ))}
          {/* the constraint circle x²+y²=r² */}
          <circle cx={cx0} cy={cy0} r={circR.toFixed(1)} fill="none" stroke={C.gold} strokeWidth="1.5" />
          {/* gradient ∇g = (2x,2y) */}
          <line x1={px} y1={py} x2={gx2} y2={gy2} stroke={C.blue} strokeWidth="2" strokeDasharray="3 2" markerEnd="url(#lag-g)" />
          {/* gradient ∇f = (y,x) */}
          <line x1={px} y1={py} x2={fx2} y2={fy2} stroke={gfColor} strokeWidth="2.5" markerEnd="url(#lag-f)" />
          {/* the point on the constraint */}
          <circle cx={px} cy={py} r="4.5" fill={C.gold} stroke={C.bg} strokeWidth="1" />
          {/* readout panel */}
          <text x="372" y="40" fill={C.mute} fontSize="12" fontFamily="monospace">f = xy on x²+y²=r²</text>
          <text x="372" y="64" fill={gfColor} fontSize="13" fontFamily="monospace">f = {fmt(fld.fValue)}</text>
          <text x="372" y="86" fill={C.blue} fontSize="13" fontFamily="monospace">cross = {fmt(fld.cross)}</text>
          <text x="372" y="108" fill={C.teal} fontSize="13" fontFamily="monospace">λ = {fmt(fld.lambda)}</text>
          <text x="372" y="132" fill={C.mute} fontSize="11" fontFamily="monospace">{opt ? "∇f ∥ ∇g — at the optimum" : "gradients not yet aligned"}</text>
        </svg>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${opt ? C.teal : C.line}`, color: opt ? C.teal : C.mute, fontSize: 12.5 }}>
          f = {fmt(fld.fValue)} at ({fmt(fld.xPt)}, {fmt(fld.yPt)}); gradient misalignment cross = {fmt(fld.cross)}. {opt ? <>⚑ <K.T>∇f = λ∇g — a level curve is tangent to the circle; f is at its constrained maximum r²/2.</K.T></> : <K.T>Rotate the point toward t = 45° so ∇f lines up with ∇g and f climbs to its maximum.</K.T>}
        </div>
        <Slider label="radius r" value={r} set={setR} min={1} max={3} step={0.5} unit="" color={C.gold} onUp={() => event("adjusted", `Set r = ${fmt(r)}`, { response: `fValue ${fmt(fld.fValue)}` }, C.gold)} />
        <Slider label="angle t" value={t} set={(v) => setT(Math.round(v))} min={0} max={360} step={5} unit="°" color={C.blue} onUp={() => event("adjusted", `Set t = ${Math.round(t)}°`, { response: `cross ${fmt(fld.cross)}` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="f = xy" v={fld.fValue} d={2} color={gfColor} />
          <StatMeter label="cross" v={fld.cross} d={2} color={C.blue} />
          <StatMeter label="λ" v={fld.lambda} d={2} color={C.teal} />
          <StatMeter label="at optimum" v={opt ? 1 : 0} d={0} color={opt ? C.teal : C.mute} />
        </div>
      </>
    );
  }

  window.LagrangeTutor = K.makeTutor(LagrangeFig, { moduleLabel: "Lagrange Multipliers bench", benchId: "lagrange" });
})();
