/* opticslab.jsx — OpticsTutor: the optical bench (single converging lens). Built on
   window.BenchKit (loaded first), so this file is just the FIGURE + computeFields().
   Defines window.OpticsTutor. computeFields() returns EXACTLY the fields
   server/benches/optics.js declares, since tasks are graded against this live state. */
(function () {
  const { useState, useMemo, useEffect, useRef } = React;
  const K = window.BenchKit, C = K.C, fmt = K.fmt, clamp = K.clamp, btn = K.btn, SVG_BG = K.SVG_BG;

  /* ── the physics: thin converging lens. EXACTLY the fields optics.js declares. ── */
  function computeFields(doCm, fCm) {
    const di = Math.abs(doCm - fCm) < 1e-6 ? Infinity : (doCm * fCm) / (doCm - fCm);
    const m = isFinite(di) ? -di / doCm : Infinity;
    const absMag = Math.abs(m);
    return {
      objectDistance: Math.round(doCm),
      focalLength: Math.round(fCm),
      imageDistance: isFinite(di) ? Math.round(di * 10) / 10 : 99999,
      magnification: isFinite(m) ? Math.round(m * 100) / 100 : 9999,
      absMag: isFinite(absMag) ? Math.round(absMag * 100) / 100 : 9999,
      isReal: isFinite(di) && di > 0,
      isVirtual: isFinite(di) && di < 0,
      isInverted: isFinite(m) && m < 0,
      isUpright: isFinite(m) && m > 0,
      isMagnified: isFinite(absMag) && absMag > 1.05,
      isDiminished: isFinite(absMag) && absMag < 0.95,
      insideF: doCm < fCm,
      beyond2F: doCm > 2 * fCm,
      atF: Math.abs(doCm - fCm) <= 2,
      at2F: Math.abs(doCm - 2 * fCm) <= 2,
    };
  }

  /* ── the figure: drag the object along the rail; tune f; rays + image update ── */
  function OpticsFig({ task, setTask, tasks, report, event, done }) {
    const [doCm, setDoCm] = useState(45);
    const [fCm, setFCm] = useState(15);
    const [drag, setDrag] = useState(false);
    const svgRef = useRef(null), dragV = useRef(45);
    const fld = useMemo(() => computeFields(doCm, fCm), [doCm, fCm]);

    useEffect(() => { report(fld); }, [doCm, fCm]); // eslint-disable-line

    // geometry: principal axis, lens at LENSX; SCALE px per cm.
    const VBW = 600, AY = 175, LENSX = 305, SCALE = 2.4, H = 46;
    const cmToX = (cm) => LENSX + cm * SCALE;           // +cm to the right of the lens
    const objX = LENSX - doCm * SCALE;                  // object sits left of the lens
    const di = fld.imageDistance === 99999 ? Infinity : fld.imageDistance;
    const m = fld.magnification === 9999 ? Infinity : fld.magnification;
    const imgX = isFinite(di) ? LENSX + di * SCALE : null; // real di>0 → right; virtual di<0 → left
    const objTipY = AY - H;
    const imgTipY = isFinite(m) ? AY - H * m : AY;         // m<0 inverted (below); m>0 upright (above)
    const Fr = cmToX(fCm), Fl = cmToX(-fCm), F2r = cmToX(2 * fCm), F2l = cmToX(-2 * fCm);
    const EDGE = 588;

    // ray through lens center (undeviated): obj tip → (LENSX, AY) → forward
    const slopeC = (AY - objTipY) / (LENSX - objX);
    const centerFwdY = AY + slopeC * (EDGE - LENSX);
    // ray parallel to axis then through far focus Fr: obj tip → (LENSX, objTipY) → through (Fr, AY)
    const slopeF = (AY - objTipY) / (Fr - LENSX);
    const parFwdY = objTipY + slopeF * (EDGE - LENSX);

    useEffect(() => {
      if (!drag) return;
      const move = (e) => {
        const rect = svgRef.current.getBoundingClientRect();
        const sx = (e.clientX - rect.left) / rect.width * VBW;
        const cm = clamp((LENSX - sx) / SCALE, 5, 110);
        dragV.current = Math.round(cm); setDoCm(Math.round(cm));
      };
      const up = () => { const f = computeFields(dragV.current, fCm); event("adjusted", `Moved object to ${dragV.current} cm`, { response: `di ${fmt(f.imageDistance)} cm, m ${fmt(f.magnification, 2)}` }, C.blue); setDrag(false); };
      window.addEventListener("pointermove", move); window.addEventListener("pointerup", up);
      return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
    }, [drag, fCm]); // eslint-disable-line

    const t = tasks.find((x) => x.id === task) || tasks[0];
    const imageKind = fld.atF ? "no image (rays parallel)" : fld.isReal ? `real · ${fld.isInverted ? "inverted" : "upright"} · ${fld.isMagnified ? "enlarged" : fld.isDiminished ? "reduced" : "same size"}` : `virtual · ${fld.isUpright ? "upright" : "inverted"} · ${fld.isMagnified ? "enlarged" : "reduced"}`;

    return (
      <>
        <K.TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        <svg ref={svgRef} viewBox="0 0 600 300" style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8, touchAction: "none" }}>
          <line x1="12" y1={AY} x2={EDGE} y2={AY} stroke={C.faint} strokeWidth="1.5" />
          {/* lens */}
          <ellipse cx={LENSX} cy={AY} rx="11" ry="74" fill={C.blue} opacity="0.12" stroke={C.blue} strokeWidth="2" />
          {/* focal & 2F markers */}
          {[[Fr, "F"], [Fl, "F"], [F2r, "2F"], [F2l, "2F"]].map(([x, lab], i) => (
            <g key={i}><circle cx={x} cy={AY} r="3" fill={C.gold} /><text x={x} y={AY + 16} textAnchor="middle" fontSize="9.5" fill={C.gold} fontFamily="monospace">{lab}</text></g>
          ))}
          {/* principal rays (solid forward; dashed back-extensions to a virtual image) */}
          <polyline points={`${objX},${objTipY} ${LENSX},${objTipY} ${EDGE},${parFwdY}`} fill="none" stroke={C.crimson} strokeWidth="1.5" opacity="0.9" />
          <polyline points={`${objX},${objTipY} ${EDGE},${centerFwdY}`} fill="none" stroke={C.teal} strokeWidth="1.5" opacity="0.9" />
          {fld.isVirtual && imgX != null && (
            <g stroke={C.mute} strokeWidth="1" strokeDasharray="4 3" opacity="0.8">
              <line x1={LENSX} y1={objTipY} x2={imgX} y2={imgTipY} /><line x1={LENSX} y1={AY} x2={imgX} y2={imgTipY} />
            </g>
          )}
          {/* object arrow (draggable) */}
          <g style={{ cursor: "grab" }} onPointerDown={() => setDrag(true)}>
            <line x1={objX} y1={AY} x2={objX} y2={objTipY} stroke={C.ink} strokeWidth="3" />
            <polygon points={`${objX},${objTipY} ${objX - 5},${objTipY + 10} ${objX + 5},${objTipY + 10}`} fill={C.ink} />
            <circle cx={objX} cy={objTipY} r="9" fill={C.amber} opacity={drag ? 0.9 : 0.55} />
            <text x={objX} y={AY + 16} textAnchor="middle" fontSize="9.5" fill={C.ink} fontFamily="monospace">do {Math.round(doCm)}</text>
          </g>
          {/* image arrow */}
          {imgX != null && Math.abs(imgX - LENSX) > 1 && (
            <g>
              <line x1={imgX} y1={AY} x2={imgX} y2={imgTipY} stroke={fld.isReal ? C.teal : C.mute} strokeWidth="3" strokeDasharray={fld.isReal ? "0" : "4 3"} />
              <polygon points={`${imgX},${imgTipY} ${imgX - 5},${imgTipY + (imgTipY > AY ? -10 : 10)} ${imgX + 5},${imgTipY + (imgTipY > AY ? -10 : 10)}`} fill={fld.isReal ? C.teal : C.mute} />
              <text x={imgX} y={fld.isReal ? AY + 16 : AY - 4} textAnchor="middle" fontSize="9.5" fill={fld.isReal ? C.teal : C.mute} fontFamily="monospace">{fld.isReal ? "real" : "virtual"}</text>
            </g>
          )}
        </svg>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${fld.atF ? C.crimson : C.line}`, color: fld.atF ? C.crimson : C.mute, fontSize: 12.5 }}>
          {fld.atF ? "Object at the focal point — refracted rays are parallel; the image is at infinity." : `Image: ${imageKind}.`} Drag the object along the rail.
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 12 }}>
          <span style={{ fontSize: 12, color: C.mute, width: 110 }}>focal length f</span>
          <input type="range" min={8} max={40} value={fCm} onChange={(e) => setFCm(parseInt(e.target.value))} onPointerUp={() => { const f = computeFields(doCm, fCm); event("adjusted", `Set focal length f=${fCm} cm`, { response: `di ${fmt(f.imageDistance)} cm` }, C.amber); }} style={{ flex: 1 }} />
          <span style={{ color: C.amber, width: 52, textAlign: "right" }}>{fCm} cm</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <K.StatMeter label="image dist di" v={fld.imageDistance === 99999 ? "∞" : fld.imageDistance} unit={fld.imageDistance === 99999 ? "" : "cm"} color={fld.isReal ? C.teal : C.mute} />
          <K.StatMeter label="magnification m" v={fld.magnification === 9999 ? "∞" : fld.magnification} d={2} color={C.blue} />
          <K.StatMeter label="|m|" v={fld.absMag === 9999 ? "∞" : fld.absMag} d={2} color={C.amber} />
          <K.StatMeter label="image" v={fld.atF ? "—" : fld.isReal ? "real" : "virtual"} color={fld.isReal ? C.teal : C.crimson} />
        </div>
      </>
    );
  }

  window.OpticsTutor = K.makeTutor(OpticsFig, { moduleLabel: "Optical bench", benchId: "optics" });
})();
