/* osc555-free.jsx — M7 of the free-build simulator.
   A live 555 astable bench: the user turns R_A, R_B and C knobs and watches a
   REAL 555 (the engine's IC555 latch + comparators) blink an LED and draw its
   square wave on the live scope. The frequency formula updates with real-world
   component values; the on-screen blink speed scales with R·C so the relationship
   is felt, not just read.

   Reuses window.useMnaSim + window.BBScope + window.useProbeHistory.
   Names oc555*. Exports Osc555Free.
*/

const { useState: o5UseState, useMemo: o5UseMemo } = React;

/* The engine blinks at period ≈ 1.55·(Ra+2Rb)·farads (deterministic). We map the
   chosen component values to a WATCHABLE display period that scales with the TRUE
   astable period by LOG COMPRESSION — never a hard clamp. A hard [0.5,5] s clamp
   saturated the moment any knob went up (C=50 and C=100 blinked identically), so
   the rate looked dead across half the range. Compression keeps the visible blink
   monotonic in R·C everywhere (~0.6–2.5 s), default ~1.3 s. The on-screen
   frequency readout still shows the TRUE textbook value (1.44/((Ra+2Rb)·C)). */
const OC555_KAPPA = 1.55;                       // engine period per (Ra+2Rb)·farads

function oc555Parts(RaK, RbK, CuF) {
  const Ra = RaK * 1000, Rb = RbK * 1000;
  const tTrue = ((Ra + 2 * Rb) * (CuF * 1e-6)) / 1.44;        // true astable period (s)
  const dispPeriod = 0.45 + 1.3 * Math.log10(1 + tTrue / 0.06); // watchable, monotonic
  const farads = dispPeriod / (OC555_KAPPA * (Ra + 2 * Rb));   // back-solve for the engine
  return [
    { type: "battery", a: "VCC", b: "GND", volts: 9 },
    { type: "ic555", pins: { vcc: "VCC", gnd: "GND", out: "OUT", dis: "DIS", thr: "THR", trig: "THR", rst: "VCC", ctrl: null } },
    { type: "resistor", a: "VCC", b: "DIS", R: Ra },     // R_A: VCC → discharge
    { type: "resistor", a: "DIS", b: "THR", R: Rb },     // R_B: discharge → threshold
    { type: "capacitor", a: "THR", b: "GND", C: 0, farads },  // timing cap (explicit farads)
    { type: "resistor", a: "OUT", b: "LEDM", R: 330 },   // LED limiter
    { type: "led", a: "LEDM", b: "GND" },                // the blinker
  ];
}

function Oc555Knob({ label, sub, value, min, max, step, onChange, fmt }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 5 }}>
        <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 13, color: "var(--ink)" }}>{label}</span>
        <span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 13, color: "var(--water)" }}>{fmt(value)}</span>
      </div>
      <input type="range" min={min} max={max} step={step} value={value}
             onChange={e => onChange(+e.target.value)}
             style={{ width: "100%", accentColor: "var(--current)" }} />
      {sub && <div style={{ fontSize: 11.5, color: "var(--ink-faint)", marginTop: 3 }}>{sub}</div>}
    </div>
  );
}

function Osc555Free({ kids }) {
  const [RaK, setRaK] = o5UseState(10);
  const [RbK, setRbK] = o5UseState(10);
  const [CuF, setCuF] = o5UseState(10);
  const [running, setRunning] = o5UseState(true);

  const parts = o5UseMemo(() => oc555Parts(RaK, RbK, CuF), [RaK, RbK, CuF]);
  const sim = window.useMnaSim
    ? window.useMnaSim(parts, {}, { active: running, dt: 1 / 240 })
    : { ready: false, brightness: {}, probe: () => 0, nodeV: [], reset: () => {} };

  const probes = o5UseMemo(() => ([
    { id: "out", hole: "OUT", label: kids ? "chip output" : "OUT (pin 3)", color: "#4aa3e0" },
    { id: "cap", hole: "THR", label: kids ? "bucket fill" : "cap (pin 2/6)", color: "#7dff9a" },
  ]), [kids]);
  const bufs = window.useProbeHistory ? window.useProbeHistory(probes, sim, { len: 240 }) : {};

  const ledBright = sim.brightness ? (sim.brightness[6] || 0) : 0;

  // real-world frequency from the textbook formula
  const Ra = RaK * 1000, Rb = RbK * 1000, C = CuF * 1e-6;
  const freq = 1.44 / ((Ra + 2 * Rb) * C);
  const duty = (Ra + Rb) / (Ra + 2 * Rb) * 100;

  return (
    <div className="oc555-grid" style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr) 260px", gap: 22, alignItems: "start" }}>
      {/* left: the live blinker + schematic + scope */}
      <div>
        <div style={{ display: "flex", alignItems: "center", gap: 18, marginBottom: 16 }}>
          <div style={{
            width: 64, height: 64, borderRadius: "50%",
            background: `radial-gradient(circle at 38% 34%, rgba(255,225,170,${0.25 + 0.75 * ledBright}), rgba(255,150,46,${0.15 + 0.85 * ledBright}))`,
            boxShadow: ledBright > 0.05 ? `0 0 ${10 + 38 * ledBright}px rgba(255,170,60,${0.7 * ledBright})` : "none",
            border: "2px solid var(--rule-strong)", transition: "box-shadow .04s linear",
          }} />
          <div>
            <div style={{ fontFamily: "Newsreader, serif", fontSize: 22, color: "var(--ink)" }}>
              {kids ? "Your blinker" : "555 astable · live"}
            </div>
            <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 13, color: "var(--ink-soft)" }}>
              {sim.ready ? (kids ? "blinking by itself!" : "free-running — no input, just R·C") : "starting…"}
            </div>
          </div>
          <button onClick={() => setRunning(r => !r)}
                  style={{ marginLeft: "auto", cursor: "pointer", fontFamily: "IBM Plex Mono, monospace", fontSize: 12,
                           padding: "8px 16px", borderRadius: 8, border: "1.5px solid var(--rule-strong)",
                           background: running ? "var(--current)" : "transparent", color: running ? "#fff" : "var(--ink-soft)" }}>
            {running ? "⏸ pause" : "▶ run"}
          </button>
        </div>
        {window.Osc555Schem &&
          <div style={{ marginBottom: 16 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>{kids ? "your circuit · watch the charges" : "the live circuit · current flows where the dots go"}</div>
            <window.Osc555Schem sim={sim} kids={kids} />
          </div>}
        {window.BBScope &&
          <div className="eyebrow" style={{ marginBottom: 6 }}>{kids ? "the scope · voltage over time" : "scope · voltage over time"}</div>}
        {window.BBScope &&
          <window.BBScope height={170}
            traces={probes.map(p => ({ label: p.label, color: p.color, samples: bufs[p.id] || [] }))} />}
        <div style={{ fontSize: 12.5, color: "var(--ink-faint)", marginTop: 10, lineHeight: 1.5 }}>
          {kids
            ? "Blue line = the chip flipping on and off. Green line = the bucket filling up and tipping over. That tipping is what makes the beat!"
            : "Blue = OUT (pin 3) flipping rail-to-rail. Green = the cap ramping between ⅓ and ⅔ of supply — the comparators trip at those lines and toggle the latch."}
        </div>
      </div>

      {/* right: the knobs + the formula */}
      <div style={{ background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 12, padding: "18px 18px 8px" }}>
        <div className="eyebrow" style={{ marginBottom: 14 }}>{kids ? "turn the knobs" : "timing network"}</div>
        <Oc555Knob label="R_A" sub={kids ? "" : "VCC → discharge"} value={RaK} min={1} max={100} step={1}
                   onChange={setRaK} fmt={v => v + " kΩ"} />
        <Oc555Knob label="R_B" sub={kids ? "" : "discharge → threshold"} value={RbK} min={1} max={100} step={1}
                   onChange={setRbK} fmt={v => v + " kΩ"} />
        <Oc555Knob label="C" sub={kids ? "the bucket" : "timing capacitor"} value={CuF} min={1} max={100} step={1}
                   onChange={setCuF} fmt={v => v + " µF"} />
        <div style={{ borderTop: "1px solid var(--rule)", marginTop: 6, paddingTop: 12 }}>
          {kids ? (
            <div style={{ fontSize: 13, color: "var(--ink-soft)", lineHeight: 1.5 }}>
              Bigger knobs = <b style={{ color: "var(--water)" }}>slower</b> blink.
              Smaller = <b style={{ color: "var(--water)" }}>faster</b>!
            </div>
          ) : (
            <>
              <div style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: 12.5, color: "var(--ink-soft)", marginBottom: 6 }}>
                f = 1.44 / ((R_A + 2·R_B)·C)
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", fontFamily: "IBM Plex Mono, monospace", fontSize: 14 }}>
                <span style={{ color: "var(--ink-faint)" }}>frequency</span>
                <span style={{ color: "var(--current)" }}>{freq < 1 ? (freq * 1000).toFixed(0) + " mHz" : freq.toFixed(2) + " Hz"}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", fontFamily: "IBM Plex Mono, monospace", fontSize: 14, marginTop: 3 }}>
                <span style={{ color: "var(--ink-faint)" }}>duty</span>
                <span style={{ color: "var(--ink)" }}>{duty.toFixed(0)} %</span>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { Osc555Free });
