/* loop-scene6.jsx — Chapter 6 transistor loop (NPN). The big idea drawn:
   a SMALL base trickle (left) opens a GATE that lets a BIG collector flow
   (top -> bottom) through. Collector throughput = β x base (capped by supply),
   so the gate's openness and the big stream both scale with the little one.
   Base=0 -> gate shut -> no collector flow. Spectrum charges as in Ch2-5.
   Driven by props (vCC, vBase, beta). Names fl6-/LoopScene6 prefixed. */

function fl6HexRgb(h) { h = (h || "").trim().replace("#", ""); if (h.length === 3) h = h.split("").map(c => c + c).join(""); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; }
function fl6MixHex(a, b, t) { const A = fl6HexRgb(a), B = fl6HexRgb(b); return `rgb(${Math.round(A[0] + (B[0] - A[0]) * t)},${Math.round(A[1] + (B[1] - A[1]) * t)},${Math.round(A[2] + (B[2] - A[2]) * t)})`; }
const FL6_BLUE = "#2f6db0", FL6_AMBER = "#e0a32e", FL6_RED = "#c0392b";
function fl6Spectrum(e) { e = Math.max(0, Math.min(1, e)); return e >= 0.5 ? fl6MixHex(FL6_AMBER, FL6_BLUE, (e - 0.5) / 0.5) : fl6MixHex(FL6_RED, FL6_AMBER, e / 0.5); }

function fl6Path(pts) {
  const n = pts.length, cum = [0];
  for (let i = 1; i < n; i++) cum.push(cum[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]));
  const L = cum[n - 1];
  function at(d) { d = ((d % L) + L) % L; let i = 1; while (i < n && cum[i] < d) i++; const a = pts[i - 1], b = pts[i] || pts[0], seg = cum[i] - cum[i - 1] || 1, f = (d - cum[i - 1]) / seg; return { x: a[0] + (b[0] - a[0]) * f, y: a[1] + (b[1] - a[1]) * f }; }
  return { L, at, pts };
}

function LoopScene6({ vCC, vBase, beta, kids }) {
  const cv = React.useRef(null);
  const propRef = React.useRef({ vCC, vBase, beta, kids });
  propRef.current = { vCC, vBase, beta, kids };

  React.useEffect(() => {
    const canvas = cv.current, ctx = canvas.getContext("2d");
    const VBW = 820, VBH = 460;
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let scale = 1;
    function fit() { const cw = canvas.clientWidth || VBW; scale = cw / VBW; canvas.width = Math.round(cw * dpr); canvas.height = Math.round(VBH * scale * dpr); ctx.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0); }
    fit(); window.addEventListener("resize", fit);

    const cs = getComputedStyle(document.body);
    const C = { ink: cs.getPropertyValue("--ink").trim() || "#2c2a26", faint: cs.getPropertyValue("--ink-faint").trim() || "#a79f90", card: cs.getPropertyValue("--bg-card").trim() || "#faf6ec", deep: cs.getPropertyValue("--bg-deeper").trim() || "#e6dfca" };

    // transistor body (the gate) centred
    const Tx = 470, Ty = 235;                       // gate centre
    const ccY = 70, gndY = 400;                     // supply rails (top +, bottom −)
    const colX = Tx;                                // collector vertical line
    const baseInX = 120;                            // base input node (left)
    const bodyHalf = 48;                            // half the transistor body height
    const emJunc = Ty + bodyHalf;                   // where base + collector MERGE (emitter)
    // collector path: VCC -> load -> collector into body -> emitter junction -> SHARED emitter leg -> GND -> up right -> VCC
    const collPts = [[colX, ccY], [colX, Ty - bodyHalf], [colX, emJunc], [colX, gndY], [690, gndY], [690, ccY], [colX, ccY]];
    const collPath = fl6Path(collPts);
    const loadY = (ccY + (Ty - bodyHalf)) / 2;      // load resistor on collector feed
    // base path: base node -> Rb -> base into body side -> emitter junction (MERGE) -> SHARED emitter leg -> GND -> back left -> up
    const basePts = [[baseInX, Ty], [Tx - 26, Ty], [colX, emJunc], [colX, gndY], [baseInX, gndY], [baseInX, Ty]];
    const basePath = fl6Path(basePts);
    const baseRX = (baseInX + (Tx - 26)) / 2;

    const radC = 4.4, radB = 3.4;
    const NC = 46, NB = 22;
    const coll = [], base = [];
    for (let i = 0; i < NC; i++) coll.push({ d: (i / NC) * collPath.L });
    for (let i = 0; i < NB; i++) base.push({ d: (i / NB) * basePath.L });

    let gateOpen = 0;     // animated 0..1
    let raf;

    function frame() {
      const { vCC: VCC, vBase: VB, beta: B, kids: K } = propRef.current;
      const baseFrac = Math.max(0, Math.min(1, (VB - 0.6) / 1.2));
      const iB = baseFrac;                                   // 0..1 (normalised base current)
      const iCraw = iB * (B / 100);                          // β scaling, normalised
      const cap = 1.0;                                       // supply ceiling (normalised)
      const iC = Math.min(cap, iCraw);
      const saturated = iCraw > cap && iB > 0.02;
      gateOpen += (baseFrac - gateOpen) * 0.16;

      const baseSpd = iB > 0.01 ? 0.7 + iB * 1.3 : 0;
      const collSpd = iC > 0.01 ? 1.0 + iC * 3.2 : 0;

      for (const p of base) p.d = (p.d + baseSpd) % basePath.L;
      for (const p of coll) p.d = (p.d + collSpd) % collPath.L;

      ctx.clearRect(0, 0, VBW, VBH);

      // supply rails
      ctx.strokeStyle = C.faint; ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(80, ccY); ctx.lineTo(740, ccY); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(80, gndY); ctx.lineTo(740, gndY); ctx.stroke();
      ctx.fillStyle = FL6_BLUE; ctx.font = "600 12px 'IBM Plex Mono', monospace"; ctx.textAlign = "left";
      ctx.fillText(K ? "+  power" : "+V  (" + VCC.toFixed(0) + "V)", 84, ccY - 8);
      ctx.fillStyle = C.faint; ctx.fillText(K ? "−  ground" : "GND", 84, gndY + 18);

      // wires (collector loop + base loop)
      ctx.strokeStyle = C.deep; ctx.lineWidth = 10; ctx.lineJoin = "round"; ctx.lineCap = "round";
      const wire = (pp) => { ctx.beginPath(); pp.pts.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); ctx.stroke(); };
      wire(collPath); ctx.lineWidth = 6; wire(basePath);
      // the SHARED emitter leg — drawn fat to show both currents combine here
      ctx.strokeStyle = C.deep; ctx.lineWidth = 14; ctx.lineCap = "round";
      ctx.beginPath(); ctx.moveTo(colX, emJunc); ctx.lineTo(colX, gndY); ctx.stroke();

      // collector charges (only above the gate flow into it; render whole loop while iC>0)
      if (collSpd > 0) for (const p of coll) { const a = collPath.at(p.d); ctx.beginPath(); ctx.arc(a.x, a.y, radC, 0, 7); const e = a.y < Ty ? 1 : Math.max(0, 1 - (a.y - Ty) / 150); ctx.fillStyle = fl6Spectrum(e); ctx.fill(); }
      // base charges (small, always a trickle when on)
      if (baseSpd > 0) for (const p of base) { const a = basePath.at(p.d); ctx.beginPath(); ctx.arc(a.x, a.y, radB, 0, 7); ctx.fillStyle = fl6Spectrum(0.85); ctx.fill(); }

      // load resistor on the collector feed
      ctx.save(); ctx.translate(colX, loadY); ctx.fillStyle = "#d9c089"; ctx.strokeStyle = C.ink; ctx.lineWidth = 1.6;
      ctx.beginPath(); ctx.rect(-13, -22, 26, 44); ctx.fill(); ctx.stroke(); ctx.restore();
      ctx.fillStyle = C.ink; ctx.font = "600 11px 'IBM Plex Mono', monospace"; ctx.textAlign = "left"; ctx.fillText(K ? "bulb" : "load", colX + 18, loadY);
      // base resistor
      ctx.save(); ctx.translate(baseRX, Ty); ctx.fillStyle = "#d9c089"; ctx.strokeStyle = C.ink; ctx.lineWidth = 1.4;
      ctx.beginPath(); ctx.rect(-18, -9, 36, 18); ctx.fill(); ctx.stroke(); ctx.restore();
      ctx.fillStyle = C.ink; ctx.font = "600 10px 'IBM Plex Mono', monospace"; ctx.textAlign = "center"; ctx.fillText(K ? "small in" : "Rb", baseRX, Ty - 16);

      // ── the transistor GATE: a valve that opens with the base ──
      const bodyW = 52, bodyH = 96;
      ctx.fillStyle = C.card; ctx.strokeStyle = C.ink; ctx.lineWidth = 2;
      ctx.beginPath(); ctx.roundRect(Tx - bodyW / 2, Ty - bodyH / 2, bodyW, bodyH, 8); ctx.fill(); ctx.stroke();
      // gate plate inside that slides down as it opens
      const slot = bodyH - 26, plateY = Ty - slot / 2 + (1 - gateOpen) * slot;
      ctx.fillStyle = gateOpen > 0.04 ? fl6MixHex(FL6_RED, FL6_BLUE, gateOpen) : C.faint;
      ctx.fillRect(Tx - bodyW / 2 + 5, plateY - 4, bodyW - 10, 8);
      // base arrow into the body
      ctx.strokeStyle = C.ink; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(Tx - bodyW / 2 - 12, Ty); ctx.lineTo(Tx - bodyW / 2, Ty); ctx.stroke();
      ctx.fillStyle = C.faint; ctx.font = "600 9px 'IBM Plex Mono', monospace"; ctx.textAlign = "center";
      ctx.fillText("B", Tx - bodyW / 2 - 18, Ty + 3);
      ctx.fillText("C", Tx + bodyW / 2 + 8, Ty - bodyH / 2 + 12);
      ctx.fillText("E", Tx + bodyW / 2 + 8, Ty + bodyH / 2 - 6);
      ctx.fillStyle = C.faint; ctx.font = "600 10px 'IBM Plex Mono', monospace";
      ctx.fillText(K ? "the gate" : "NPN", Tx, Ty + bodyH / 2 + 16);

      // ── the shared emitter: I_E = I_B + I_C ──
      ctx.strokeStyle = C.faint; ctx.lineWidth = 1.4; ctx.setLineDash([2, 4]);
      ctx.beginPath(); ctx.moveTo(colX + 14, (emJunc + gndY) / 2); ctx.lineTo(colX + 54, (emJunc + gndY) / 2); ctx.stroke(); ctx.setLineDash([]);
      ctx.fillStyle = C.ink; ctx.font = "600 11px 'IBM Plex Mono', monospace"; ctx.textAlign = "left";
      ctx.fillText(K ? "both flows" : "I_E = I_B + I_C", colX + 58, (emJunc + gndY) / 2 - 4);
      ctx.fillStyle = C.faint; ctx.font = "500 9.5px 'IBM Plex Mono', monospace";
      ctx.fillText(K ? "join here, then split" : "shared emitter leg", colX + 58, (emJunc + gndY) / 2 + 9);

      // ── centre readout: tiny in → big out (×β) ──
      const mx = 230, my = 150;
      ctx.textAlign = "center";
      ctx.fillStyle = C.faint; ctx.font = "600 10px 'IBM Plex Mono', monospace";
      ctx.fillText(K ? "GATE OPEN" : "GATE", mx, my - 18);
      ctx.fillStyle = fl6MixHex(FL6_RED, FL6_BLUE, gateOpen); ctx.font = "600 30px 'Newsreader', serif";
      ctx.fillText(Math.round(baseFrac * 100) + "%", mx, my + 12);
      ctx.fillStyle = C.faint; ctx.font = "500 10px 'IBM Plex Mono', monospace";
      ctx.fillText(K
        ? (baseFrac < 0.02 ? "shut — no big flow" : saturated ? "wide open · maxed out" : "small in × " + B + " = big out")
        : (baseFrac < 0.02 ? "I_B ≈ 0 → I_C = 0 (cut off)" : saturated ? "saturated · I_C capped by supply" : "I_C = β·I_B = " + B + "·I_B"), mx, my + 30);

      // small-vs-big stream caption near the two channels
      ctx.fillStyle = C.faint; ctx.font = "600 9.5px 'IBM Plex Mono', monospace"; ctx.textAlign = "center";
      ctx.fillText(K ? "tiny trickle" : "small I_B", baseRX, Ty + 30);
      ctx.fillText(K ? "big gush" : "big I_C", 690, (ccY + gndY) / 2);

      raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);
    return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", fit); };
  }, []);

  return <canvas ref={cv} className="loop6-canvas" style={{ width: "100%", display: "block" }}></canvas>;
}

Object.assign(window, { LoopScene6 });
