/* densitylab.jsx — DensityTutor: the object-in-a-fluid density 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.density — the SAME function
   server/benches/density.js calls, so client and server fields are identical by construction.
   A 3-D fluid tank holds a cube that floats (straddling the waterline by submergedFraction) or
   sinks to the floor; drag to orbit. z is UP. 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.density(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 DensityFig({ task, setTask, tasks, report, event, done }) {
    const [mass, setMass] = useState(80);
    const [volume, setVolume] = useState(100);
    const [fluidDensity, setFluidDensity] = useState(1.0);
    const [az, setAz] = useState(32);
    const [el, setEl] = useState(18);
    const fld = useMemo(() => compute({ mass, volume, fluidDensity }), [mass, volume, fluidDensity]);
    useEffect(() => { report(fld); }, [mass, volume, fluidDensity]); // eslint-disable-line

    const floats = fld.floats;
    const sub = fld.submergedFraction; // 0..1 fraction of cube height below the waterline
    // world: z is UP. Tank box [−2,2]×[−2,2]×[0,4]; water fills up to zW.
    const TX = 2, zW = 3, zTop = 4;
    const L = Math.max(0.5, Math.min(2.4, Math.cbrt(volume) * 0.25)); // cube side ∝ ∛volume, clamped
    // floating: cube straddles the waterline, "sub" of its height below zW; sinking: rests on floor.
    const cbBot = floats ? (zW - L * sub) : 0, cbTop = cbBot + L, h = L / 2;
    const cubeRGB = floats ? [84, 160, 200] : [200, 90, 80];
    const t = tasks.find((x) => x.id === task) || tasks[0];

    // fit hull: tank corners + cube extents so the camera frames everything.
    const fit = [];
    for (const x of [-TX, TX]) for (const y of [-TX, TX]) { fit.push([x, y, 0]); fit.push([x, y, zTop]); }
    fit.push([-h, -h, cbBot], [h, h, cbTop]);

    const build = (screen) => {
      let out = "";
      // WATER first (translucent, painted before the cube so depth-sort lets the cube show through).
      // four side faces of the fluid body (faint), then the top surface plane at zW.
      const wf = "rgba(108,182,255,0.07)";
      out += K.poly3d(screen, [[-TX, -TX, 0], [TX, -TX, 0], [TX, -TX, zW], [-TX, -TX, zW]], wf);
      out += K.poly3d(screen, [[-TX, TX, 0], [TX, TX, 0], [TX, TX, zW], [-TX, TX, zW]], wf);
      out += K.poly3d(screen, [[-TX, -TX, 0], [-TX, TX, 0], [-TX, TX, zW], [-TX, -TX, zW]], wf);
      out += K.poly3d(screen, [[TX, -TX, 0], [TX, TX, 0], [TX, TX, zW], [TX, -TX, zW]], wf);
      out += K.poly3d(screen, [[-TX, -TX, zW], [TX, -TX, zW], [TX, TX, zW], [-TX, TX, zW]], "rgba(108,182,255,0.16)", C.blue, 1);
      out += K.text3d(screen, [TX + 0.55, TX, zW], "waterline", C.blue, 10);
      // TANK: the 12 edges of the box.
      const cs = [[-TX, -TX, 0], [TX, -TX, 0], [TX, TX, 0], [-TX, TX, 0], [-TX, -TX, zTop], [TX, -TX, zTop], [TX, TX, zTop], [-TX, TX, zTop]];
      const E = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]];
      for (const [a, b] of E) out += K.seg3d(screen, cs[a], cs[b], C.faint, 1);
      // the OBJECT cube (shaded, depth-sorted) — paints over the translucent water.
      out += K.paint3d(K.boxFaces3d(screen, -h, h, -h, h, cbBot, cbTop, cubeRGB, C.ink));
      out += K.text3d(screen, [0, 0, (cbBot + cbTop) / 2], `ρ ${fmt(fld.density)}`, "#0c1116", 11);
      // buoyant-force arrow (up) above the cube.
      if (fld.buoyantForce > 0.01) {
        const aL = Math.min(1.4, 0.4 + fld.buoyantForce / 6);
        out += K.vec3d(screen, [0, 0, cbTop + 0.1], [0, 0, cbTop + 0.1 + aL], C.gold, 2.4);
        out += K.text3d(screen, [0, 0, cbTop + 0.3 + aL], `F_b ${fmt(fld.buoyantForce)} N`, C.gold, 10);
      }
      out += K.text3d(screen, [0, -TX - 0.4, zTop + 0.3], floats ? "FLOATS" : "SINKS", floats ? C.teal : C.crimson, 13);
      return out;
    };

    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: `ρ ${fmt(fld.density)}` }, 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> · ρ = {fmt(fld.density)} g/cm³ vs fluid {fmt(fluidDensity)} · <span style={{ color: floats ? C.teal : C.crimson }}>{floats ? "floats" : "sinks"}</span></span>
          <span>m = {Math.round(mass)} g · V = {Math.round(volume)} cm³</span>
        </div>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${C.line}`, color: C.mute, fontSize: 12.5 }}>
          ρ = m/V = {fmt(fld.density)} g/cm³ vs ρ_fluid = {fmt(fluidDensity)} g/cm³ · {floats ? `floats with ${Math.round(sub * 100)}% submerged.` : <K.T>sinks (ρ ≥ ρ_fluid). Lower the mass or raise the volume to float it.</K.T>}
        </div>
        <Slider label="object mass m" value={mass} set={setMass} min={1} max={1000} step={1} unit="g" color={C.crimson} onUp={() => event("adjusted", `Set m = ${Math.round(mass)} g`, { response: `ρ ${fmt(fld.density)} · ${fld.floats ? "floats" : "sinks"}` }, C.crimson)} />
        <Slider label="object volume V" value={volume} set={setVolume} min={1} max={1000} step={1} unit="cm³" color={C.teal} onUp={() => event("adjusted", `Set V = ${Math.round(volume)} cm³`, { response: `ρ ${fmt(fld.density)} · ${fld.floats ? "floats" : "sinks"}` }, C.teal)} />
        <Slider label="fluid density" value={fluidDensity} set={setFluidDensity} min={0.5} max={14} step={0.1} unit="g/cm³" color={C.blue} onUp={() => event("adjusted", `Set ρ_fluid = ${fmt(fluidDensity)} g/cm³`, { response: `ρ ${fmt(fld.density)} · ${fld.floats ? "floats" : "sinks"}` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="density ρ" v={fld.density} d={2} unit="g/cm³" color={floats ? C.teal : C.crimson} />
          <StatMeter label="buoyant force" v={fld.buoyantForce} d={2} unit="N" color={C.gold} />
          <StatMeter label="submerged" v={fld.submergedFraction} d={2} color={C.amber} />
          <StatMeter label="fluid ρ" v={fld.fluidDensity} d={2} unit="g/cm³" color={C.blue} />
        </div>
      </>
    );
  }

  window.DensityTutor = K.makeTutor(DensityFig, { moduleLabel: "Density bench", benchId: "density" });
})();
