/* osc555-bench.jsx — Level 2 · The Oscillator.
   A scoped, hands-on "wire the timing network around a seated 555" bench.
   The chip sits pre-wired for power (pins 1/4/8); the learner places only the
   five parts that set the behaviour — RA, RB, C, the THR↔TRG jumper, and the
   LED output — then powers up and scopes the real square wave. Reuses
   use555Sim + Scope555 from visuals555.jsx. All names ob5-/Osc555- prefixed. */

const { useState: _o5State } = React;

/* labelled connection pads on the mini-board */
const OB5_PADS = {
  vcc: { x: 430, y: 28,  label: "+", sub: "Vcc" },
  gnd: { x: 140, y: 332, label: "\u2212", sub: "Gnd" },
  p7:  { x: 430, y: 152, label: "7", sub: "DIS" },
  p6:  { x: 430, y: 182, label: "6", sub: "THR" },
  p2:  { x: 140, y: 152, label: "2", sub: "TRG" },
  p3:  { x: 140, y: 182, label: "3", sub: "OUT" },
};

/* the five parts the learner must place, and the edge each one belongs on */
const OB5_PARTS = [
  { id: "ra",  type: "resistor",  label: { adult: "Resistor R\u2090", kids: "Pipe A (resistor)" } },
  { id: "rb",  type: "resistor",  label: { adult: "Resistor R\u1d47", kids: "Pipe B (resistor)" } },
  { id: "c",   type: "capacitor", label: { adult: "Timing cap C", kids: "Bucket (capacitor)" } },
  { id: "jmp", type: "wire",      label: { adult: "Jumper wire", kids: "Jumper wire" } },
  { id: "out", type: "output",    label: { adult: "LED + resistor", kids: "Light + resistor" } },
];

const OB5_REQUIRED = [
  { type: "resistor",  a: "vcc", b: "p7", adult: "R\u2090: + rail \u2192 pin 7 (DIS)",        kids: "Pipe A: + rail \u2192 pin 7" },
  { type: "resistor",  a: "p7",  b: "p6", adult: "R\u1d47: pin 7 (DIS) \u2192 pin 6 (THR)",    kids: "Pipe B: pin 7 \u2192 pin 6" },
  { type: "capacitor", a: "p6",  b: "gnd", adult: "C: pin 6 (THR) \u2192 \u2212 rail",          kids: "Bucket: pin 6 \u2192 \u2212 rail" },
  { type: "wire",      a: "p6",  b: "p2", adult: "Jumper: pin 6 (THR) \u2194 pin 2 (TRG)",     kids: "Jumper: pin 6 \u2194 pin 2" },
  { type: "output",    a: "p3",  b: "gnd", adult: "Output: pin 3 (OUT) \u2192 LED \u2192 \u2212 rail", kids: "Light: pin 3 \u2192 light \u2192 \u2212 rail" },
];

const ob5Same = (p, a, b) => (p.a === a && p.b === b) || (p.a === b && p.b === a);

/* a placed connection drawn as an arc bowing away from the chip centre */
function ob5Conn(a, b, type, key, active, lit) {
  const A = OB5_PADS[a], B = OB5_PADS[b];
  const mx = (A.x + B.x) / 2, my = (A.y + B.y) / 2;
  let nx = mx - 285, ny = my - 170; const nl = Math.hypot(nx, ny) || 1; nx /= nl; ny /= nl;
  const cx = mx + nx * 26, cy = my + ny * 26;
  const gx = 0.25 * A.x + 0.5 * cx + 0.25 * B.x;
  const gy = 0.25 * A.y + 0.5 * cy + 0.25 * B.y;
  const col = active ? "var(--current)" : "var(--ink)";
  return (
    <g key={key} style={{ filter: active ? "drop-shadow(0 0 4px var(--current))" : "none" }}>
      <path d={`M${A.x} ${A.y} Q${cx} ${cy} ${B.x} ${B.y}`} fill="none" stroke={col} strokeWidth="2.4" strokeLinecap="round" />
      {type === "resistor" && (
        <g transform={`translate(${gx} ${gy})`}>
          <rect x="-15" y="-7" width="30" height="14" rx="2.5" fill="#d9c089" stroke="var(--ink)" strokeWidth="1.4" />
        </g>
      )}
      {type === "capacitor" && (
        <g transform={`translate(${gx} ${gy})`}>
          <rect x="-8" y="-11" width="16" height="22" rx="3" fill="#2b3a55" stroke="var(--ink)" strokeWidth="1.4" />
          <text x="0" y="3" textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="10" fill="#cdd">+</text>
        </g>
      )}
      {type === "output" && (
        <g transform={`translate(${gx} ${gy})`}>
          <rect x="-22" y="-6" width="18" height="12" rx="2" fill="#d9c089" stroke="var(--ink)" strokeWidth="1.2" />
          <circle cx="10" cy="0" r="9" fill={lit ? "var(--current)" : "var(--bg-deeper)"} stroke="var(--ink)" strokeWidth="1.6"
                  style={{ transition: "fill 80ms linear", filter: lit ? "drop-shadow(0 0 9px var(--current))" : "none" }} />
        </g>
      )}
    </g>
  );
}

function Osc555Bench({ kids }) {
  const [selected, setSelected] = _o5State(null);
  const [pendingPad, setPendingPad] = _o5State(null);
  const [placed, setPlaced] = _o5State([]);
  const [powered, setPowered] = _o5State(false);

  const edgeSat = (req) => placed.some(p => p.type === req.type && ob5Same(p, req.a, req.b));
  const valid = OB5_REQUIRED.every(edgeSat);

  // nominal parts → the same 1.38 Hz the chapter teaches
  const RA = 10, RB = 47, C = 10;
  const tHigh = 0.693e-3 * (RA + RB) * C, tLow = 0.693e-3 * RB * C, T = tHigh + tLow;
  const sim = use555Sim({ R: 5, C: 2, running: powered && valid });
  const lit = powered && valid && sim.high;

  const trayUsed = (id) => placed.some(p => p.partId === id);
  const tapTray = (id) => {
    if (powered || trayUsed(id)) return;
    setSelected(s => (s === id ? null : id));
    setPendingPad(null);
  };
  const tapPad = (padId) => {
    if (powered || !selected) return;
    if (pendingPad == null) { setPendingPad(padId); return; }
    if (pendingPad === padId) { setPendingPad(null); return; }
    const part = OB5_PARTS.find(t => t.id === selected);
    setPlaced(ps => [...ps, { partId: selected, type: part.type, a: pendingPad, b: padId }]);
    setSelected(null); setPendingPad(null);
  };
  const removeLast = () => { setPlaced(ps => ps.slice(0, -1)); setSelected(null); setPendingPad(null); };
  const reset = () => { setPlaced([]); setSelected(null); setPendingPad(null); setPowered(false); };

  const selPart = OB5_PARTS.find(t => t.id === selected);
  const T_ = (x) => (kids ? x.kids : x.adult);

  return (
    <div className="osc-bench">
      <div className="osc-bench-board">
        <svg viewBox="0 0 560 360" width="100%" className="osc-board-svg" role="img"
             aria-label="555 timer wiring bench">
          <rect x="6" y="14" width="548" height="332" rx="12" fill="var(--bg-card)" stroke="var(--rule-strong)" strokeWidth="1.5" />
          {/* power rails */}
          <line x1="24" y1="28" x2="536" y2="28" stroke="#c0392b" strokeWidth="3" />
          <line x1="24" y1="332" x2="536" y2="332" stroke="#2a6fb0" strokeWidth="3" />
          <text x="30" y="22" fontFamily="IBM Plex Mono, monospace" fontSize="11" fill="#c0392b">+ 9V</text>
          <text x="30" y="346" fontFamily="IBM Plex Mono, monospace" fontSize="11" fill="#2a6fb0">\u2212 GND</text>

          {/* pre-wired housekeeping pins (faint) */}
          <g opacity="0.5">
            <path d="M330 122 L360 122 L360 28" fill="none" stroke="#c0392b" strokeWidth="2" />
            <path d="M330 212 L398 212 L398 28" fill="none" stroke="#c0392b" strokeWidth="2" />
            <path d="M240 122 L206 122 L206 332" fill="none" stroke="#2a6fb0" strokeWidth="2" />
          </g>

          {/* placed connections (under the chip so crossings hide cleanly) */}
          {placed.map((p, i) => ob5Conn(p.a, p.b, p.type, "pc" + i, powered && valid, lit))}

          {/* the 555 DIP */}
          <g>
            <rect x="240" y="104" width="90" height="132" rx="6" fill="#1c1c1c" stroke="var(--ink)" strokeWidth="1.8" />
            <path d="M276 104 A 9 9 0 0 0 294 104" fill="none" stroke="#666" strokeWidth="1.5" />
            <text x="285" y="166" textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="22" fontWeight="700" fill="#e8e2d2">555</text>
            <text x="285" y="184" textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="10" fill="#888">TIMER</text>
            {/* pin stubs */}
            {[122, 152, 182, 212].map((y, i) => <line key={"L" + i} x1="224" y1={y} x2="240" y2={y} stroke="var(--ink)" strokeWidth="2.2" />)}
            {[122, 152, 182, 212].map((y, i) => <line key={"R" + i} x1="330" y1={y} x2="346" y2={y} stroke="var(--ink)" strokeWidth="2.2" />)}
            {/* pin numbers */}
            {["1", "2", "3", "4"].map((n, i) => <text key={"ln" + i} x="232" y={[122, 152, 182, 212][i] - 5} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="9" fill="var(--ink-faint)">{n}</text>)}
            {["8", "7", "6", "5"].map((n, i) => <text key={"rn" + i} x="338" y={[122, 152, 182, 212][i] - 5} textAnchor="middle" fontFamily="IBM Plex Mono, monospace" fontSize="9" fill="var(--ink-faint)">{n}</text>)}
          </g>

          {/* connection pads */}
          {Object.entries(OB5_PADS).map(([id, p]) => {
            const isPending = pendingPad === id;
            const armed = !!selected && !powered;
            return (
              <g key={id} onClick={() => tapPad(id)} style={{ cursor: armed ? "pointer" : "default" }}>
                <circle cx={p.x} cy={p.y} r={isPending ? 11 : 8}
                        fill={isPending ? "var(--current)" : "var(--bg-deeper)"}
                        stroke={armed ? "var(--current)" : "var(--rule-strong)"}
                        strokeWidth={armed ? 2.4 : 1.6}
                        style={{ transition: "all 120ms ease" }} />
                <text x={p.x} y={p.y + 4} textAnchor="middle" fontFamily="IBM Plex Mono, monospace"
                      fontSize="11" fontWeight="600" fill={isPending ? "var(--bg-card)" : "var(--ink-soft)"}>{p.label}</text>
                <text x={p.x} y={p.y + (p.y > 200 ? 22 : -14)} textAnchor="middle" fontFamily="IBM Plex Mono, monospace"
                      fontSize="10" fill="var(--ink-faint)">{p.sub}</text>
              </g>
            );
          })}
        </svg>

        {/* parts tray */}
        <div className="osc-tray">
          {OB5_PARTS.map(part => {
            const used = trayUsed(part.id);
            return (
              <button key={part.id} className={`osc-chip ${selected === part.id ? "sel" : ""} ${used ? "used" : ""}`}
                      disabled={used || powered} onClick={() => tapTray(part.id)}>
                {used ? "\u2713 " : ""}{T_(part.label)}
              </button>
            );
          })}
        </div>
        <div className="osc-bench-hint">
          {powered
            ? <span className="osc-hint-live">{valid ? (kids ? "It's alive \u2014 watch the scope!" : "Oscillating \u2014 scope it on the right.") : "\u2014"}</span>
            : selPart
              ? (pendingPad
                  ? <>Now tap the <b>second</b> pad to drop <b>{T_(selPart.label)}</b> across it.</>
                  : <>Tap the <b>two pads</b> that <b>{T_(selPart.label)}</b> should connect.</>)
              : (kids ? <>Pick a part below, then tap the two dots it bridges.</> : <>Select a part, then tap its two connection pads on the board.</>)}
        </div>
        <div className="osc-bench-actions">
          <button className="osc-btn" onClick={removeLast} disabled={!placed.length || powered}>Undo</button>
          <button className="osc-btn" onClick={reset} disabled={!placed.length && !powered}>Reset</button>
          <button className={`osc-btn power ${powered ? "on" : ""}`} onClick={() => setPowered(v => !v)} disabled={!valid && !powered}>
            {powered ? "\u25fc Power off" : "\u23fb Power up"}
          </button>
        </div>
      </div>

      <aside className="osc-bench-side">
        <div className="osc-check-title">{kids ? "The five connections" : "Timing network checklist"}</div>
        <ul className="osc-check">
          {OB5_REQUIRED.map((req, i) => {
            const ok = edgeSat(req);
            return (
              <li key={i} className={ok ? "ok" : ""}>
                <span className="osc-check-mark">{ok ? "\u2713" : "\u25cb"}</span>
                <span>{kids ? req.kids : req.adult}</span>
              </li>
            );
          })}
        </ul>
        <div className="osc-scope-slot">
          {powered && valid
            ? <Scope555 vcap={sim.level} out={sim.high} running={powered && valid} kids={kids} tHigh={tHigh} tLow={tLow} T={T} />
            : <div className="osc-scope-idle">
                <div className="osc-scope-idle-mark">\u223f</div>
                {kids ? "Wire all five, then power up to watch the wave." : "Complete the five connections and power up to scope the output."}
              </div>}
        </div>
      </aside>
    </div>
  );
}

Object.assign(window, { Osc555Bench });
