/* forceslab.jsx — ForcesTutor: the block-on-a-friction-incline bench. Built on
   window.BenchKit (harness) + window.BenchFields (shared field math), so this file is
   just the FIGURE. computeFields = window.BenchFields.forces — the SAME function
   server/benches/forces.js calls, so client and server fields are identical by
   construction. Follows the gas-laws TEMPLATE: sliders → BenchFields.fn(state) → report + meters. */
(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.forces(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 ForcesFig({ task, setTask, tasks, report, event, done }) {
    const [mass, setMass] = useState(5);
    const [inclineAngle, setInclineAngle] = useState(30);
    const [mu, setMu] = useState(0.2);
    const [az, setAz] = useState(32);
    const [el, setEl] = useState(16);
    const fld = useMemo(() => compute({ mass, inclineAngle, mu }), [mass, inclineAngle, mu]);
    useEffect(() => { report(fld); }, [mass, inclineAngle, mu]); // eslint-disable-line

    const sliding = !fld.isStatic;

    // world: z is UP. A triangular-prism wedge along x (base B), height H, width Wy in y.
    // right-angle at [0,*,0]; high end A=[0,*,H]; low end B0=[B,*,0]; incline runs A→B0.
    const th = inclineAngle * Math.PI / 180;
    const B = 3, Wy = 1.6, hy = Wy / 2;
    const H = Math.min(4, B * Math.tan(th));               // clamp height
    // wedge corners (y = ±hy faces)
    const O = (y) => [0, y, 0], Hi = (y) => [0, y, H], Lo = (y) => [B, y, 0];
    // incline-surface unit vectors (in x-z plane)
    const slopeLen = Math.hypot(B, H) || 1;
    const sdx = B / slopeLen, sdz = -H / slopeLen;         // down-slope direction (A→B0), z<0
    const up = [-sdx, 0, -sdz];                            // up-slope unit (B0→A)
    const surfN = V3.norm([H, 0, B]);                      // outward incline normal (up & away)

    // block: a small cube centred just above the incline midpoint along the surface normal.
    const s = 0.5;
    const Pmid = [0 + s * (B - 0), 0, H + s * (0 - H)];    // A + s·(B0−A), at y=0
    const cube = 0.42, liftN = cube * 0.62;
    const bc = [Pmid[0] + surfN[0] * liftN, 0, Pmid[2] + surfN[2] * liftN];  // block centre
    const blockCol = fld.isStatic ? C.teal : C.crimson;

    // force vectors from the block centre, lengths ∝ magnitude (largest ≈ 1.5 world units).
    const maxMag = Math.max(fld.weight, fld.normal, fld.friction, Math.abs(fld.netForce), 1e-6);
    const VS = 1.5 / maxMag;
    const vEnd = (dir, mag) => [bc[0] + dir[0] * mag * VS, bc[1] + dir[1] * mag * VS, bc[2] + dir[2] * mag * VS];
    const wTip = vEnd([0, 0, -1], fld.weight);            // weight straight down
    const nTip = vEnd(surfN, fld.normal);                 // normal along incline outward normal
    const fTip = vEnd(up, fld.friction);                  // friction up the slope (opposing slide)
    const netTip = vEnd([sdx, 0, sdz], Math.abs(fld.netForce)); // net / gravity-along, down-slope

    // fit points: wedge corners + block + vector tips + ground span
    const fit = [];
    for (const y of [-hy, hy]) { fit.push(O(y), Hi(y), Lo(y)); }
    fit.push(bc, wTip, nTip, fTip, [-0.6, 0, 0], [B + 0.6, 0, 0], [0, 0, H + 0.4]);
    if (Math.abs(fld.netForce) > 0.01) fit.push(netTip);

    const stone = [120, 134, 146];

    const build = (screen) => {
      const faces = [];
      // triangular sides at y = ±hy
      for (const y of [-hy, hy]) {
        const a = O(y), b = Hi(y), c = Lo(y);
        const nrm = y < 0 ? [0, -1, 0] : [0, 1, 0];
        faces.push(K.face3d(screen, [a, b, c, c], V3.shade(stone, nrm)));
      }
      // base (z=0 rectangle): O(-hy),O(hy),Lo(hy),Lo(-hy)
      faces.push(K.face3d(screen, [O(-hy), O(hy), Lo(hy), Lo(-hy)], V3.shade(stone, [0, 0, -1])));
      // vertical back wall at x=0: O(-hy),O(hy),Hi(hy),Hi(-hy)
      faces.push(K.face3d(screen, [O(-hy), O(hy), Hi(hy), Hi(-hy)], V3.shade(stone, [-1, 0, 0])));
      // inclined top surface (the quad the block sits on): Hi(-hy),Hi(hy),Lo(hy),Lo(-hy)
      faces.push(K.face3d(screen, [Hi(-hy), Hi(hy), Lo(hy), Lo(-hy)], V3.shade([138, 152, 164], surfN)));

      let out = K.paint3d(faces);
      // ground line + axis hints
      out += K.seg3d(screen, [-0.6, 0, 0], [B + 0.6, 0, 0], C.faint, 1.2);
      out += K.seg3d(screen, [0, 0, 0], [0, 0, H + 0.4], C.faint, 0.8, "3 3");
      out += K.text3d(screen, [B + 0.9, 0, 0], "x", C.faint, 10);
      out += K.text3d(screen, [0, 0, H + 0.7], "z", C.faint, 10);
      out += K.text3d(screen, [0.5, 0, 0.18], `θ ${Math.round(inclineAngle)}°`, C.mute, 10);

      // the block (small shaded cube around bc)
      out += K.paint3d(K.boxFaces3d(screen, bc[0] - cube / 2, bc[0] + cube / 2, bc[1] - cube / 2, bc[1] + cube / 2, bc[2] - cube / 2, bc[2] + cube / 2, sliding ? [255, 122, 107] : [84, 242, 178]));

      // force vectors + labels
      out += K.vec3d(screen, bc, wTip, C.gold, 2.6);
      out += K.text3d(screen, wTip, "W", C.gold, 11);
      out += K.vec3d(screen, bc, nTip, C.blue, 2.6);
      out += K.text3d(screen, nTip, "N", C.blue, 11);
      if (fld.friction > 0.01) {
        out += K.vec3d(screen, bc, fTip, C.teal, 2.6);
        out += K.text3d(screen, fTip, "f", C.teal, 11);
      }
      if (Math.abs(fld.netForce) > 0.01) out += K.vec3d(screen, bc, netTip, C.crimson, 2.4);
      return out;
    };

    const t = tasks.find((x) => x.id === task) || tasks[0];

    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: `a ${fmt(fld.accel)} m/s²` }, 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> · θ = {Math.round(inclineAngle)}° · {sliding ? `sliding, a = ${fmt(fld.accel)} m/s²` : "static — does not slide"}</span>
          <span>m = {fmt(mass)} kg · μ = {fmt(mu)}</span>
        </div>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${sliding ? C.crimson : C.line}`, color: sliding ? C.crimson : C.mute, fontSize: 12.5 }}>
          F_net = mg·sinθ − μN = {fmt(fld.netForce)} N · a = F_net/m = {fmt(fld.accel)} m/s². {sliding ? <>⚠ <K.T>block is sliding.</K.T></> : <K.T>Steepen θ or lower μ to make it slide.</K.T>}
        </div>
        <Slider label="mass m" value={mass} set={setMass} min={0.5} max={20} step={0.5} unit="kg" color={C.gold} onUp={() => event("adjusted", `Set m = ${fmt(mass)} kg`, { response: `a ${fmt(fld.accel)} m/s²` }, C.gold)} />
        <Slider label="incline angle θ" value={inclineAngle} set={setInclineAngle} min={0} max={60} step={1} unit="°" color={C.crimson} onUp={() => event("adjusted", `Set θ = ${Math.round(inclineAngle)}°`, { response: `a ${fmt(fld.accel)} m/s²` }, C.crimson)} />
        <Slider label="friction μ" value={mu} set={setMu} min={0} max={1} step={0.05} unit="" color={C.blue} onUp={() => event("adjusted", `Set μ = ${fmt(mu)}`, { response: `a ${fmt(fld.accel)} m/s²` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="net force" v={fld.netForce} d={2} unit="N" color={sliding ? C.crimson : C.teal} />
          <StatMeter label="acceleration" v={fld.accel} d={2} unit="m/s²" color={C.crimson} />
          <StatMeter label="normal N" v={fld.normal} d={1} unit="N" color={C.blue} />
          <StatMeter label="friction f" v={fld.friction} d={2} unit="N" color={C.teal} />
        </div>
      </>
    );
  }

  window.ForcesTutor = K.makeTutor(ForcesFig, { moduleLabel: "Forces bench", benchId: "forces" });
})();
