/* doubleintegrallab.jsx — DoubleIntegralTutor: the double integrals 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.doubleintegral — the SAME function
   server/benches/doubleintegral.js calls, so client and server fields are identical by construction.
   The learner sets the region [0,X]×[0,Y] and the grid resolution n; an n×n grid of Riemann COLUMNS rises
   under the surface z = x²+y², drag-to-rotate, converging onto the exact volume XY(X²+Y²)/3 — the
   midpoint Riemann sum made literally three-dimensional. 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, V3 = K.V3;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter, Scene3D = K.Scene3D;
  const compute = (s) => window.BenchFields.doubleintegral(s);
  const surf = (x, y) => x * x + y * y; // z = f(x,y)

  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 DoubleIntegralFig({ task, setTask, tasks, report, event, done }) {
    const [X, setX] = useState(2);
    const [Y, setY] = useState(2);
    const [n, setN] = useState(2);
    const [az, setAz] = useState(38);
    const [el, setEl] = useState(26);
    const fld = useMemo(() => compute({ X, Y, n }), [X, Y, n]);
    useEffect(() => { report(fld); }, [X, Y, n]); // eslint-disable-line

    const ni = Math.max(1, Math.round(n));
    const dx = X / ni, dy = Y / ni;
    const zmax = X * X + Y * Y || 1;             // max surface height (far corner) — colour + fit scale
    // midpoint column heights h[i][j] = f(xm, ym)
    const heights = useMemo(() => {
      const h = [];
      for (let i = 0; i < ni; i++) { h[i] = []; for (let j = 0; j < ni; j++) { const xm = (i + 0.5) * dx, ym = (j + 0.5) * dy; h[i][j] = xm * xm + ym * ym; } }
      return h;
    }, [X, Y, ni, dx, dy]);

    // world bounding box corners → framed by the projector
    const fit = [];
    for (const xx of [0, X]) for (const yy of [0, Y]) for (const zz of [0, zmax]) fit.push([xx, yy, zz]);

    const build = (screen) => {
      const faces = [];
      const H = (i, j) => (i < 0 || j < 0 || i >= ni || j >= ni) ? 0 : heights[i][j];
      // Riemann columns — top face always, side faces only where a neighbour is shorter (cull hidden faces)
      for (let i = 0; i < ni; i++) for (let j = 0; j < ni; j++) {
        const h = heights[i][j]; if (h <= 1e-9) continue;
        const ax = i * dx, bx = (i + 1) * dx, ay = j * dy, by = (j + 1) * dy;
        const col = V3.colormap(h / zmax);
        faces.push(K.face3d(screen, [[ax, ay, h], [bx, ay, h], [bx, by, h], [ax, by, h]], V3.shade(col, [0, 0, 1])));
        const nb = [
          { hN: H(i - 1, j), P: [[ax, ay], [ax, by]], nrm: [-1, 0, 0] },
          { hN: H(i + 1, j), P: [[bx, by], [bx, ay]], nrm: [1, 0, 0] },
          { hN: H(i, j - 1), P: [[bx, ay], [ax, ay]], nrm: [0, -1, 0] },
          { hN: H(i, j + 1), P: [[ax, by], [bx, by]], nrm: [0, 1, 0] },
        ];
        for (const s of nb) {
          if (h <= s.hN) continue;
          const zlo = Math.max(0, s.hN), A = s.P[0], B = s.P[1];
          faces.push(K.face3d(screen, [[A[0], A[1], zlo], [B[0], B[1], zlo], [B[0], B[1], h], [A[0], A[1], h]], V3.shade(col, s.nrm)));
        }
      }
      let out = K.paint3d(faces);
      // the TRUE surface z = x²+y² as a faint wireframe the columns converge to
      const SN = 16, sdx = X / SN, sdy = Y / SN;
      for (let i = 0; i <= SN; i++) { const pts = []; for (let j = 0; j <= SN; j++) { const x = i * sdx, y = j * sdy; pts.push(screen([x, y, surf(x, y)])); } out += `<polyline points="${pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ")}" fill="none" stroke="rgba(84,242,178,.42)" stroke-width="0.8"/>`; }
      for (let j = 0; j <= SN; j++) { const pts = []; for (let i = 0; i <= SN; i++) { const x = i * sdx, y = j * sdy; pts.push(screen([x, y, surf(x, y)])); } out += `<polyline points="${pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ")}" fill="none" stroke="rgba(84,242,178,.42)" stroke-width="0.8"/>`; }
      // base rectangle outline + axes
      out += K.poly3d(screen, [[0, 0, 0], [X, 0, 0], [X, Y, 0], [0, Y, 0]], "none", C.faint, 1.2);
      out += K.seg3d(screen, [0, 0, 0], [X * 1.12, 0, 0], C.faint, 1) + K.text3d(screen, [X * 1.2, 0, 0], "x", C.faint);
      out += K.seg3d(screen, [0, 0, 0], [0, Y * 1.12, 0], C.faint, 1) + K.text3d(screen, [0, Y * 1.2, 0], "y", C.faint);
      out += K.seg3d(screen, [0, 0, 0], [0, 0, zmax * 1.08], C.faint, 1, "3 3") + K.text3d(screen, [0, 0, zmax * 1.15], "z", C.faint);
      return out;
    };

    const t = tasks.find((x) => x.id === task) || tasks[0];
    const okColor = fld.converged ? 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: `riemann ${fmt(fld.riemann)}` }, C.blue)} />
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 8, fontSize: 11.5, color: C.faint, fontFamily: "monospace" }}>
          <span><K.T>drag the figure to orbit</K.T> · {ni}×{ni} = {fld.nBoxes} <K.T>columns</K.T> · z = x²+y²</span>
          <span style={{ color: C.blue }}>Σ = {fmt(fld.riemann)} <span style={{ color: C.faint }}>vs</span> <span style={{ color: C.teal }}>V = {fmt(fld.exactVol)}</span></span>
        </div>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.converged ? C.teal : C.line}`, color: fld.converged ? C.teal : C.mute, fontSize: 12.5 }}>
          Midpoint Riemann sum = {fmt(fld.riemann)} vs exact volume XY(X²+Y²)/3 = {fmt(fld.exactVol)}. {fld.converged ? <>⚑ <K.T>Converged — the columns have closed onto the exact volume under the surface.</K.T></> : <K.T>Not converged yet — add boxes (raise n) to shrink the error.</K.T>}
        </div>
        <Slider label="region width X" value={X} set={setX} min={0.5} max={4} step={0.5} unit="" color={C.teal} onUp={() => event("adjusted", `Set X = ${fmt(X)}`, { response: `riemann ${fmt(fld.riemann)}` }, C.teal)} />
        <Slider label="region depth Y" value={Y} set={setY} min={0.5} max={4} step={0.5} unit="" color={C.gold} onUp={() => event("adjusted", `Set Y = ${fmt(Y)}`, { response: `riemann ${fmt(fld.riemann)}` }, C.gold)} />
        <Slider label="boxes per side n" value={n} set={(v) => setN(Math.round(v))} min={1} max={40} step={1} unit="" color={C.blue} onUp={() => event("adjusted", `Set n = ${fmt(Math.round(n))}`, { response: `error ${fmt(fld.error)}` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="Riemann sum" v={fld.riemann} d={2} color={C.blue} />
          <StatMeter label="exact volume" v={fld.exactVol} d={2} color={C.teal} />
          <StatMeter label="error" v={fld.error} d={3} color={okColor} />
          <StatMeter label="# boxes" v={fld.nBoxes} d={0} color={C.amber} />
        </div>
      </>
    );
  }

  window.DoubleIntegralTutor = K.makeTutor(DoubleIntegralFig, { moduleLabel: "Double Integrals bench", benchId: "doubleintegral" });
})();
