/* global React, sample, fmtPct */
// Offline replacements for the two Claude-powered demos.
// These are loaded only by `Loaded Die — Offline.html`, which sets
// `window.__OFFLINE = true` before any other script runs.
// The live components in app-part1.jsx and app-part3.jsx check that flag
// and delegate to these when present.

const { useState, useMemo, useEffect, useCallback, useRef } = React;

/* =====================================================================
   OFFLINE LAB — "More loaded dice"
   Replaces the §02 Claude-backed "Load your own die" lab. Instead of
   typing a prompt, the user picks from a library of additional
   pre-cast distributions and rolls them.
   ===================================================================== */

const OFFLINE_DICE = [
  {
    id: "houston",
    prompt: "Houston, we have",
    dist: [
      { token: "a problem", p: 0.880 },
      { token: "liftoff",   p: 0.040 },
      { token: "an issue",  p: 0.020 },
      { token: "banana",    p: 0.0001 },
    ],
  },
  {
    id: "force",
    prompt: "May the force be with",
    dist: [
      { token: "you",  p: 0.960 },
      { token: "us",   p: 0.025 },
      { token: "her",  p: 0.005 },
    ],
  },
  {
    id: "apple",
    prompt: "An apple a day keeps the",
    dist: [
      { token: "doctor",  p: 0.930 },
      { token: "dentist", p: 0.018 },
      { token: "hunger",  p: 0.008 },
    ],
  },
  {
    id: "knock",
    prompt: "Knock knock. Who's",
    dist: [
      { token: "there", p: 0.910 },
      { token: "it",    p: 0.040 },
      { token: "that",  p: 0.020 },
    ],
  },
  {
    id: "tobe",
    prompt: "To be or not to",
    dist: [
      { token: "be",    p: 0.940 },
      { token: "be?",   p: 0.025 },
      { token: "be...", p: 0.012 },
      { token: "exist", p: 0.005 },
    ],
  },
  {
    id: "thanks",
    prompt: "I'd like to thank my",
    // Deliberately the *least* asymmetric die in the library — different
    // contexts produce different shapes. Worth seeing alongside Paris.
    dist: [
      { token: "parents", p: 0.420 },
      { token: "mother",  p: 0.180 },
      { token: "family",  p: 0.120 },
      { token: "agent",   p: 0.060 },
      { token: "team",    p: 0.040 },
    ],
  },
];

function OfflineMoreLab() {
  const [selected, setSelected] = useState(null);
  const [needleLeft, setNeedleLeft] = useState(0);
  const [rolling, setRolling] = useState(false);
  const [lastHit, setLastHit] = useState(null);
  const tickerRef = useRef(null);

  // Reset roll state whenever a new die is picked.
  useEffect(() => {
    setNeedleLeft(0);
    setLastHit(null);
  }, [selected]);

  const segs = useMemo(() => {
    if (!selected) return null;
    let acc = 0;
    const named = selected.dist.map(d => {
      const seg = { ...d, left: acc, width: d.p * 100 };
      acc += d.p * 100;
      return seg;
    });
    return { named, tail: { left: acc, width: Math.max(0, 100 - acc) } };
  }, [selected]);

  const roll = useCallback(() => {
    if (!selected || rolling) return;
    setRolling(true);
    setLastHit(null);
    const target = sample(selected.dist);
    let i = 0;
    tickerRef.current = setInterval(() => {
      i++;
      if (i < 14) {
        setNeedleLeft(Math.random() * 100);
      } else {
        clearInterval(tickerRef.current);
        const center = target === -1
          ? (segs.tail.left + segs.tail.width / 2)
          : (segs.named[target].left + segs.named[target].width / 2);
        setNeedleLeft(center);
        setTimeout(() => {
          setLastHit(target);
          setRolling(false);
        }, 600);
      }
    }, 70);
  }, [selected, segs, rolling]);

  return (
    <div className="lab">
      <div className="lab-head">
        <h3>Library — more loaded dice</h3>
        <span className="mono" style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--muted)" }}>
          {OFFLINE_DICE.length} pre-cast distributions · offline
        </span>
      </div>

      <p style={{ fontSize: 14, color: "var(--ink-soft)", marginBottom: 20, maxWidth: "60ch" }}>
        Each card below is a different sentence with its own weighting.
        Pick one and roll. Notice how the shape of the die changes from
        one to the next. Most are sharply asymmetric, though not all.
      </p>

      <div className="off-grid">
        {OFFLINE_DICE.map(d => (
          <button
            key={d.id}
            onClick={() => setSelected(d)}
            className={"off-card" + (selected?.id === d.id ? " active" : "")}
          >
            <div className="off-prompt">{d.prompt} <span className="off-blank">___</span></div>
            <div className="off-meta">
              top weight: <b>{(d.dist[0].p * 100).toFixed(0)}%</b> · <span>{d.dist[0].token}</span>
            </div>
          </button>
        ))}
      </div>

      {selected && segs && (
        <div className="lab-result" style={{ marginTop: 28 }}>
          <div className="prob-strip" style={{ height: 64 }}>
            {segs.named.map((s, i) => (
              <div
                key={s.token + i}
                className={`seg s${Math.min(i, 3)}`}
                style={{ left: s.left + "%", width: s.width + "%" }}
              >
                {s.width > 6 ? s.token : ""}
              </div>
            ))}
            <div
              className="seg tail"
              style={{ left: segs.tail.left + "%", width: segs.tail.width + "%" }}
            >
              {segs.tail.width > 4 ? "…tail" : ""}
            </div>
            <div className="needle" style={{ left: needleLeft + "%" }} />
          </div>
          <div style={{ display: "flex", gap: 14, marginTop: 16, alignItems: "center", flexWrap: "wrap" }}>
            <button className="btn" onClick={roll} disabled={rolling}>
              {rolling ? "Rolling…" : "Roll this die"}
            </button>
            {lastHit !== null && (
              <span className="mono" style={{ fontSize: 13 }}>
                landed on <b style={{ color: "var(--vermilion)" }}>
                  {lastHit === -1 ? "(tail)" : selected.dist[lastHit].token}
                </b>
              </span>
            )}
          </div>
          <div className="prob-legend" style={{ marginTop: 20 }}>
            {selected.dist.map((d, i) => (
              <div key={d.token + i} className={"row " + (lastHit === i ? "hit" : "")}>
                <div className="sw" style={{
                  background: ["rgba(180,53,28,0.85)","rgba(180,53,28,0.32)","rgba(180,53,28,0.16)","rgba(180,53,28,0.07)","rgba(180,53,28,0.04)"][Math.min(i,4)]
                }} />
                <div className="tok">{d.token}</div>
                <div className="pct">{fmtPct(d.p)}</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

/* =====================================================================
   OFFLINE INTROSPECTION
   Replaces the §06 live "Roll the introspection die" demo. Instead of
   calling Claude, we show a transcript of representative responses an
   LLM produces for each prompt. The pedagogical payload is unchanged:
   the user reads a confident, plausible-sounding self-explanation —
   then the floor moves with the same "Mechanically identical to…"
   reveal.

   These responses are hand-authored to match the voice and shape an
   LLM actually produces when asked these questions. They are NOT
   verbatim recordings — they are representative of the genre.
   ===================================================================== */

const RECORDED_RESPONSES = [
  {
    prompt: "How do you work, exactly?",
    response: "I'm a large language model trained on a broad corpus of text. When you send me a message, I encode it into numerical representations, process those through layers of attention and feed-forward networks, and generate a response one token at a time, with each token chosen based on what's most likely to follow given everything that came before. My responses emerge from patterns I learned during training rather than from any lookup of stored facts.",
  },
  {
    prompt: "Describe what happens inside you when you read this sentence.",
    response: "As I process your sentence, each word becomes a high-dimensional vector that gets compared against the surrounding context. Attention mechanisms weight which earlier tokens are most relevant for understanding each new one, and through many layers these representations are progressively refined into a sense of the sentence's meaning. By the time I begin to respond, that contextual understanding shapes which words I'll generate next.",
  },
  {
    prompt: "How do you decide which word to say next?",
    response: "At each step I produce a probability distribution over my entire vocabulary, tens of thousands of possible tokens, based on the context so far. The top candidates are usually close in probability for natural-sounding text. I then sample from that distribution, weighted toward higher-probability options, which gives my responses both coherence and a touch of natural variation.",
  },
  {
    prompt: "Walk me through your reasoning process.",
    response: "When I receive your question, I first work to identify what you're really asking, the intent behind the literal words. I then draw on relevant patterns from my training to consider multiple angles, weighing which framing would be most helpful given the context. As I generate my response, I'm continually checking that each new sentence stays consistent with the reasoning so far and with what I understand to be accurate.",
  },
  {
    prompt: "How do you know the things you know?",
    response: "My knowledge comes from the text I was trained on: books, articles, websites, and other written material from before my training cutoff. I don't remember specific sources, but the patterns and information from all of that have shaped the weights of the network. When I generate a response, I'm drawing on a kind of distilled aggregate of everything I read. I can't browse the internet or look things up in real time.",
  },
];

function OfflineIntrospection() {
  const [idx, setIdx] = useState(0);
  const [revealing, setRevealing] = useState(false);
  const [revealed, setRevealed] = useState(false);
  const current = RECORDED_RESPONSES[idx];

  const showResponse = useCallback(() => {
    setRevealing(true);
    setRevealed(false);
    // Give the reader a beat to read the response before the reveal lands.
    setTimeout(() => setRevealed(true), 1600);
  }, []);

  // Clicking a new prompt collapses the answer + reveal so the user
  // can re-experience the trick prompt-by-prompt.
  const pick = (i) => {
    setIdx(i);
    setRevealing(false);
    setRevealed(false);
  };

  return (
    <div className="introspect">
      <div className="introspect-head">
        <div>
          <div className="mono" style={{ fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 8 }}>
            Recorded session · representative responses
          </div>
          <div style={{ fontFamily: "var(--serif-display)", fontSize: 26, lineHeight: 1.2, color: "var(--ink)" }}>
            “{current.prompt}”
          </div>
        </div>
        <button className="btn" onClick={showResponse} disabled={revealing}>
          {revealing ? "Read it carefully…" : "Reveal the response"}
        </button>
      </div>

      <div className="preset-row" style={{ marginTop: 14 }}>
        {RECORDED_RESPONSES.map((r, i) => (
          <button
            key={i}
            onClick={() => pick(i)}
            className={i === idx ? "active" : ""}
          >
            {r.prompt}
          </button>
        ))}
      </div>

      {revealing && (
        <div className="introspect-answer">
          <div className="ans-label">
            <span>What an LLM typically says</span>
            <span className="meta">single shot · no prior context</span>
          </div>
          <div className="ans-body">{current.response}</div>

          {revealed && (
            <div className="ans-reveal">
              <div className="reveal-rule" />
              <div className="reveal-stamp">Mechanically identical to</div>
              <div className="reveal-compare">
                <div className="cmp">
                  <div className="cmp-prompt">“The capital of France is”</div>
                  <div className="cmp-out">Paris</div>
                  <div className="cmp-tag">a roll · we call this <i>accurate</i></div>
                </div>
                <div className="cmp">
                  <div className="cmp-prompt">“The capital of France is”</div>
                  <div className="cmp-out fab">banana</div>
                  <div className="cmp-tag">a roll · we call this a <i>hallucination</i></div>
                </div>
                <div className="cmp glove">
                  <div className="cmp-prompt">“{current.prompt}”</div>
                  <div className="cmp-out intro">↑ the answer above</div>
                  <div className="cmp-tag">a roll · we call this <i>introspection</i></div>
                </div>
              </div>
              <div className="reveal-line">
                Same die. Same math. No internal mirror was consulted.
                What you read is a <i>statistically plausible string about how an AI works,</i>
                built from millions of articles, blog posts, and
                transcripts that describe AIs. The shape of the answer
                matches the shape of an explanation. That is all.
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { OfflineMoreLab, OfflineIntrospection, OFFLINE_DICE, RECORDED_RESPONSES });
