/* tangentplanelab.jsx — TangentPlaneTutor: the tangent-plane bench, in TRUE 3-D. Built on
   window.BenchKit (harness + 3-D SVG toolkit) + window.BenchFields (shared field math), so this file is
   just the FIGURE. computeFields = window.BenchFields.tangentplane — the SAME function
   server/benches/tangentplane.js calls, so client and server fields are identical by construction.
   The learner sets the tangency point (x0,y0) and an offset h; the paraboloid z = x²+y² is rendered as a
   shaded surface, with the translucent TANGENT PLANE L(X,Y) = f0 + fx·(X−x0) + fy·(Y−y0) laid over it
   around (x0,y0). At the offset point (x0+h, y0+h) a vertical error bar joins the plane to the surface
   (teal when the linearization is still accurate). The error |f − L| is second-order, so it opens up as
   h grows. Drag to orbit. 3-D method: docs/samples/triple-integral-svg.html. */
(function () {
  const { useState, useMemo, useEffect } = React;
  const K = window.BenchKit, C = K.C, fmt = K.fmt;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter, Scene3D = K.Scene3D;
  const compute = (s) => window.BenchFields.tangentplane(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 TangentPlaneFig({ task, setTask, tasks, report, event, done }) {
    const [x0, setX0] = useState(1);
    const [y0, setY0] = useState(1);
    const [h, setH] = useState(0.5);
    const [az, setAz] = useState(40);
    const [el, setEl] = useState(22);
    const fld = useMemo(() => compute({ x0, y0, h }), [x0, y0, h]);
    useEffect(() => { report(fld); }, [x0, y0, h]); // eslint-disable-line

    // the paraboloid and its tangent plane at (x0,y0)
    const f = (X, Y) => X * X + Y * Y;
    const fx = 2 * x0, fy = 2 * y0;
    const L = (X, Y) => f(x0, y0) + fx * (X - x0) + fy * (Y - y0);
    // offset point (x0+h, y0+h): on the surface vs on the plane — the gap is the error
    const ox = x0 + h, oy = y0 + h;
    const surfPt = [ox, oy, f(ox, oy)];
    const planePt = [ox, oy, L(ox, oy)];
    // tangent-plane patch half-width, clamped so its corners stay inside ~[-3,3]
    const w = Math.min(1.6, 3 - Math.abs(x0), 3 - Math.abs(y0));
    const cMM = [x0 - w, y0 - w, L(x0 - w, y0 - w)];
    const cPM = [x0 + w, y0 - w, L(x0 + w, y0 - w)];
    const cPP = [x0 + w, y0 + w, L(x0 + w, y0 + w)];
    const cMP = [x0 - w, y0 + w, L(x0 - w, y0 + w)];

    // frame: corners of [-3,3]² at their surface z and at z=0, plus tangency + offset point
    const fit = [];
    for (const xx of [-3, 3]) for (const yy of [-3, 3]) { fit.push([xx, yy, f(xx, yy)]); fit.push([xx, yy, 0]); }
    fit.push([x0, y0, f(x0, y0)], surfPt, planePt);

    const build = (screen) => {
      // base plane + axes (behind the surface)
      let out = K.poly3d(screen, [[-3, -3, 0], [3, -3, 0], [3, 3, 0], [-3, 3, 0]], "rgba(108,182,255,0.05)", C.line, 0.7);
      out += K.seg3d(screen, [-3, 0, 0], [3.2, 0, 0], C.faint, 0.8) + K.text3d(screen, [3.5, 0, 0], "x", C.faint);
      out += K.seg3d(screen, [0, -3, 0], [0, 3.2, 0], C.faint, 0.8) + K.text3d(screen, [0, 3.5, 0], "y", C.faint);
      // the paraboloid z = x²+y², height-coloured + Lambert-shaded
      out += K.surfaceMesh3d(screen, f, -3, 3, -3, 3, { n: 22 });
      // the TANGENT PLANE as a translucent quad around (x0,y0), with a couple of grid lines
      out += K.poly3d(screen, [cMM, cPM, cPP, cMP], "rgba(108,182,255,0.22)", C.blue, 1);
      out += K.seg3d(screen, [x0 - w, y0, L(x0 - w, y0)], [x0 + w, y0, L(x0 + w, y0)], C.blue, 0.7);
      out += K.seg3d(screen, [x0, y0 - w, L(x0, y0 - w)], [x0, y0 + w, L(x0, y0 + w)], C.blue, 0.7);
      // the tangency point
      out += K.dot3d(screen, [x0, y0, f(x0, y0)], 4.5, C.gold, C.ink);
      // the vertical ERROR segment between plane and surface at the offset point
      out += K.seg3d(screen, planePt, surfPt, fld.accurate ? C.teal : C.crimson, 2.6);
      out += K.dot3d(screen, planePt, 3.5, C.blue, C.ink);
      out += K.dot3d(screen, surfPt, 3.5, C.gold, C.ink);
      out += K.text3d(screen, [ox, oy, (surfPt[2] + planePt[2]) / 2], "err " + fmt(fld.error), fld.accurate ? C.teal : C.crimson);
      return out;
    };

    const t = tasks.find((x) => x.id === task) || tasks[0];
    const okColor = fld.accurate ? C.teal : C.amber;

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        <Scene3D az={az} el={el} setAz={setAz} setEl={setEl} fit={fit} width={600} height={320} build={build}
          onUp={() => event("rotated", `View az ${Math.round(az)}° · el ${Math.round(el)}°`, { response: `error ${fmt(fld.error)}` }, C.blue)} />
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 8, fontSize: 11.5, color: fld.accurate ? C.teal : C.faint, fontFamily: "monospace" }}>
          <span><K.T>drag to orbit</K.T> · <span style={{ color: C.blue }}>L = {fmt(fld.planeVal)}</span> · <span style={{ color: C.gold }}>f = {fmt(fld.fVal)}</span></span>
          <span>error = {fmt(fld.error)}</span>
        </div>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.accurate ? C.teal : C.line}`, color: fld.accurate ? C.teal : C.mute, fontSize: 12.5 }}>
          Plane value L = {fmt(fld.planeVal)} vs true surface value f = {fmt(fld.fVal)}. {fld.accurate ? <>⚑ <K.T>Accurate — the tangent plane is hugging the surface near (x0,y0).</K.T></> : <K.T>Drifting — shrink the offset h to bring the plane back onto the surface.</K.T>}
        </div>
        <Slider label="tangency x0" value={x0} set={setX0} min={-3} max={3} step={0.5} unit="" color={C.teal} onUp={() => event("adjusted", `Set x0 = ${fmt(x0)}`, { response: `fx ${fmt(fld.fx)}` }, C.teal)} />
        <Slider label="tangency y0" value={y0} set={setY0} min={-3} max={3} step={0.5} unit="" color={C.gold} onUp={() => event("adjusted", `Set y0 = ${fmt(y0)}`, { response: `fy ${fmt(fld.fy)}` }, C.gold)} />
        <Slider label="offset h" value={h} set={setH} min={-2} max={2} step={0.1} unit="" color={C.blue} onUp={() => event("adjusted", `Set h = ${fmt(h)}`, { response: `error ${fmt(fld.error)}` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="plane L" v={fld.planeVal} d={2} color={C.blue} />
          <StatMeter label="true f" v={fld.fVal} d={2} color={C.gold} />
          <StatMeter label="error" v={fld.error} d={3} color={okColor} />
          <StatMeter label="fx" v={fld.fx} d={2} color={C.teal} />
        </div>
      </>
    );
  }

  window.TangentPlaneTutor = K.makeTutor(TangentPlaneFig, { moduleLabel: "Tangent Plane bench", benchId: "tangentplane" });
})();
