/* loop-scene7.jsx — Chapter 7 diode loop (the one-way valve).
   Like Ch5's switch, but the "switch" is thrown by DIRECTION:
   - forward bias -> charges circulate, paying a small fixed TOLL at the diode
     (the ~0.7V drop) on top of the big drop at the LED load; the LED glows.
   - reverse bias -> the diode blocks; the whole loop freezes (grey), LED dark.
   Spectrum charges (blue->amber->red) as in Ch2-6. Driven by props (v, forward).
   Names fl7-/LoopScene7 prefixed. */

function fl7HexRgb(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 fl7MixHex(a, b, t) { const A = fl7HexRgb(a), B = fl7HexRgb(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)})`; }
function fl7Smooth(t) { t = Math.max(0, Math.min(1, t)); return t * t * (3 - 2 * t); }
const FL7_BLUE = "#2f6db0", FL7_AMBER = "#e0a32e", FL7_RED = "#c0392b";
function fl7Spectrum(e) { e = Math.max(0, Math.min(1, e)); return e >= 0.5 ? fl7MixHex(FL7_AMBER, FL7_BLUE, (e - 0.5) / 0.5) : fl7MixHex(FL7_RED, FL7_AMBER, e / 0.5); }

const FL7 = (() => {
  const X0 = 130, X1 = 690, Y0 = 110, Y1 = 350, rc = 64;
  const arc = (cx, cy, a0, a1, k) => { const o = []; for (let i = 0; i <= k; i++) { const a = a0 + (a1 - a0) * (i / k); o.push([cx + rc * Math.cos(a), cy + rc * Math.sin(a)]); } return o; };
  let pts = [[(X0 + X1) / 2, Y1], [X0 + rc, Y1]];
  pts.push(...arc(X0 + rc, Y1 - rc, Math.PI / 2, Math.PI, 12), [X0, Y0 + rc]);
  pts.push(...arc(X0 + rc, Y0 + rc, Math.PI, 1.5 * Math.PI, 12), [X1 - rc, Y0]);
  pts.push(...arc(X1 - rc, Y0 + rc, -Math.PI / 2, 0, 12), [X1, Y1 - rc]);
  pts.push(...arc(X1 - rc, Y1 - rc, 0, Math.PI / 2, 12), [(X0 + X1) / 2, Y1]);
  const dense = [pts[0].slice()];
  for (let i = 1; i < pts.length; i++) { const a = pts[i - 1], b = pts[i], d = Math.hypot(b[0] - a[0], b[1] - a[1]), k = Math.max(1, Math.round(d / 6)); for (let j = 1; j <= k; j++) { const t = j / k; dense.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]); } }
  pts = dense;
  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 P = cum[n - 1];
  function at(s) { s = ((s % P) + P) % P; let i = 1; while (i < n && cum[i] < s) i++; const a = pts[i - 1], b = pts[i] || pts[0], seg = cum[i] - cum[i - 1] || 1, f = (s - cum[i - 1]) / seg; let tx = b[0] - a[0], ty = b[1] - a[1]; const tl = Math.hypot(tx, ty) || 1; return { x: a[0] + (b[0] - a[0]) * f, y: a[1] + (b[1] - a[1]) * f, tx: tx / tl, ty: ty / tl }; }
  function sAt(px, py) { let best = 0, bd = Infinity; for (let i = 0; i < n; i++) { const d = (pts[i][0] - px) ** 2 + (pts[i][1] - py) ** 2; if (d < bd) { bd = d; best = cum[i]; } } return best; }
  return { P, at, sAt, pts, cum, X0, X1, Y0, Y1 };
})();

function LoopScene7({ v, forward, kids }) {
  const cv = React.useRef(null);
  const propRef = React.useRef({ v, forward, kids });
  propRef.current = { v, forward, 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" };
    const hWide = 16, rad = 4.6;

    // diode top-left, LED (load) top-right, battery bottom
    const diodeS = FL7.sAt(300, FL7.Y0);
    const ledS = FL7.sAt(500, FL7.Y0);
    const battS = FL7.sAt((FL7.X0 + FL7.X1) / 2, FL7.Y1);
    const tollHalf = 26, ledHalf = 34, battHalf = 46;
    const diodePt = FL7.at(diodeS), ledPt = FL7.at(ledS), battPt = FL7.at(battS);

    function winFrac(ph, phc, half) { if (ph < phc - half) return 0; if (ph > phc + half) return 1; return fl7Smooth((ph - (phc - half)) / (2 * half)); }
    function near(s, sc, half) { const d = ((s - (sc - half)) % FL7.P + FL7.P) % FL7.P; return d <= 2 * half ? d / (2 * half) : -1; }

    const N = 150, cell = 2 * rad;
    const ps = [];
    for (let i = 0; i < N; i++) ps.push({ s: Math.random() * FL7.P, u: (Math.random() * 2 - 1) * hWide * 0.55, e: Math.random() });
    const cache = new Array(N);
    let blockAmt = propRef.current.forward ? 0 : 1, ledGlow = 0;
    let raf;

    function frame() {
      const { v: V, forward: FWD, kids: K } = propRef.current;
      blockAmt += ((FWD ? 0 : 1) - blockAmt) * 0.16;
      const flowing = FWD && blockAmt < 0.5 && V > 0.7;     // needs to clear the ~0.7V toll
      const base = flowing ? 0.42 * Math.max(0, V - 0.7) / 1.5 : 0;
      ledGlow += ((flowing ? 1 : 0) - ledGlow) * 0.12;

      // energy windows (phase measured from battery): small toll at diode, big drop at LED
      const ph_d = ((diodeS - battS) % FL7.P + FL7.P) % FL7.P;
      const ph_l = ((ledS - battS) % FL7.P + FL7.P) % FL7.P;
      const tollDrop = 0.18;                                 // ~the 0.7V toll, as a fraction

      for (let i = 0; i < N; i++) { const p = ps[i], a = FL7.at(p.s), nx = -a.ty, ny = a.tx; cache[i] = { x: a.x + nx * p.u, y: a.y + ny * p.u, tx: a.tx, ty: a.ty, nx, ny }; }
      if (flowing) {
        const grid = new Map();
        for (let i = 0; i < N; i++) { const c = cache[i], k = Math.floor(c.x / cell) + "," + Math.floor(c.y / cell); let g = grid.get(k); if (!g) { g = []; grid.set(k, g); } g.push(i); }
        for (let i = 0; i < N; i++) { const ci = cache[i], gx = Math.floor(ci.x / cell), gy = Math.floor(ci.y / cell); for (let ox = -1; ox <= 1; ox++) for (let oy = -1; oy <= 1; oy++) { const g = grid.get((gx + ox) + "," + (gy + oy)); if (!g) continue; for (const j of g) { if (j <= i) continue; const cj = cache[j]; let dx = cj.x - ci.x, dy = cj.y - ci.y, d2 = dx * dx + dy * dy, mn = 2 * rad; if (d2 < mn * mn && d2 > 1e-4) { const d = Math.sqrt(d2), ov = (mn - d) / d * 0.5; dx *= ov; dy *= ov; const pi = ps[i], pj = ps[j]; pi.s -= dx * ci.tx + dy * ci.ty; pi.u -= dx * ci.nx + dy * ci.ny; pj.s += dx * cj.tx + dy * cj.ty; pj.u += dx * cj.nx + dy * cj.ny; } } } }
        for (let i = 0; i < N; i++) {
          const p = ps[i];
          p.s = (p.s + base) % FL7.P;
          const maxU = hWide - rad;
          if (p.u > maxU) p.u = maxU; if (p.u < -maxU) p.u = -maxU;
          const ph = ((p.s - battS) % FL7.P + FL7.P) % FL7.P;
          let e = 1 - tollDrop * winFrac(ph, ph_d, tollHalf) - (1 - tollDrop) * winFrac(ph, ph_l, ledHalf);
          const nb = near(p.s, battS, battHalf); if (nb >= 0) e = nb;
          p.e = e;
        }
      }

      // ---- draw ----
      ctx.clearRect(0, 0, VBW, VBH);
      const out = [], inn = [];
      for (let i = 0; i < FL7.pts.length; i++) { const a = FL7.pts[Math.max(0, i - 1)], c = FL7.pts[Math.min(FL7.pts.length - 1, i + 1)]; let tx = c[0] - a[0], ty = c[1] - a[1]; const tl = Math.hypot(tx, ty) || 1; tx /= tl; ty /= tl; const nx = -ty, ny = tx; out.push([FL7.pts[i][0] + nx * hWide, FL7.pts[i][1] + ny * hWide]); inn.push([FL7.pts[i][0] - nx * hWide, FL7.pts[i][1] - ny * hWide]); }
      ctx.beginPath(); out.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); for (let i = inn.length - 1; i >= 0; i--) ctx.lineTo(inn[i][0], inn[i][1]); ctx.closePath(); ctx.fillStyle = C.deep; ctx.fill();

      // particles (frozen grey when blocked)
      let spot = null;
      for (let i = 0; i < N; i++) { const c = cache[i], p = ps[i]; ctx.beginPath(); ctx.arc(c.x, c.y, rad, 0, 7); ctx.fillStyle = flowing ? fl7Spectrum(p.e) : C.faint; ctx.fill(); if (i === 0) spot = { x: c.x, y: c.y, e: p.e }; }

      // channel outline
      ctx.lineWidth = 2; ctx.strokeStyle = C.ink; ctx.lineJoin = "round";
      ctx.beginPath(); out.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); ctx.stroke();
      ctx.beginPath(); inn.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); ctx.stroke();

      // ── the diode: triangle + cathode bar. Points in the ALLOWED flow dir. ──
      // On the top wire flow goes left→right when forward; reverse tries right→left and is blocked.
      const dca = FL7.at(diodeS), tA = 15;
      ctx.save(); ctx.translate(dca.x, dca.y);
      const blocked = blockAmt > 0.5;
      ctx.fillStyle = blocked ? FL7_RED : fl7MixHex(FL7_BLUE, C.ink, 0.2);
      ctx.strokeStyle = blocked ? FL7_RED : C.ink; ctx.lineWidth = 2.5; ctx.lineCap = "round";
      ctx.beginPath(); ctx.moveTo(-tA, -tA); ctx.lineTo(tA, 0); ctx.lineTo(-tA, tA); ctx.closePath(); ctx.fill();   // triangle → right
      ctx.beginPath(); ctx.moveTo(tA, -tA); ctx.lineTo(tA, tA); ctx.stroke();                                       // cathode bar
      // blocked: a red "no" slash across it
      if (blocked) { ctx.strokeStyle = FL7_RED; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(-tA - 6, tA + 6); ctx.lineTo(tA + 6, -tA - 6); ctx.stroke(); }
      ctx.restore();
      ctx.fillStyle = blocked ? FL7_RED : C.faint; ctx.font = "600 10px 'IBM Plex Mono', monospace"; ctx.textAlign = "center";
      ctx.fillText(K ? (blocked ? "shut" : "open") : (blocked ? "REVERSE · blocked" : "FORWARD · conducts"), diodePt.x, diodePt.y - hWide - 14);

      // ── the LED load: glows when forward ──
      const lg = ledGlow;
      if (lg > 0.05) { const g = ctx.createRadialGradient(ledPt.x, ledPt.y, 2, ledPt.x, ledPt.y, 60); g.addColorStop(0, `rgba(224,163,46,${0.7 * lg})`); g.addColorStop(1, "rgba(0,0,0,0)"); ctx.fillStyle = g; ctx.fillRect(ledPt.x - 64, ledPt.y - 64, 128, 128); }
      ctx.beginPath(); ctx.arc(ledPt.x, ledPt.y, 11, 0, 7); ctx.fillStyle = lg > 0.4 ? FL7_AMBER : C.deep; ctx.fill(); ctx.lineWidth = 2; ctx.strokeStyle = C.ink; ctx.stroke();
      ctx.fillStyle = C.faint; ctx.font = "600 10px 'IBM Plex Mono', monospace"; ctx.fillText(K ? "the light" : "LED", ledPt.x, ledPt.y - hWide - 14);

      // battery
      ctx.strokeStyle = C.ink; ctx.lineCap = "round";
      ctx.lineWidth = 3.4; ctx.beginPath(); ctx.moveTo(battPt.x - 30, battPt.y - 9); ctx.lineTo(battPt.x + 30, battPt.y - 9); ctx.stroke();
      ctx.lineWidth = 3.4; ctx.beginPath(); ctx.moveTo(battPt.x - 16, battPt.y + 6); ctx.lineTo(battPt.x + 16, battPt.y + 6); ctx.stroke();
      ctx.fillStyle = FL7_BLUE; ctx.font = "600 15px 'IBM Plex Mono', monospace"; ctx.fillText("+", battPt.x - 44, battPt.y - 3);
      ctx.fillStyle = C.ink; ctx.fillText("\u2013", battPt.x - 44, battPt.y + 15);

      // centre readout
      const mx = VBW / 2, my = VBH / 2;
      ctx.textAlign = "center";
      ctx.fillStyle = C.faint; ctx.font = "600 11px 'IBM Plex Mono', monospace";
      ctx.fillText(K ? "WHICH WAY?" : "DIODE STATE", mx, my - 20);
      ctx.fillStyle = flowing ? FL7_BLUE : FL7_RED; ctx.font = "600 32px 'Newsreader', serif";
      ctx.fillText(flowing ? (K ? "FLOWS" : "FORWARD") : (K ? "BLOCKED" : "REVERSE"), mx, my + 14);
      ctx.fillStyle = C.faint; ctx.font = "500 11px 'IBM Plex Mono', monospace";
      ctx.fillText(flowing ? (K ? "pays a small toll, then lights the bulb" : "conducts · ~0.7V toll dropped at the diode") : (K ? "the valve shuts \u2014 nothing moves" : "one-way valve blocks reverse flow"), mx, my + 34);

      if (flowing && spot) { ctx.beginPath(); ctx.arc(spot.x, spot.y, rad + 3.4, 0, 7); ctx.lineWidth = 2.2; ctx.strokeStyle = C.ink; ctx.stroke(); ctx.beginPath(); ctx.arc(spot.x, spot.y, rad, 0, 7); ctx.fillStyle = fl7Spectrum(spot.e); ctx.fill(); }

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

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

Object.assign(window, { LoopScene7 });
