/* scatterlab.jsx — ScatterTutor: the scatter / regression bench. Built on window.BenchKit
   (loaded first), so this file is just the FIGURE + computeFields(). Defines window.ScatterTutor.
   computeFields() returns EXACTLY the fields server/benches/scatter.js declares. */
(function () {
  const { useState, useMemo, useEffect, useRef } = React;
  const K = window.BenchKit, C = K.C, fmt = K.fmt, clamp = K.clamp, btn = K.btn;
  const r2 = (x, d) => Math.round(x * Math.pow(10, d)) / Math.pow(10, d);

  /* the physics: ordinary least squares. EXACTLY the fields scatter.js declares. */
  function computeFields(points) {
    const n = points.length;
    const xm = points.reduce((a, p) => a + p.x, 0) / n;
    const ym = points.reduce((a, p) => a + p.y, 0) / n;
    let Sxx = 0, Syy = 0, Sxy = 0;
    points.forEach((p) => { const dx = p.x - xm, dy = p.y - ym; Sxx += dx * dx; Syy += dy * dy; Sxy += dx * dy; });
    const slope = Sxx > 0 ? Sxy / Sxx : 0;
    const intercept = ym - slope * xm;
    const r = (Sxx > 0 && Syy > 0) ? Sxy / Math.sqrt(Sxx * Syy) : 0;
    let sse = 0; points.forEach((p) => { const e = p.y - (intercept + slope * p.x); sse += e * e; });
    const residSD = n > 2 ? Math.sqrt(sse / (n - 2)) : 0;
    let maxLev = 0; points.forEach((p) => { const h = 1 / n + (Sxx > 0 ? (p.x - xm) ** 2 / Sxx : 0); if (h > maxLev) maxLev = h; });
    const xs = points.map((p) => p.x);
    return {
      n, r: r2(r, 3), r2: r2(r * r, 3), slope: r2(slope, 3), intercept: r2(intercept, 1),
      residSD: r2(residSD, 2), maxLeverage: r2(maxLev, 3), hasHighLeverage: maxLev > 4 / n,
      slopeSign: slope > 0.01 ? 1 : slope < -0.01 ? -1 : 0, xRange: r2(Math.max(...xs) - Math.min(...xs), 1),
    };
  }

  const INIT = [[12, 22], [22, 30], [30, 28], [40, 41], [50, 46], [58, 52], [68, 60], [78, 66], [86, 75], [94, 80]].map(([x, y]) => ({ x, y }));

  function ScatterFig({ task, setTask, tasks, report, event, done }) {
    const [pts, setPts] = useState(INIT.map((p) => ({ ...p })));
    const [drag, setDrag] = useState(null);
    const [showResid, setShowResid] = useState(true);
    const svgRef = useRef(null), dragP = useRef(null);
    const fld = useMemo(() => computeFields(pts), [pts]);

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

    const VBW = 600, X0 = 55, X1 = 560, YB = 250, YT = 30; // data 0..100 both axes
    const px = (x) => X0 + (x / 100) * (X1 - X0);
    const py = (y) => YB - (y / 100) * (YB - YT);
    const toData = (clientX, clientY) => {
      const rect = svgRef.current.getBoundingClientRect();
      const sx = (clientX - rect.left) / rect.width * VBW;
      const sy = (clientY - rect.top) / rect.height * (VBW * (280 / 600)); // viewBox is 600x280
      return { x: clamp((sx - X0) / (X1 - X0) * 100, 0, 100), y: clamp((YB - sy) / (YB - YT) * 100, 0, 100) };
    };

    useEffect(() => {
      if (drag == null) return;
      const move = (e) => { const d = toData(e.clientX, e.clientY); dragP.current = d; setPts((a) => a.map((p, i) => (i === drag ? { x: Math.round(d.x), y: Math.round(d.y) } : p))); };
      const up = () => { const f = computeFields(pts); event("adjusted", "Moved a point", { response: `r ${fmt(f.r, 2)}, R² ${fmt(f.r2, 2)}` }, C.blue); setDrag(null); };
      window.addEventListener("pointermove", move); window.addEventListener("pointerup", up);
      return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
    }, [drag, pts]); // eslint-disable-line

    const yAt = (x) => fld.intercept + fld.slope * x;
    const lineY0 = clamp(yAt(0), -50, 150), lineY100 = clamp(yAt(100), -50, 150);
    const t = tasks.find((x) => x.id === task) || tasks[0];
    const strength = Math.abs(fld.r) >= 0.8 ? "strong" : Math.abs(fld.r) >= 0.5 ? "moderate" : Math.abs(fld.r) >= 0.2 ? "weak" : "no";
    const dir = fld.slopeSign > 0 ? "positive" : fld.slopeSign < 0 ? "negative" : "flat";

    return (
      <>
        <K.TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
        <svg ref={svgRef} viewBox="0 0 600 280" style={{ width: "100%", background: K.SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8, touchAction: "none" }}>
          <line x1={X0} y1={YT} x2={X0} y2={YB} stroke={C.faint} strokeWidth="1.5" />
          <line x1={X0} y1={YB} x2={X1} y2={YB} stroke={C.faint} strokeWidth="1.5" />
          <text x={X0 - 6} y={YT + 6} textAnchor="end" fontSize="10" fill={C.mute} fontFamily="monospace">y</text>
          <text x={X1} y={YB + 16} textAnchor="end" fontSize="10" fill={C.mute} fontFamily="monospace">x</text>
          {/* least-squares line */}
          <line x1={px(0)} y1={py(lineY0)} x2={px(100)} y2={py(lineY100)} stroke={C.crimson} strokeWidth="2" />
          {/* residual segments */}
          {showResid && pts.map((p, i) => <line key={`r${i}`} x1={px(p.x)} y1={py(p.y)} x2={px(p.x)} y2={py(yAt(p.x))} stroke={C.teal} strokeWidth="1" strokeDasharray="3 2" opacity="0.55" />)}
          {/* points */}
          {pts.map((p, i) => <circle key={i} cx={px(p.x)} cy={py(p.y)} r={drag === i ? 8 : 6} fill={drag === i ? C.amber : C.blue} stroke={K.SVG_BG} strokeWidth="1.5" style={{ cursor: "grab" }} onPointerDown={() => setDrag(i)} />)}
        </svg>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${C.line}`, color: C.mute, fontSize: 12.5 }}>
          {`${strength} ${dir} association · r = ${fmt(fld.r, 2)}, ŷ = ${fmt(fld.intercept, 1)} ${fld.slope >= 0 ? "+" : "−"} ${fmt(Math.abs(fld.slope), 2)}·x`}{fld.hasHighLeverage ? " · ⚠ high-leverage point" : ""}. Drag any point.
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }}>
          <button onClick={() => setPts((a) => [...a, { x: 50, y: 50 }])} style={btn(C.panel2, C.ink)}>+ point</button>
          <button onClick={() => setPts((a) => (a.length > 4 ? a.slice(0, -1) : a))} style={btn(C.panel2, C.ink)}>− point</button>
          <button onClick={() => setShowResid((s) => !s)} style={{ ...btn(C.panel2, C.mute), border: `1px solid ${C.line}` }}>{showResid ? "hide" : "show"} residuals</button>
          <span style={{ alignSelf: "center", fontSize: 11.5, color: C.faint }}>n = {fld.n}</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <K.StatMeter label="r" v={fld.r} d={2} color={C.crimson} />
          <K.StatMeter label="R²" v={fld.r2} d={2} color={C.teal} />
          <K.StatMeter label="slope" v={fld.slope} d={2} color={C.blue} />
          <K.StatMeter label="resid SD" v={fld.residSD} d={2} color={C.amber} />
        </div>
      </>
    );
  }

  window.ScatterTutor = K.makeTutor(ScatterFig, { moduleLabel: "Scatter / regression bench", benchId: "scatter" });
})();
