/* serieslab.jsx — SeriesTutor: the series convergence bench. Built on window.BenchKit
   (harness) + window.BenchFields (shared field math), so this file is just the FIGURE.
   computeFields = window.BenchFields.series — the SAME function server/benches/series.js
   calls, so client and server fields are identical by construction. The learner sets the
   common ratio r, first term a and number of terms N of Σ a·rⁿ; a staircase of partial
   sums climbs toward (or runs away from) the limit a/(1−r). */
(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.series(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 SeriesFig({ task, setTask, tasks, report, event, done }) {
    const [r, setR] = useState(0.5);
    const [a, setA] = useState(1);
    const [N, setN] = useState(5);
    const fld = useMemo(() => compute({ r, a, N }), [r, a, N]);
    useEffect(() => { report(fld); }, [r, a, N]); // eslint-disable-line

    // partial sums S_1..S_N of the geometric series Σ a·rⁿ.
    const Ni = Math.max(1, Math.round(N));
    const sums = [];
    for (let k = 1; k <= Ni; k++) {
      const Sk = Math.abs(r - 1) < 1e-9 ? a * k : a * (1 - Math.pow(r, k)) / (1 - r);
      sums.push(Sk);
    }

    // plot window: bars across the columns 1..Ni, y auto-scaled to the data + the limit.
    const X0 = 60, X1 = 560, Y0 = 30, Y1 = 270;
    const vals = sums.slice();
    if (fld.converges) vals.push(fld.limitSum);
    let lo = Math.min(0, ...vals), hi = Math.max(0, ...vals);
    if (hi - lo < 1e-6) { hi = lo + 1; }
    const pad = (hi - lo) * 0.12 || 1;
    lo -= pad; hi += pad;
    const sy = (y) => Y1 - ((y - lo) / (hi - lo)) * (Y1 - Y0);
    const colW = (X1 - X0) / Ni;
    const bw = Math.max(2, colW * 0.7);
    const zeroY = sy(0);

    const conv = fld.converges;
    const barColor = conv ? C.teal : C.crimson;
    const t = tasks.find((x) => x.id === task) || tasks[0];

    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 }}>
          {/* axes */}
          <line x1={X0} y1={zeroY} x2={X1} y2={zeroY} stroke={C.faint} strokeWidth="1" />
          <line x1={X0} y1={Y0} x2={X0} y2={Y1} stroke={C.faint} strokeWidth="1" />
          {/* limit line when it converges */}
          {conv && <line x1={X0} y1={sy(fld.limitSum)} x2={X1} y2={sy(fld.limitSum)} stroke={C.gold} strokeWidth="1.5" strokeDasharray="5 4" />}
          {conv && <text x={X1 - 6} y={sy(fld.limitSum) - 5} fill={C.gold} fontSize="11" fontFamily="monospace" textAnchor="end">limit = {fmt(fld.limitSum)}</text>}
          {/* the staircase of partial-sum bars */}
          {sums.map((Sk, i) => {
            const cx = X0 + (i + 0.5) * colW;
            const yTop = sy(Math.max(0, Sk)), yBot = sy(Math.min(0, Sk));
            return <rect key={i} x={cx - bw / 2} y={yTop} width={bw} height={Math.max(1, yBot - yTop)} fill={barColor} opacity={0.55 + 0.4 * (i + 1) / Ni} />;
          })}
          <text x={X0 + 4} y={Y0 + 14} fill={C.mute} fontSize="11" fontFamily="monospace">Σ {fmt(a)}·({fmt(r)})ⁿ · S_{Ni} = {fmt(fld.partialSum)}</text>
          <text x={X1 - 6} y={Y1 - 6} fill={C.faint} fontSize="10" fontFamily="monospace" textAnchor="end">N → {Ni}</text>
        </svg>
        <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${conv ? C.teal : C.crimson}`, color: conv ? C.teal : C.crimson, fontSize: 12.5 }}>
          {conv
            ? <><K.T>Converges</K.T> (|r| &lt; 1): S = a/(1−r) = {fmt(fld.limitSum)}. <K.T>partial sum</K.T> S_{Ni} = {fmt(fld.partialSum)}, <K.T>tail error</K.T> {fmt(fld.tailError)}.</>
            : <><K.T>Diverges</K.T> (|r| ≥ 1): the partial sums run away — no finite limit. S_{Ni} = {fmt(fld.partialSum)}.</>}
        </div>
        <Slider label="common ratio r" value={r} set={setR} min={-1.2} max={1.2} step={0.05} unit="" color={C.teal} onUp={() => event("adjusted", `Set r = ${fmt(r)}`, { response: `|r| ${fmt(fld.ratioTestValue)} · ${conv ? "converges" : "diverges"}` }, C.teal)} />
        <Slider label="first term a" value={a} set={setA} min={-3} max={3} step={0.5} unit="" color={C.gold} onUp={() => event("adjusted", `Set a = ${fmt(a)}`, { response: `limit ${fmt(fld.limitSum)}` }, C.gold)} />
        <Slider label="terms N" value={N} set={(v) => setN(Math.round(v))} min={1} max={40} step={1} unit="" color={C.blue} onUp={() => event("adjusted", `Set N = ${Math.round(N)}`, { response: `S_N ${fmt(fld.partialSum)}` }, C.blue)} />
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8, marginTop: 12 }}>
          <StatMeter label="partial sum S_N" v={fld.partialSum} d={3} color={barColor} />
          <StatMeter label="limit a/(1−r)" v={fld.limitSum} d={3} color={conv ? C.gold : C.crimson} />
          <StatMeter label="ratio test |r|" v={fld.ratioTestValue} d={2} color={C.amber} />
          <StatMeter label="tail error" v={fld.tailError} d={3} color={fld.isClose ? C.teal : C.mute} />
        </div>
      </>
    );
  }

  window.SeriesTutor = K.makeTutor(SeriesFig, { moduleLabel: "Series Convergence bench", benchId: "series" });
})();
