/* bb-scope.jsx — M5 of the free-build simulator.
   A live, rolling multi-trace oscilloscope fed by probe sample buffers, plus
   the hook that fills those buffers from a useMnaSim() result each frame.

   Unlike scope-screen.jsx (which sweeps an analytic t→v function), this draws
   real captured samples — so it shows whatever the transient engine actually
   did on the board: RC ramps, astable squares, an inductor's kick.

   Exports: BBScope, useProbeHistory  (names bbs*).
*/

const { useRef: bbsUseRef, useState: bbsUseState, useEffect: bbsUseEffect } = React;

/* pick a V/div so the busiest trace fills the screen without wasting range:
   target the four divisions above zero, snapping to a fine set of round steps
   (so a 9 V signal tops out at ~10 V, a 12 V signal at 12 V — not 20). */
function bbsNiceVDiv(peak) {
  const target = Math.max(0.1, peak) / 4;            // fill the 4 top divisions
  const pow = Math.pow(10, Math.floor(Math.log10(target)));
  const base = target / pow;
  const steps = [1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10];
  const step = steps.find(s => s >= base - 1e-9) || 10;
  return step * pow;
}

/* Accumulate one sample per probe every time the sim repaints. Buffers live in
   a ref (no churn); a tick state forces the redraw. Probes: [{id, hole, label,
   color}]. */
function useProbeHistory(probes, sim, opts = {}) {
  const len = opts.len || 240;
  const bufs = bbsUseRef({});
  const [, tick] = bbsUseState(0);
  bbsUseEffect(() => {
    const b = bufs.current;
    probes.forEach(p => { if (!b[p.id]) b[p.id] = []; });
    Object.keys(b).forEach(id => { if (!probes.find(p => p.id === id)) delete b[id]; });
    if (!sim.ready) return;
    probes.forEach(p => {
      const arr = b[p.id];
      arr.push(sim.probe(p.hole));
      if (arr.length > len) arr.shift();
    });
    tick(n => (n + 1) & 0xffff);
  }, [sim.nodeV, probes.map(p => p.id).join(",")]);
  return bufs.current;
}

/* The screen. traces: [{label, color, samples:[v…]}].
   Now with a labelled voltage axis (left gutter), a time axis, and live
   per-trace value readouts. Backward-compatible props. */
function BBScope({ traces = [], height = 230, tDivMs, footer, windowSec }) {
  const LM = 50, RM = 14, TM = 12, BM = 30;        // gutters for axis labels
  const W = 600, H = 300;
  const plotL = LM, plotR = W - RM, plotT = TM, plotB = H - BM;
  const plotW = plotR - plotL, plotH = plotB - plotT;
  const divW = plotW / 10, divH = plotH / 8;
  let peak = 0.6, maxLen = 0;
  traces.forEach(t => (t.samples || []).forEach(v => { if (isFinite(v)) peak = Math.max(peak, Math.abs(v)); }));
  traces.forEach(t => { maxLen = Math.max(maxLen, (t.samples || []).length); });
  const vDiv = bbsNiceVDiv(peak);
  const toY = (v) => Math.max(plotT + 2, Math.min(plotB - 2, (plotT + plotH / 2) - (v / vDiv) * divH));
  const poly = (samples) => {
    const n = samples.length;
    if (n < 2) return "";
    let s = "";
    for (let i = 0; i < n; i++) s += `${(plotL + (i / (n - 1)) * plotW).toFixed(1)},${toY(samples[i]).toFixed(1)} `;
    return s;
  };
  const fmtTick = (v) => (Math.abs(v) >= 10 ? v.toFixed(0) : Math.abs(v) >= 1 ? v.toFixed(1) : v.toFixed(2));
  const last = (s) => (s && s.length ? s[s.length - 1] : null);
  // time window: explicit, or estimate from sample count (sandbox samples ~30/s)
  const totalSec = windowSec || (maxLen ? +(maxLen / 30).toFixed(1) : null);
  return (
    <div style={{ background: "#0c1d14", border: "3px solid var(--ink)", borderRadius: 10, padding: "12px 12px 8px" }}>
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={height} style={{ display: "block" }}>
        {/* vertical (time) gridlines */}
        {Array.from({ length: 11 }, (_, i) => (
          <line key={"v" + i} x1={plotL + i * divW} y1={plotT} x2={plotL + i * divW} y2={plotB} stroke="#234134" strokeWidth={i === 0 || i === 10 ? 1.4 : 0.8} />
        ))}
        {/* horizontal (voltage) gridlines + left-axis labels */}
        {Array.from({ length: 9 }, (_, i) => {
          const y = plotT + i * divH;
          const vVal = (4 - i) * vDiv;
          const isZero = i === 4;
          return (
            <g key={"h" + i}>
              <line x1={plotL} y1={y} x2={plotR} y2={y} stroke={isZero ? "#3f6b53" : "#234134"} strokeWidth={isZero ? 1.6 : 0.8} />
              <text x={plotL - 8} y={y + 4} textAnchor="end" fontFamily="IBM Plex Mono, monospace" fontSize="13" fill={isZero ? "#9fcbb1" : "#5f8f72"}>{fmtTick(vVal)}</text>
            </g>
          );
        })}
        {/* axis unit labels */}
        <text x={14} y={plotT + plotH / 2} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="12.5" fill="#7fae90" transform={`rotate(-90 14 ${plotT + plotH / 2})`}>volts</text>
        <text x={plotL + plotW / 2} y={H - 8} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="12.5" fill="#7fae90">time →{totalSec ? `  (~${totalSec}s window)` : ""}</text>
        {/* traces */}
        {traces.map((t, i) => {
          const pts = poly(t.samples || []);
          if (!pts) return null;
          return (
            <g key={i}>
              <polyline points={pts} fill="none" stroke={t.color || "#7dff9a"} strokeWidth="5" opacity="0.20" />
              <polyline points={pts} fill="none" stroke={t.color || "#7dff9a"} strokeWidth="2.4" strokeLinejoin="round" />
            </g>
          );
        })}
      </svg>
      {/* legend with live values */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline",
                    fontFamily: "IBM Plex Mono, monospace", fontSize: 13, color: "#7fae90",
                    padding: "8px 4px 2px", gap: 12, flexWrap: "wrap" }}>
        <span style={{ color: "#9fcbb1" }}>{vDiv} V/div{tDivMs ? ` · ${tDivMs} ms/div` : ""}</span>
        <span style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
          {traces.map((t, i) => {
            const lv = last(t.samples);
            return (
              <span key={i} style={{ color: t.color || "#a8d4b6", display: "inline-flex", alignItems: "center", gap: 6 }}>
                <span style={{ width: 11, height: 3, borderRadius: 2, background: t.color || "#a8d4b6", display: "inline-block" }}></span>
                {t.label}{lv != null ? <b style={{ marginLeft: 5, fontWeight: 600 }}>{fmtTick(lv)} V</b> : null}
              </span>
            );
          })}
        </span>
        {footer && <span style={{ color: "#a8d4b6" }}>{footer}</span>}
      </div>
    </div>
  );
}

Object.assign(window, { BBScope, useProbeHistory, bbsNiceVDiv });
