// Syntax highlighter — TS + Python.
function highlight(code, lang = "ts") {
  let s = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  if (lang === "py") {
    s = s.replace(/(#[^\n]*)/g, '<span class="c">$1</span>');
    s = s.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g, '<span class="s">$1</span>');
    const pykw = ["import","from","def","class","async","await","return","if","elif","else","for","while","as","in","is","not","and","or","try","except","finally","raise","with","lambda","yield","pass","break","continue","global","nonlocal","True","False","None","self"];
    s = s.replace(new RegExp("\\b(" + pykw.join("|") + ")\\b", "g"), '<span class="k">$1</span>');
    s = s.replace(/\b([A-Z][A-Za-z0-9_]*)\b/g, '<span class="t">$1</span>');
    s = s.replace(/\b([a-z_][A-Za-z0-9_]*)(\()/g, '<span class="f">$1</span>$2');
    s = s.replace(/\b(\d+(?:\.\d+)?)\b/g, '<span class="n">$1</span>');
    return s;
  }
  s = s.replace(/(\/\/[^\n]*)/g, '<span class="c">$1</span>');
  s = s.replace(/("(?:[^"\\]|\\.)*")/g, '<span class="s">$1</span>');
  const kws = ["import","from","const","let","var","async","function","await","return","new","if","else","for","while","export","default","class","extends","interface","type","enum","as","of","in","try","catch","finally","throw"];
  s = s.replace(new RegExp("\\b(" + kws.join("|") + ")\\b", "g"), '<span class="k">$1</span>');
  s = s.replace(/\b([A-Z][A-Za-z0-9_]*)\b/g, '<span class="t">$1</span>');
  s = s.replace(/\b([a-z_][A-Za-z0-9_]*)(\()/g, '<span class="f">$1</span>$2');
  s = s.replace(/\b(\d+(?:\.\d+)?)\b/g, '<span class="n">$1</span>');
  return s;
}

// Language tabs — TS / Python / Rust(locked). Hoverable/click-to-pin tooltip on Rust.
function LangTabs({ value, onChange }) {
  const [pinned, setPinned] = React.useState(false);
  const [hover, setHover] = React.useState(false);
  const wrapRef = React.useRef(null);
  React.useEffect(() => {
    if (!pinned) return;
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setPinned(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [pinned]);
  const show = hover || pinned;
  return (
    <div className="langtabs" role="tablist">
      <button role="tab" className={"langtab" + (value === "ts" ? " active" : "")} onClick={() => onChange("ts")}>TypeScript</button>
      <button role="tab" className={"langtab" + (value === "py" ? " active" : "")} onClick={() => onChange("py")}>Python</button>
      <span className="langtab-wrap" ref={wrapRef}
            onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
        <button className={"langtab locked" + (pinned ? " pinned" : "")}
                onClick={(e) => { e.preventDefault(); setPinned(p => !p); }}
                aria-disabled="true">
          <svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true" style={{marginRight:6,verticalAlign:"-1px"}}>
            <path fill="currentColor" d="M4.5 7V4.5a3.5 3.5 0 1 1 7 0V7H12a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h.5Zm1.5 0h4V4.5a2 2 0 1 0-4 0V7Z"/>
          </svg>
          Rust
        </button>
        <span className={"langtab-tip" + (show ? " show" : "")} role="tooltip">
          <span className="tip-title">Rust bindings — in progress</span>
          <span className="tip-body">A thin wrapper over the V2 HTTP + WS protocol. Targeted for June 2026 (post-cutover). <a href="#/hire" onClick={(e) => { e.preventDefault(); setPinned(false); location.hash = "#/hire"; }}>Sponsor it →</a></span>
        </span>
      </span>
    </div>
  );
}

function CopyButton({ text }) {
  const [copied, setCopied] = React.useState(false);
  return (
    <button
      className={"copy-btn" + (copied ? " copied" : "")}
      onClick={() => {
        navigator.clipboard?.writeText(text);
        setCopied(true);
        setTimeout(() => setCopied(false), 1200);
      }}
    >{copied ? "copied" : "copy"}</button>
  );
}

function CodeBlock({ filename, code, lang = "ts" }) {
  return (
    <div className="codeblock">
      <div className="codeblock-head">
        <span className="filename">{filename}</span>
        <span className="actions"><CopyButton text={code} /></span>
      </div>
      <pre dangerouslySetInnerHTML={{ __html: highlight(code, lang) }} />
    </div>
  );
}

// Paired TS/Python block with tabs; Rust tab locked.
function DualCodeBlock({ filenameTs, filenamePy, codeTs, codePy, lang, setLang }) {
  const code = lang === "py" ? codePy : codeTs;
  const file = lang === "py" ? filenamePy : filenameTs;
  return (
    <div className="codeblock">
      <div className="codeblock-head with-tabs">
        <LangTabs value={lang} onChange={setLang} />
        <span className="filename sep-left">{file}</span>
        <span className="actions"><CopyButton text={code} /></span>
      </div>
      <pre dangerouslySetInnerHTML={{ __html: highlight(code, lang) }} />
    </div>
  );
}

// Paired install one-liner block with tabs.
function DualInstallBlock({ ts, py, lang, setLang }) {
  const code = lang === "py" ? py : ts;
  return (
    <div className="codeblock">
      <div className="codeblock-head with-tabs">
        <LangTabs value={lang} onChange={setLang} />
        <span className="filename sep-left">shell</span>
        <span className="actions"><CopyButton text={code} /></span>
      </div>
      <pre dangerouslySetInnerHTML={{ __html: highlight(code, "ts") }} />
    </div>
  );
}

// Live terminal — streams lines with a typing cadence.
function LiveTerminal({ lines, label = "bash — 01-clob-client.ts", prompt = "$", loop = true }) {
  const [shown, setShown] = React.useState(0);
  const [typed, setTyped] = React.useState("");
  const [phase, setPhase] = React.useState("typing"); // typing | streaming | done

  // Reset on lines change
  React.useEffect(() => {
    setShown(0); setTyped(""); setPhase("typing");
  }, [lines]);

  // Typing animation for the first (command) line
  React.useEffect(() => {
    if (phase !== "typing") return;
    const cmd = lines[0]?.text ?? "";
    if (typed.length >= cmd.length) {
      const t = setTimeout(() => { setShown(1); setPhase("streaming"); }, 350);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => setTyped(cmd.slice(0, typed.length + 1)), 28 + Math.random() * 20);
    return () => clearTimeout(t);
  }, [typed, phase, lines]);

  // Stream remaining lines
  React.useEffect(() => {
    if (phase !== "streaming") return;
    if (shown >= lines.length) {
      if (!loop) { setPhase("done"); return; }
      const t = setTimeout(() => { setShown(0); setTyped(""); setPhase("typing"); }, 2600);
      return () => clearTimeout(t);
    }
    const delays = { cmd: 400, muted: 280, ok: 360, warn: 520, err: 520, idle: 600 };
    const cur = lines[shown];
    const d = delays[cur?.k] ?? 300;
    const t = setTimeout(() => setShown(shown + 1), d + Math.random() * 80);
    return () => clearTimeout(t);
  }, [shown, phase, lines, loop]);

  return (
    <div className="terminal">
      <div className="terminal-head">
        <span className="dots"><span className="dot red"></span><span className="dot yellow"></span><span className="dot green"></span></span>
        <span className="label">{label}</span>
        <span className="actions"><CopyButton text={lines.map(l => l.text).join("\n")} /></span>
      </div>
      <div className="terminal-body">
        {/* Command line */}
        <div className="line"><span className="prompt">{prompt}</span> <span>{phase === "typing" ? typed : lines[0].text}</span>{phase === "typing" && <span className="cursor" />}</div>
        {/* Streamed lines */}
        {lines.slice(1, shown).map((l, i) => (
          <div key={i} className={"line " + (l.k || "")}>
            {l.k === "muted" && <span className="muted">{l.text}</span>}
            {l.k === "ok"    && <span className="ok">{l.text}</span>}
            {l.k === "warn"  && <span className="warn">{l.text}</span>}
            {l.k === "err"   && <span className="err">{l.text}</span>}
            {l.k === "idle"  && <span className="muted">&nbsp;</span>}
            {!["muted","ok","warn","err","idle"].includes(l.k) && <span>{l.text}</span>}
          </div>
        ))}
        {phase !== "typing" && shown >= 1 && shown < lines.length && (
          <div className="line"><span className="muted">…</span><span className="cursor" /></div>
        )}
        {phase === "streaming" && shown >= lines.length && (
          <div className="line"><span className="prompt">{prompt}</span> <span className="cursor" /></div>
        )}
      </div>
    </div>
  );
}

function CountdownBanner({ iso = CUTOVER_ISO }) {
  const [now, setNow] = React.useState(() => Date.now());
  React.useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);
  const target = new Date(iso).getTime();
  const delta = Math.max(0, target - now);
  const d = Math.floor(delta / 86400000);
  const h = Math.floor(delta / 3600000) % 24;
  const m = Math.floor(delta / 60000) % 60;
  const s = Math.floor(delta / 1000) % 60;
  const pad = (n) => String(n).padStart(2, "0");
  return (
    <div className="cutover-banner">
      <span className="dot" />
      <span>[T-minus {pad(d)}d {pad(h)}h {pad(m)}m {pad(s)}s] V2 cutover · 2026-04-28 11:00 UTC</span>
    </div>
  );
}

// Sidebar
function Sidebar({ route, go }) {
  return (
    <aside className="sidebar">
      <div className="sidebar-brand">
        <span className="glyph">~/</span>
        <span className="name"><b>Polymarket-V2</b> Migration </span>
      </div>
      <div className="sidebar-section">~ Guide</div>
      {[{slug:"home",file:"README.md",n:"00"}].map(() => (
        <a key="home" className={"chapter-link" + (route.page === "home" ? " active" : "")}
           href="#/" onClick={(e) => { e.preventDefault(); go("home"); }}>
          <span className="prompt">$</span>
          <span className="cmd">cat</span>
          <span className="file">README.md</span>
        </a>
      ))}
      <div className="sidebar-section">~ chapters</div>
      {CHAPTERS.map(c => (
        <a key={c.slug}
           className={"chapter-link" + (route.page === "chapter" && route.slug === c.slug ? " active" : "")}
           href={"#/chapter/" + c.slug}
           onClick={(e) => { e.preventDefault(); go("chapter", c.slug); }}>
          <span className="prompt">$</span>
          <span className="cmd">cat</span>
          <span className="file">{c.file}</span>
        </a>
      ))}
      <div className="sidebar-section">~ meta</div>
      <a className={"chapter-link meta" + (route.page === "about" ? " active" : "")}
         href="#/about" onClick={(e) => { e.preventDefault(); go("about"); }}>
        <span className="prompt">$</span><span className="cmd">whoami</span>
      </a>
      <a className={"chapter-link meta" + (route.page === "hire" ? " active" : "")}
         href="#/hire" onClick={(e) => { e.preventDefault(); go("hire"); }}>
        <span className="prompt">$</span><span className="cmd">./hire.sh</span>
      </a>
    </aside>
  );
}

function TopBar({ route, go, theme, toggleTheme }) {
  return (
    <header className="topbar">
      <div className="title">
        <b>Polymarket-v2-Migration</b>
        <span className="sep">/</span>
        <span>{route.page === "chapter" ? "ch" + String(CHAPTERS.find(c => c.slug === route.slug)?.n ?? "").padStart(2, "0") + " · " + (CHAPTERS.find(c => c.slug === route.slug)?.title ?? "") : route.page}</span>
      </div>
      <nav>
        <a href="#/" onClick={(e) => { e.preventDefault(); go("home"); }}>Guide</a>
        <a href="#/about" onClick={(e) => { e.preventDefault(); go("about"); }}>About</a>
        {/* <a href="#/hire" onClick={(e) => { e.preventDefault(); go("hire"); }} className="cta">hire me ↗</a> */}
        <button
          className="theme-toggle"
          onClick={toggleTheme}
          aria-label={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
          title={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
        >
          {theme === "dark" ? (
            /* Sun — shown in dark mode (click → light) */
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <circle cx="12" cy="12" r="4" />
              <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
            </svg>
          ) : (
            /* Moon — shown in light mode (click → dark) */
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
            </svg>
          )}
        </button>
      </nav>
    </header>
  );
}

function MobileNav({ route, go }) {
  return (
    <div className="mobile-nav">
      <a href="#/" className={route.page === "home" ? "active" : ""} onClick={(e)=>{e.preventDefault();go("home");}}>README</a>
      {CHAPTERS.map(c => (
        <a key={c.slug} className={route.page === "chapter" && route.slug === c.slug ? "active" : ""}
           href={"#/chapter/" + c.slug}
           onClick={(e)=>{e.preventDefault();go("chapter", c.slug);}}>
          {c.file}
        </a>
      ))}
      <a href="#/about" className={route.page === "about" ? "active" : ""} onClick={(e)=>{e.preventDefault();go("about");}}>whoami</a>
      <a href="#/hire" className={route.page === "hire" ? "active" : ""} onClick={(e)=>{e.preventDefault();go("hire");}}>hire.sh</a>
    </div>
  );
}

// Paired terminal with tabs.
function DualTerminal({ linesTs, linesPy, labelTs, labelPy, lang, setLang }) {
  const lines = lang === "py" ? linesPy : linesTs;
  const label = lang === "py" ? labelPy : labelTs;
  return (
    <div className="terminal">
      <div className="terminal-head with-tabs">
        <span className="dots"><span className="dot red"></span><span className="dot yellow"></span><span className="dot green"></span></span>
        <LangTabs value={lang} onChange={setLang} />
        <span className="label sep-left">{label}</span>
        <span className="actions"><CopyButton text={lines.map(l => l.text).join("\n")} /></span>
      </div>
      <LiveTerminalBody lines={lines} />
    </div>
  );
}

// Split LiveTerminal into head + body so DualTerminal can reuse the body.
function LiveTerminalBody({ lines, prompt = "$", loop = true }) {
  const [shown, setShown] = React.useState(0);
  const [typed, setTyped] = React.useState("");
  const [phase, setPhase] = React.useState("typing");
  React.useEffect(() => { setShown(0); setTyped(""); setPhase("typing"); }, [lines]);
  React.useEffect(() => {
    if (phase !== "typing") return;
    const cmd = lines[0]?.text ?? "";
    if (typed.length >= cmd.length) {
      const t = setTimeout(() => { setShown(1); setPhase("streaming"); }, 350);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => setTyped(cmd.slice(0, typed.length + 1)), 28 + Math.random() * 20);
    return () => clearTimeout(t);
  }, [typed, phase, lines]);
  React.useEffect(() => {
    if (phase !== "streaming") return;
    if (shown >= lines.length) {
      if (!loop) { setPhase("done"); return; }
      const t = setTimeout(() => { setShown(0); setTyped(""); setPhase("typing"); }, 2600);
      return () => clearTimeout(t);
    }
    const delays = { cmd: 400, muted: 280, ok: 360, warn: 520, err: 520, idle: 600 };
    const d = delays[lines[shown]?.k] ?? 300;
    const t = setTimeout(() => setShown(shown + 1), d + Math.random() * 80);
    return () => clearTimeout(t);
  }, [shown, phase, lines, loop]);
  return (
    <div className="terminal-body">
      <div className="line"><span className="prompt">{prompt}</span> <span>{phase === "typing" ? typed : lines[0].text}</span>{phase === "typing" && <span className="cursor" />}</div>
      {lines.slice(1, shown).map((l, i) => (
        <div key={i} className={"line " + (l.k || "")}>
          {l.k === "muted" && <span className="muted">{l.text}</span>}
          {l.k === "ok"    && <span className="ok">{l.text}</span>}
          {l.k === "warn"  && <span className="warn">{l.text}</span>}
          {l.k === "err"   && <span className="err">{l.text}</span>}
          {l.k === "idle"  && <span className="muted">&nbsp;</span>}
          {!["muted","ok","warn","err","idle"].includes(l.k) && <span>{l.text}</span>}
        </div>
      ))}
      {phase !== "typing" && shown >= 1 && shown < lines.length && (
        <div className="line"><span className="muted">…</span><span className="cursor" /></div>
      )}
      {phase === "streaming" && shown >= lines.length && (
        <div className="line"><span className="prompt">{prompt}</span> <span className="cursor" /></div>
      )}
    </div>
  );
}

Object.assign(window, { highlight, CopyButton, CodeBlock, DualCodeBlock, DualInstallBlock, DualTerminal, LangTabs, LiveTerminal, LiveTerminalBody, CountdownBanner, Sidebar, TopBar, MobileNav });
