/* volumerevlab.jsx — VolumeRevTutor: the volumes-of-revolution 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.volumerev — the SAME function
   server/benches/volumerev.js calls, so client and server fields are identical by construction.
   The learner sets the region [a,b] and the number of disks n; a drag-to-rotate stack of n low-poly
   cylinders — the solid swept by revolving y = x about the x-axis — makes the midpoint disk sum visible
   as it converges to the exact volume π(b³−a³)/3. A point on disk i at angle θ is (x, r·cosθ, r·sinθ),
   so the cross-sections are circles in the y–z plane. 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.volumerev(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 VolumeRevFig({ task, setTask, tasks, report, event, done }) {
    const [a, setA] = useState(0);
    const [b, setB] = useState(4);
    const [n, setN] = useState(4);
    const [az, setAz] = useState(28);
    const [el, setEl] = useState(18);
    const fld = useMemo(() => compute({ a, b, n }), [a, b, n]);
    useEffect(() => { report(fld); }, [a, b, n]); // eslint-disable-line

    // the solid of revolution about the x-axis, drawn as a stack of n low-poly cylinders (disks).
    const ni = Math.max(1, Math.round(n));
    const aa = Math.max(0, a), bb = Math.max(aa + 0.1, b);
    const dx = (bb - aa) / ni;
    const rmax = bb || 1;                        // f(x) = x, so max radius is b

    // world bounding box: frame the whole tube along x with the rim corners (±rmax in y and z)
    const fit = [[aa, 0, 0], [bb, 0, 0]];
    for (const xx of [aa, bb]) for (const yy of [-rmax, rmax]) for (const zz of [-rmax, rmax]) fit.push([xx, yy, zz]);

    const SEG = 24; // angular segments per cylinder
    const build = (screen) => {
      const faces = [];
      for (let i = 0; i < ni; i++) {
        const xm = aa + (i + 0.5) * dx, r = Math.max(0, xm); // f(xm) = xm
        if (r <= 1e-9) continue;
        const xLo = aa + i * dx, xHi = aa + (i + 1) * dx;
        const t = Math.max(0, Math.min(1, r / rmax));
        for (let k = 0; k < SEG; k++) {
          const th0 = (k / SEG) * 2 * Math.PI, th1 = ((k + 1) / SEG) * 2 * Math.PI;
          const c0 = Math.cos(th0), s0 = Math.sin(th0), c1 = Math.cos(th1), s1 = Math.sin(th1);
          const thm = (th0 + th1) / 2, nrm = [0, Math.cos(thm), Math.sin(thm)]; // outward radial normal
          const col = V3.shade(V3.colormap(t), nrm);
          // side quad of the cylinder shell
          faces.push(K.face3d(screen, [[xLo, r * c0, r * s0], [xHi, r * c0, r * s0], [xHi, r * c1, r * s1], [xLo, r * c1, r * s1]], col));
          // first disk's left cap and last disk's right cap (fan triangles to the axis point)
          if (i === 0) faces.push(K.face3d(screen, [[xLo, 0, 0], [xLo, r * c0, r * s0], [xLo, r * c1, r * s1], [xLo, 0, 0]], V3.shade(V3.colormap(t), [-1, 0, 0])));
          if (i === ni - 1) faces.push(K.face3d(screen, [[xHi, 0, 0], [xHi, r * c1, r * s1], [xHi, r * c0, r * s0], [xHi, 0, 0]], V3.shade(V3.colormap(t), [1, 0, 0])));
        }
        // exposed annular step on the +x face where the next disk is larger (radius increases)
        if (i < ni - 1) {
          const rNext = Math.max(0, aa + (i + 1.5) * dx);
          if (rNext > r) {
            const colS = V3.shade(V3.colormap(t), [1, 0, 0]);
            for (let k = 0; k < SEG; k++) {
              const th0 = (k / SEG) * 2 * Math.PI, th1 = ((k + 1) / SEG) * 2 * Math.PI;
              const c0 = Math.cos(th0), s0 = Math.sin(th0), c1 = Math.cos(th1), s1 = Math.sin(th1);
              faces.push(K.face3d(screen, [[xHi, r * c0, r * s0], [xHi, r * c1, r * s1], [xHi, rNext * c1, rNext * s1], [xHi, rNext * c0, rNext * s0]], colS));
            }
          }
        }
      }
      let out = K.paint3d(faces);
      // x-axis through the solid + generating line y = x (the swept profile) + bound labels
      out += K.seg3d(screen, [aa - 0.3, 0, 0], [bb + 0.3, 0, 0], C.faint, 1, "4 3") + K.text3d(screen, [bb + 0.55, 0, 0], "x", C.faint);
      out += K.seg3d(screen, [aa, aa, 0], [bb, bb, 0], C.gold, 2);
      out += K.text3d(screen, [aa, 0, 0], `a = ${fmt(aa)}`, C.faint) + K.text3d(screen, [bb, 0, 0], `b = ${fmt(bb)}`, 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 to orbit</K.T> · {fld.nDisks} <K.T>disks</K.T> · <K.T>revolve y = x about the x-axis</K.T></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 disk sum = {fmt(fld.riemann)} vs exact volume π(b³−a³)/3 = {fmt(fld.exactVol)}. {fld.converged ? <>⚑ <K.T>Converged — the disk sum has closed onto the exact volume.</K.T></> : <K.T>Not converged yet — add disks (raise n) to shrink the error.</K.T>}
        </div>
        <Slider label="left bound a" value={a} set={setA} min={0} max={3} step={0.5} unit="" color={C.teal} onUp={() => event("adjusted", `Set a = ${fmt(a)}`, { response: `volume ${fmt(fld.exactVol)}` }, C.teal)} />
        <Slider label="right bound b" value={b} set={setB} min={1} max={6} step={0.5} unit="" color={C.gold} onUp={() => event("adjusted", `Set b = ${fmt(b)}`, { response: `volume ${fmt(fld.exactVol)}` }, C.gold)} />
        <Slider label="disks n" value={n} set={(v) => setN(Math.round(v))} min={1} max={60} 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="disk 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="# disks" v={fld.nDisks} d={0} color={C.amber} />
        </div>
      </>
    );
  }

  window.VolumeRevTutor = K.makeTutor(VolumeRevFig, { moduleLabel: "Volumes of Revolution bench", benchId: "volumerev" });
})();
