/* springslab.jsx — SpringsTutor: the mass-on-a-spring bench. Built on window.BenchKit
   (harness) + window.BenchFields (shared field math), so this file is just the FIGURE.
   computeFields = window.BenchFields.springs — the SAME function server/benches/springs.js
   calls, so client and server fields are identical by construction. */
(function () {
  const { useState, useMemo, useEffect } = React;
  const K = window.BenchKit, C = K.C, SVG_BG = K.SVG_BG, fmt = K.fmt;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter;
  const compute = (s) => window.BenchFields.springs(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 }}>{label}</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 SpringsFig({ task, setTask, tasks, report, event, done }) {
    const [springK, setSpringK] = useState(200);
    const [displacement, setDisplacement] = useState(0.1);
    const [mass, setMass] = useState(0.5);
    const fld = useMemo(() => compute({ springK, displacement, mass }), [springK, displacement, mass]);
    useEffect(() => { report(fld); }, [springK, displacement, mass]); // eslint-disable-line

    // Layout: spring anchored at the top, hangs down; displacement x (m, −0.3..0.3) stretches/
    // compresses the coil about its equilibrium length. The mass block hangs below.
    const CX = 300, TOP = 40;            // anchor x, ceiling y
    const EQ_LEN = 120;                  // equilibrium spring length (px)
    const springLen = EQ_LEN + (displacement / 0.3) * 70; // stretched (x>0) longer, compressed shorter
    const coils = 7;
    const massY = TOP + springLen;       // top of the mass block
    const blockH = 26 + Math.min(48, mass * 12); // block grows with mass
    const blockW = 40 + Math.min(60, mass * 14);

    // zigzag coil path between anchor and the block.
    let d = `M ${CX} ${TOP}`;
    const segs = coils * 2;
    for (let i = 1; i <= segs; i++) {
      const y = TOP + (springLen / segs) * i;
      const x = i === segs ? CX : CX + (i % 2 === 0 ? -16 : 16);
      d += ` L ${x} ${y}`;
    }

    const t = tasks.find((x) => x.id === task) || tasks[0];
    const force = fld.force;
    const stretched = displacement > 0.005;
    const compressed = displacement < -0.005;
    // restoring force arrow: points opposite the displacement (toward equilibrium).
    const arrLen = Math.min(70, Math.abs(force) / 5);
    const blockMidY = massY + blockH / 2;
    const arrDir = force >= 0 ? -1 : 1; // force>0 (compressed) pushes block up(-); force<0 pulls up too visually toward eq
    // For a hanging mass: x>0 means stretched downward → restoring force points UP; x<0 → points DOWN.
    const fUp = displacement > 0;
    const arrY2 = blockMidY + (fUp ? -arrLen : arrLen);

    return (
      <>
        <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        <svg viewBox="0 0 600 300" style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8 }}>
          {/* ceiling / anchor */}
          <line x1={CX - 60} y1={TOP} x2={CX + 60} y2={TOP} stroke={C.faint} strokeWidth="3" />
          {[...Array(6)].map((_, i) => <line key={i} x1={CX - 54 + i * 20} y1={TOP} x2={CX - 64 + i * 20} y2={TOP - 10} stroke={C.faint} strokeWidth="1.5" />)}
          {/* equilibrium reference line */}
          <line x1={CX - 90} y1={TOP + EQ_LEN} x2={CX + 90} y2={TOP + EQ_LEN} stroke={C.faint} strokeDasharray="4 4" strokeWidth="1" opacity="0.6" />
          <text x={CX + 96} y={TOP + EQ_LEN + 4} fill={C.mute} fontSize="10" fontFamily="monospace">equilibrium</text>
          {/* spring coil */}
          <path d={d} fill="none" stroke={stretched ? C.crimson : (compressed ? C.blue : C.teal)} strokeWidth="2.5" />
          {/* mass block */}
          <rect x={CX - blockW / 2} y={massY} width={blockW} height={blockH} rx="4" fill={C.gold} opacity="0.85" stroke={C.faint} strokeWidth="1" />
          <text x={CX} y={massY + blockH / 2 + 4} textAnchor="middle" fill={C.ink} fontSize="11" fontFamily="monospace">{fmt(mass)} kg</text>
          {/* restoring-force arrow */}
          {Math.abs(force) > 0.5 && (
            <g stroke={C.crimson} strokeWidth="2.5" fill={C.crimson}>
              <line x1={CX + blockW / 2 + 18} y1={blockMidY} x2={CX + blockW / 2 + 18} y2={arrY2} />
              <polygon points={`${CX + blockW / 2 + 18},${arrY2} ${CX + blockW / 2 + 13},${arrY2 + (fUp ? 9 : -9)} ${CX + blockW / 2 + 23},${arrY2 + (fUp ? 9 : -9)}`} />
              <text x={CX + blockW / 2 + 30} y={(blockMidY + arrY2) / 2} fill={C.crimson} fontSize="11" fontFamily="monospace" stroke="none">F = {fmt(force)} N</text>
            </g>
          )}
          {/* labels */}
          <text x={20} y={TOP + 14} fill={C.mute} fontSize="11" fontFamily="monospace">k = {Math.round(springK)} N/m · x = {fmt(displacement)} m · m = {fmt(mass)} kg</text>
          <text x={20} y={TOP + 32} fill={C.ink} fontSize="12" fontFamily="monospace">PE = {fmt(fld.peSpring)} J · T = {fmt(fld.period)} s</text>
        </svg>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${C.line}`, color: C.mute, fontSize: 12.5 }}>
          F = −kx = {fmt(force)} N · PE = ½kx² = {fmt(fld.peSpring)} J · T = 2π√(m/k) = {fmt(fld.period)} s. {fld.atRest ? "At equilibrium (x ≈ 0)." : "Displace x, stiffen k, or change m and watch the restoring force and period respond."}
        </div>
        <Slider label="spring constant k" value={springK} set={setSpringK} min={10} max={1000} step={10} unit="N/m" color={C.teal} onUp={() => event("adjusted", `Set k = ${Math.round(springK)} N/m`, { response: `F ${fmt(fld.force)} N` }, C.teal)} />
        <Slider label="displacement x" value={displacement} set={setDisplacement} min={-0.3} max={0.3} step={0.01} unit="m" color={C.crimson} onUp={() => event("adjusted", `Set x = ${fmt(displacement)} m`, { response: `F ${fmt(fld.force)} N` }, C.crimson)} />
        <Slider label="mass m" value={mass} set={setMass} min={0.1} max={5} step={0.1} unit="kg" color={C.gold} onUp={() => event("adjusted", `Set m = ${fmt(mass)} kg`, { response: `T ${fmt(fld.period)} s` }, C.gold)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="force F" v={fld.force} d={1} unit="N" color={C.crimson} />
          <StatMeter label="elastic PE" v={fld.peSpring} unit="J" color={C.blue} />
          <StatMeter label="period T" v={fld.period} unit="s" color={C.teal} />
          <StatMeter label="frequency f" v={fld.freq} unit="Hz" color={C.amber} />
        </div>
      </>
    );
  }

  window.SpringsTutor = K.makeTutor(SpringsFig, { moduleLabel: "Springs bench", benchId: "springs" });
})();
