/* global React, PRESETS, sampleWithTemp, applyTemperature, tailAfterTemperature, fmtPct */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* =====================================================================
   SECTION 3 — THE RIGGED CASINO
   Just three quiet numbers + a small visual of the asymmetry between
   training data and a single die roll. No interactivity — this is
   the "exhibit placard" moment.
   ===================================================================== */

function CasinoSection() {
  return (
    <div className="casino">
      <div className="inner">
        <div className="section-head">
          <div className="meta" style={{ color: "rgba(242,237,227,0.5)", borderColor: "rgba(242,237,227,0.3)" }}>
            <span className="num">№ 03</span>
            The Rigged Casino
          </div>
          <h2>The weights are tuned by trillions of <span className="italic">examples.</span></h2>
        </div>

        <p className="col-narrow" style={{ fontSize: 19 }}>
          A standard die was built by a factory. The loaded die was built by
          reading nearly every sentence humans have written down. The
          imbalance is the entire product.
        </p>

        <div className="grid-cards">
          <div className="gcard">
            <div className="num">~15<small>T</small></div>
            <div className="lbl">Tokens of training text</div>
            <div className="desc">
              Roughly the entire public internet, every digitized book,
              every transcript, fed in so the die's center of gravity
              can settle into the shape of human language.
            </div>
          </div>
          <div className="gcard">
            <div className="num">~100<small>K</small></div>
            <div className="lbl">Sides on the die</div>
            <div className="desc">
              The vocabulary. Every word and word-fragment the model can
              possibly emit. Each face carries a weight that shifts with
              every preceding token.
            </div>
          </div>
          <div className="gcard">
            <div className="num">10<small>¹²</small></div>
            <div className="lbl">Numbers that tune the weights</div>
            <div className="desc">
              The model's parameters: the mathematical residue of all
              that reading. They are the casino's rigging.
            </div>
          </div>
        </div>

        <p className="col-narrow" style={{ marginTop: 48, color: "rgba(242,237,227,0.7)", fontStyle: "italic", fontFamily: "var(--serif-display)", fontSize: 26, lineHeight: 1.3, maxWidth: "28ch" }}>
          A heavily loaded die is <span style={{ color: "var(--gold)" }}>all</span> the system needs to sound like it knows the answer.
        </p>
      </div>
    </div>
  );
}

/* =====================================================================
   SECTION 4 — THE TEMPERATURE KNOB
   A dial driving a stream of live rolls. As T rises, the spread of
   outcomes widens, and the "tail" — off-script invention — becomes
   visible.
   ===================================================================== */

function TemperatureSection() {
  const [T, setT] = useState(1.0);
  const [presetId, setPresetId] = useState("paris");
  const preset = PRESETS.find(p => p.id === presetId);
  const [stream, setStream] = useState([]); // recent rolls
  const intervalRef = useRef(null);

  // Compute the warped distribution + tail mass for the readouts.
  const warped = useMemo(() => applyTemperature(preset.dist, T), [preset, T]);
  const tailMass = useMemo(() => tailAfterTemperature(preset.dist, T), [preset, T]);

  // Auto-roll: emit a new sample every 700ms while the section is mounted.
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setStream(prev => {
        const result = sampleWithTemp(preset.dist, T);
        const isTopToken = result.kind === "known" && result.index === 0;
        const isFabrication = result.kind === "tail";
        const entry = {
          token: result.token,
          isTop: isTopToken,
          isFab: isFabrication,
          t: T.toFixed(2),
          id: Math.random().toString(36).slice(2, 9),
        };
        return [entry, ...prev].slice(0, 6);
      });
    }, 750);
    return () => clearInterval(intervalRef.current);
  }, [preset, T]);

  // Reset stream when the preset changes.
  useEffect(() => { setStream([]); }, [presetId]);

  const mood = T < 0.05 ? "Deterministic"
             : T < 0.4  ? "Cautious"
             : T < 0.9  ? "Steady"
             : T < 1.2  ? "Fluent"
             : T < 1.6  ? "Loose"
             : "Chaotic";

  // Convert the temperature value (0 → 1.5) to a dial angle (-135° → +135°).
  const angle = -135 + (T / 1.5) * 270;

  return (
    <div>
      <div style={{ marginBottom: 24, display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 16 }}>
        <div>
          <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 6 }}>
            Prompt
          </div>
          <div style={{ fontFamily: "var(--serif-display)", fontSize: 24, lineHeight: 1.2 }}>
            <span style={{ color: "var(--ink)" }}>“{preset.prompt}</span>
            <span style={{ color: "var(--vermilion)" }}> ___</span>
            <span style={{ color: "var(--ink)" }}>”</span>
          </div>
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          {PRESETS.map(p => (
            <button
              key={p.id}
              onClick={() => setPresetId(p.id)}
              style={{
                fontFamily: "var(--mono)",
                fontSize: 11,
                padding: "5px 10px",
                background: p.id === presetId ? "var(--ink)" : "transparent",
                color: p.id === presetId ? "var(--paper)" : "var(--ink-soft)",
                border: "1px solid " + (p.id === presetId ? "var(--ink)" : "var(--hairline)"),
                borderRadius: 100,
                cursor: "pointer",
              }}
            >
              {p.id}
            </button>
          ))}
        </div>
      </div>

      <div className="temp-stage">
        <div>
          <div className="dial">
            <svg className="dial-svg" viewBox="-150 -150 300 300">
              {/* outer arc, tick marks, and labels */}
              <defs>
                <linearGradient id="dialGrad" x1="0" y1="-1" x2="0" y2="1">
                  <stop offset="0%" stopColor="var(--blue)" />
                  <stop offset="50%" stopColor="var(--gold)" />
                  <stop offset="100%" stopColor="var(--vermilion)" />
                </linearGradient>
              </defs>
              {/* arc track */}
              <path
                d={describeArc(0, 0, 120, -135, 135)}
                fill="none"
                stroke="var(--hairline)"
                strokeWidth="2"
              />
              {/* arc filled to current T */}
              <path
                d={describeArc(0, 0, 120, -135, angle)}
                fill="none"
                stroke="url(#dialGrad)"
                strokeWidth="3"
                strokeLinecap="butt"
              />
              {/* tick marks at 0.0, 0.5, 1.0, 1.5 */}
              {[0, 0.5, 1.0, 1.5].map(v => {
                const a = -135 + (v / 1.5) * 270;
                const r1 = 124, r2 = 134;
                const rad = (a - 90) * Math.PI / 180;
                return (
                  <g key={v}>
                    <line
                      x1={Math.cos(rad) * r1} y1={Math.sin(rad) * r1}
                      x2={Math.cos(rad) * r2} y2={Math.sin(rad) * r2}
                      stroke="var(--ink)" strokeWidth="1.5"
                    />
                    <text
                      x={Math.cos(rad) * 146} y={Math.sin(rad) * 146}
                      textAnchor="middle"
                      dominantBaseline="middle"
                      fontFamily="var(--mono)"
                      fontSize="11"
                      fill="var(--muted)"
                    >{v.toFixed(1)}</text>
                  </g>
                );
              })}
              {/* needle */}
              <g style={{ transition: "transform 0.2s ease", transform: `rotate(${angle}deg)` }}>
                <line x1="0" y1="0" x2="0" y2="-108" stroke="var(--ink)" strokeWidth="2.5" />
                <polygon points="-6,-100 6,-100 0,-112" fill="var(--vermilion)" stroke="var(--ink)" strokeWidth="1" />
                <circle cx="0" cy="0" r="8" fill="var(--ink)" />
                <circle cx="0" cy="0" r="3" fill="var(--vermilion)" />
              </g>
            </svg>
            <div className="dial-readout">
              <div className="t-value">{T.toFixed(2)}</div>
              <div className="t-label">Temperature</div>
              <div className="t-mood">{mood}</div>
            </div>
          </div>
          <input
            className="t-slider"
            type="range"
            min="0"
            max="1.5"
            step="0.01"
            value={T}
            onChange={e => setT(parseFloat(e.target.value))}
          />
          <div className="t-ticks">
            <span>0.0 · safe</span>
            <span>0.7</span>
            <span>1.5 · wild</span>
          </div>
        </div>

        <div>
          <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 12 }}>
            Live rolls @ T = {T.toFixed(2)}
          </div>
          {/* the warped distribution as small bars, so you can watch the
              weights collapse / spread as T changes */}
          <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 24 }}>
            {warped.map((d, i) => (
              <div key={d.token} style={{ display: "grid", gridTemplateColumns: "100px 1fr 60px", gap: 10, alignItems: "center", fontFamily: "var(--mono)", fontSize: 12 }}>
                <div style={{ color: i === 0 ? "var(--ink)" : "var(--ink-soft)" }}>{d.token}</div>
                <div style={{ height: 10, background: "var(--paper)", border: "1px solid var(--hairline)", position: "relative" }}>
                  <div style={{
                    position: "absolute", left: 0, top: 0, bottom: 0,
                    width: (d.p * 100) + "%",
                    background: i === 0 ? "var(--vermilion)" : "var(--blue-soft)",
                    transition: "width 0.2s ease",
                  }} />
                </div>
                <div style={{ color: "var(--muted)", textAlign: "right" }}>{fmtPct(d.p)}</div>
              </div>
            ))}
            <div style={{ display: "grid", gridTemplateColumns: "100px 1fr 60px", gap: 10, alignItems: "center", fontFamily: "var(--mono)", fontSize: 12 }}>
              <div style={{ color: "var(--muted)" }}>tail</div>
              <div style={{ height: 10, background: "var(--paper)", border: "1px solid var(--hairline)", position: "relative" }}>
                <div style={{
                  position: "absolute", left: 0, top: 0, bottom: 0,
                  width: (tailMass * 100) + "%",
                  background: "var(--muted)",
                  transition: "width 0.2s ease",
                }} />
              </div>
              <div style={{ color: "var(--muted)", textAlign: "right" }}>{fmtPct(tailMass)}</div>
            </div>
          </div>
          <div className="t-output">
            {stream.map(r => (
              <div key={r.id} className="roll">
                <div className={"which" + (r.isFab ? " fab" : "")}>
                  <span style={{ color: "var(--muted)" }}>{preset.prompt} </span>
                  <span className="pick">{r.token}</span>
                </div>
                <div className="meta">{r.isFab ? "fabrication" : r.isTop ? "top" : "alternate"} · T {r.t}</div>
              </div>
            ))}
            {!stream.length && (
              <div className="roll" style={{ color: "var(--muted)" }}>
                <div>waiting for the first roll…</div>
                <div className="meta"></div>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// SVG arc helper — describes an arc from startAngle to endAngle (in degrees,
// where 0 = up, +90 = right) around (cx, cy) with radius r.
function describeArc(cx, cy, r, startAngle, endAngle) {
  const toXY = (a) => {
    const rad = (a - 90) * Math.PI / 180;
    return [cx + Math.cos(rad) * r, cy + Math.sin(rad) * r];
  };
  const [sx, sy] = toXY(startAngle);
  const [ex, ey] = toXY(endAngle);
  const large = (endAngle - startAngle) > 180 ? 1 : 0;
  return `M ${sx} ${sy} A ${r} ${r} 0 ${large} 1 ${ex} ${ey}`;
}

/* =====================================================================
   SECTION 5 — HALLUCINATION GRID
   "Roll 100 times." Watch the 1.5% London show up. This is the moment
   where the metaphor pays off: a hallucination isn't a malfunction, it
   is a low-probability roll on a die that was always going to roll.
   ===================================================================== */

function HallucinationGrid() {
  const preset = PRESETS[0]; // Paris example — the cleanest illustration
  const [results, setResults] = useState([]); // indexes 0..3 or -1 (tail)
  const [running, setRunning] = useState(false);
  const tRef = useRef(null);

  // Stop any in-flight run on unmount.
  useEffect(() => () => clearTimeout(tRef.current), []);

  const run = useCallback(() => {
    if (running) return;
    setResults([]);
    setRunning(true);
    const total = 100;
    let i = 0;
    const tick = () => {
      i++;
      setResults(prev => [...prev, sample(preset.dist)]);
      if (i < total) {
        tRef.current = setTimeout(tick, 25);
      } else {
        setRunning(false);
      }
    };
    tick();
  }, [preset, running]);

  // Aggregate counts for the legend.
  const counts = useMemo(() => {
    const c = { 0: 0, 1: 0, 2: 0, 3: 0, tail: 0 };
    results.forEach(r => { if (r === -1) c.tail++; else c[r]++; });
    return c;
  }, [results]);

  return (
    <div>
      <div className="two-up" style={{ alignItems: "start" }}>
        <div>
          <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 8 }}>
            100 independent rolls
          </div>
          <div style={{ fontFamily: "var(--serif-display)", fontSize: 26, lineHeight: 1.2, marginBottom: 16 }}>
            “{preset.prompt} <span style={{ color: "var(--vermilion)" }}>___</span>”
          </div>
          <p style={{ fontSize: 16, color: "var(--ink-soft)" }}>
            Each dot is a single die roll. Blue dots landed on the heavy
            weight: <i>Paris</i>. Red dots are the 1.5%, the 0.4%, the
            tail. They read like <i>errors</i>, even though the math
            worked exactly as designed.
          </p>
          <button className="btn" onClick={run} disabled={running} style={{ marginTop: 12 }}>
            {running ? "Rolling…" : "Roll 100 times"}
          </button>

          <div className="dot-legend">
            <div className="key"><div className="sw" style={{ background: "var(--blue-soft)" }} /> Paris <span style={{ color: "var(--muted)", marginLeft: 6 }}>· {counts[0]}</span></div>
            <div className="key"><div className="sw" style={{ background: "var(--vermilion)" }} /> other <span style={{ color: "var(--muted)", marginLeft: 6 }}>· {counts[1] + counts[2] + counts[3] + counts.tail}</span></div>
          </div>
        </div>

        <div>
          <div className="dot-grid">
            {Array.from({ length: 100 }).map((_, i) => {
              const r = results[i];
              if (r === undefined) {
                return <div key={i} className="dot" style={{ background: "rgba(0,0,0,0.05)", animationDelay: "0s", opacity: 1 }} />;
              }
              const wrong = r !== 0;
              return (
                <div
                  key={i}
                  className={"dot " + (wrong ? "wrong" : "")}
                  style={{ animationDelay: `${i * 0.012}s` }}
                  title={r === -1 ? "tail" : preset.dist[r].token}
                />
              );
            })}
          </div>
          <div style={{ marginTop: 14, fontFamily: "var(--mono)", fontSize: 11, color: "var(--muted)", letterSpacing: "0.1em", textTransform: "uppercase" }}>
            Expected wrong rolls per 100: ~3 · Observed: {counts[1] + counts[2] + counts[3] + counts.tail}
          </div>
        </div>
      </div>
    </div>
  );
}

/* =====================================================================
   SECTION 6 — TAKEAWAYS
   Three Q/A cards. The "glass-box" payoff.
   ===================================================================== */

function TakeawaysSection() {
  return (
    <div className="takeaways">
      <div className="take">
        <div className="q">Why does it sound so smart?</div>
        <div className="a">
          Because the die is loaded by trillions of human data points.
          On nearly every roll, the heaviest weight is also the
          grammatically and factually correct one.
        </div>
      </div>
      <div className="take">
        <div className="q">Why does it make up fake citations?</div>
        <div className="a">
          Because it is still, ultimately, rolling a die, and sometimes
          low-probability tokens win. A "hallucination" is the math
          working perfectly on an unlucky roll.
        </div>
      </div>
      <div className="take">
        <div className="q">Why is blaming the prompter a cop-out?</div>
        <div className="a">
          Because no prompter can stop a probability engine from
          eventually rolling a low-probability sequence. The error is
          built into the mechanism itself.
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { CasinoSection, TemperatureSection, HallucinationGrid, TakeawaysSection });
