import { useState, useEffect, useRef, useCallback } from "react";
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
import { mermaid } from "@streamdown/mermaid";
import { createMathPlugin } from "@streamdown/math";
import { cjk } from "@streamdown/cjk";
import "streamdown/styles.css";
import "katex/dist/katex.min.css";

const MODELS = [
  {
    id: "1.7b",
    name: "Bonsai 1.7B",
    params: "1.7B",
    size: "290 MB",
    blurb: "Pocket-class. Built for wearables and always-on agents.",
  },
  {
    id: "4b",
    name: "Bonsai 4B",
    params: "4B",
    size: "584 MB",
    blurb: "The sweet spot. Strong reasoning at on-device latency.",
    comingSoon: true,
  },
  {
    id: "8b",
    name: "Bonsai 8B",
    params: "8B",
    size: "1.2 GB",
    blurb: "Datacenter-grade reasoning, in your browser tab.",
    comingSoon: true,
  },
];

const formatBytes = (bytes) => {
  if (!bytes) return "0 MB";
  if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`;
  return `${(bytes / 1e6).toFixed(0)} MB`;
};

const math = createMathPlugin({ singleDollarTextMath: true });

const STREAMDOWN_PLUGINS = { code, mermaid, math, cjk };
const PRISM_GLYPH_CLASS =
  "h-9 w-9 overflow-hidden opacity-90 [clip-path:polygon(50%_4%,100%_100%,0%_100%)] bg-[radial-gradient(circle_at_50%_18%,rgba(255,255,255,0.3),transparent_28%),linear-gradient(180deg,var(--prism-2)_0%,var(--prism-1)_42%,var(--prism-6)_100%)] drop-shadow-[0_0_18px_rgba(255,184,77,0.18)]";

function SendIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
      <path
        d="M4 12L12 4M6 4H12V10"
        stroke="currentColor"
        strokeWidth="1.7"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function ResetIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
      <path
        d="M4.5 6.5H1.75V3.75M2.2 6.2A5.8 5.8 0 1 1 3.6 11.7"
        stroke="currentColor"
        strokeWidth="1.7"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function StopIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
      <rect
        x="4.25"
        y="4.25"
        width="7.5"
        height="7.5"
        rx="1.8"
        fill="currentColor"
      />
    </svg>
  );
}

export default function PrismDemo() {
  const canvasRef = useRef(null);
  const mouseRef = useRef({ x: -9999, y: -9999, inside: false });
  const cellsRef = useRef([]);
  const wavesRef = useRef([]);
  const rafRef = useRef(null);
  const playgroundRef = useRef(null);
  const inputRef = useRef(null);
  const messagesRef = useRef(null);
  const autoScrollRef = useRef(true);
  const optimizeMessageTimeoutRef = useRef(null);

  // Playground stage machine
  const [stage, setStage] = useState("select"); // 'select' | 'loading' | 'chat' | 'error'
  const [selectedId, setSelectedId] = useState("1.7b");
  const [loadProgress, setLoadProgress] = useState(0);
  const [loadMessage, setLoadMessage] = useState("Fetching weights");
  const [loadLoaded, setLoadLoaded] = useState(0);
  const [loadTotal, setLoadTotal] = useState(0);
  const [errorMessage, setErrorMessage] = useState(null);
  const messagesHistoryRef = useRef([]);

  // Chat state
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [isThinking, setIsThinking] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const [tps, setTps] = useState(null);

  const workerRef = useRef(null);

  const [fontsReady, setFontsReady] = useState(false);
  useEffect(() => {
    if (!document.fonts?.load) {
      setFontsReady(true);
      return;
    }
    Promise.all([
      document.fonts.load('400 132px "Instrument Serif"'),
      document.fonts.load('italic 400 132px "Instrument Serif"'),
    ]).finally(() => setFontsReady(true));
  }, []);

  const selected = MODELS.find((m) => m.id === selectedId) || MODELS[0];

  const scrollToPlayground = useCallback(() => {
    playgroundRef.current?.scrollIntoView({
      behavior: "smooth",
      block: "start",
    });
  }, []);

  useEffect(() => {
    if (!workerRef.current) {
      workerRef.current = new Worker(new URL("./worker.js", import.meta.url), {
        type: "module",
      });
    }
    const worker = workerRef.current;

    const onMessage = (e) => {
      const d = e.data;
      switch (d.status) {
        case "progress_total":
          setLoadProgress(d.progress);
          setLoadLoaded(d.loaded);
          setLoadTotal(d.total);
          if (Number(d.progress) >= 100) {
            clearTimeout(optimizeMessageTimeoutRef.current);
            optimizeMessageTimeoutRef.current = setTimeout(() => {
              setLoadMessage("Optimizing model for 1-bit execution");
            }, 100);
          }
          break;
        case "loading":
          setLoadMessage(d.data);
          break;
        case "ready":
          clearTimeout(optimizeMessageTimeoutRef.current);
          setStage("chat");
          setTimeout(() => inputRef.current?.focus(), 200);
          break;
        case "start":
          setIsThinking(false);
          setIsStreaming(true);
          setTps(null);
          setMessages((m) => [...m, { role: "assistant", content: "" }]);
          break;
        case "update":
          if (d.tps != null) setTps(d.tps);
          setMessages((m) => {
            const copy = [...m];
            const last = copy[copy.length - 1];
            copy[copy.length - 1] = {
              ...last,
              content: last.content + d.output,
            };
            return copy;
          });
          break;
        case "complete":
          setIsStreaming(false);
          break;
        case "error":
          clearTimeout(optimizeMessageTimeoutRef.current);
          setErrorMessage(d.data);
          setStage("error");
          setIsThinking(false);
          setIsStreaming(false);
          break;
      }
    };

    worker.addEventListener("message", onMessage);
    return () => {
      clearTimeout(optimizeMessageTimeoutRef.current);
      worker.removeEventListener("message", onMessage);
    };
  }, []);

  const startLoading = () => {
    clearTimeout(optimizeMessageTimeoutRef.current);
    setStage("loading");
    setLoadProgress(0);
    setLoadMessage("Fetching weights");
    setLoadLoaded(0);
    setLoadTotal(0);
    workerRef.current?.postMessage({ type: "load", data: selectedId });
  };

  // --- Background canvas: digits + glistening waves ---
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = window.devicePixelRatio || 1;

    const CELL_W = 16;
    const CELL_H = 22;

    const PRISM = [
      [255, 122, 92],
      [255, 184, 77],
      [196, 217, 46],
      [77, 208, 196],
      [124, 142, 232],
      [182, 123, 232],
    ];

    const setupCells = () => {
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      ctx.scale(dpr, dpr);

      const cols = Math.ceil(w / CELL_W);
      const rows = Math.ceil(h / CELL_H);
      const cells = [];
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          cells.push({
            x: c * CELL_W + CELL_W / 2,
            y: r * CELL_H + CELL_H / 2 + 6,
            val: Math.random() > 0.5 ? "1" : "0",
            nextFlip: performance.now() + Math.random() * 8000 + 3000,
            phase: Math.random() * Math.PI * 2,
          });
        }
      }
      cellsRef.current = cells;
    };

    const handleResize = () => {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      setupCells();
    };
    const handleMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      mouseRef.current.x = e.clientX - rect.left;
      mouseRef.current.y = e.clientY - rect.top;
      mouseRef.current.inside = true;
    };
    const handleLeave = () => {
      mouseRef.current.inside = false;
      mouseRef.current.x = -9999;
      mouseRef.current.y = -9999;
    };

    setupCells();
    window.addEventListener("resize", handleResize);
    window.addEventListener("mousemove", handleMove);
    canvas.addEventListener("mouseleave", handleLeave);

    let waveColorIdx = 0;
    const spawnWave = () => {
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      const angle = (Math.random() - 0.5) * (Math.PI / 3) + Math.PI / 2;
      const nx = Math.cos(angle);
      const ny = Math.sin(angle);
      const dots = [0, w * nx, h * ny, w * nx + h * ny];
      const minDot = Math.min(...dots);
      const maxDot = Math.max(...dots);
      const direction = Math.random() > 0.5 ? 1 : -1;
      const startPos = direction > 0 ? minDot - 200 : maxDot + 200;
      const endPos = direction > 0 ? maxDot + 200 : minDot - 200;
      const speed = 220 + Math.random() * 140;
      const color = PRISM[waveColorIdx % PRISM.length];
      waveColorIdx++;
      wavesRef.current.push({
        nx,
        ny,
        pos: startPos,
        endPos,
        direction,
        speed,
        color,
        bandWidth: 110,
        startedAt: performance.now(),
        flippedSet: new Set(),
      });
    };

    let nextWaveAt = performance.now() + 2200;
    let lastT = performance.now();

    const render = (now) => {
      const dt = Math.min(now - lastT, 64);
      lastT = now;

      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      ctx.clearRect(0, 0, w, h);

      ctx.font = "11px 'JetBrains Mono', monospace";
      ctx.textAlign = "center";
      ctx.textBaseline = "alphabetic";

      if (now > nextWaveAt) {
        spawnWave();
        nextWaveAt = now + 5500 + Math.random() * 3500;
      }

      const activeWaves = [];
      for (const wv of wavesRef.current) {
        wv.pos += wv.direction * wv.speed * (dt / 1000);
        const stillOnscreen =
          (wv.direction > 0 && wv.pos < wv.endPos) ||
          (wv.direction < 0 && wv.pos > wv.endPos);
        if (stillOnscreen) activeWaves.push(wv);
      }
      wavesRef.current = activeWaves;

      const mx = mouseRef.current.x;
      const my = mouseRef.current.y;
      const LENS_R = 160;
      const LENS_R2 = LENS_R * LENS_R;

      const cells = cellsRef.current;
      for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];

        if (now > cell.nextFlip) {
          cell.val = cell.val === "1" ? "0" : "1";
          cell.nextFlip = now + 4000 + Math.random() * 9000;
        }

        const pulse = 0.5 + 0.5 * Math.sin(now * 0.0006 + cell.phase);
        let opacity = 0.02 + pulse * 0.015;

        let r = 235,
          g = 229,
          b = 216;
        let waveBoost = 0;
        let tintR = 0,
          tintG = 0,
          tintB = 0,
          tintW = 0;

        for (const wv of activeWaves) {
          const cellPos = cell.x * wv.nx + cell.y * wv.ny;
          const d = cellPos - wv.pos;
          const ad = Math.abs(d);
          if (ad < wv.bandWidth) {
            const sigma = 30;
            const glow = Math.exp(-(d * d) / (2 * sigma * sigma));
            const crest = Math.max(0, 1 - ad / wv.bandWidth);
            const intensity = glow * 0.85 + crest * 0.15;

            waveBoost += intensity * 0.85;
            tintR += wv.color[0] * intensity;
            tintG += wv.color[1] * intensity;
            tintB += wv.color[2] * intensity;
            tintW += intensity;

            if (glow > 0.85 && !wv.flippedSet.has(i) && Math.random() < 0.18) {
              cell.val = cell.val === "1" ? "0" : "1";
              wv.flippedSet.add(i);
            }
          }
        }

        let lensFalloff = 0;
        if (mouseRef.current.inside) {
          const dx = cell.x - mx;
          const dy = cell.y - my;
          const d2 = dx * dx + dy * dy;
          if (d2 < LENS_R2) {
            lensFalloff = 1 - Math.sqrt(d2) / LENS_R;
          }
        }
        if (lensFalloff > 0) {
          opacity = Math.max(opacity, 0.08 + lensFalloff * 0.4);
        }

        if (waveBoost > 0) {
          opacity = Math.min(0.95, opacity + waveBoost);
          if (tintW > 0) {
            const tintMix = Math.min(1, waveBoost * 1.2);
            const tr = tintR / tintW;
            const tg = tintG / tintW;
            const tb = tintB / tintW;
            r = r * (1 - tintMix) + tr * tintMix;
            g = g * (1 - tintMix) + tg * tintMix;
            b = b * (1 - tintMix) + tb * tintMix;
          }
        }

        ctx.fillStyle = `rgba(${r | 0}, ${g | 0}, ${b | 0}, ${opacity})`;
        ctx.fillText(cell.val, cell.x, cell.y);
      }

      rafRef.current = requestAnimationFrame(render);
    };
    rafRef.current = requestAnimationFrame(render);

    return () => {
      cancelAnimationFrame(rafRef.current);
      window.removeEventListener("resize", handleResize);
      window.removeEventListener("mousemove", handleMove);
      canvas.removeEventListener("mouseleave", handleLeave);
    };
  }, []);

  useEffect(() => {
    messagesHistoryRef.current = messages;
  }, [messages]);

  const sendMessage = (text) => {
    const trimmed = text.trim();
    if (!trimmed || isStreaming || isThinking) return;
    setInput("");
    const nextHistory = [
      ...messagesHistoryRef.current,
      { role: "user", content: trimmed },
    ];
    setMessages(nextHistory);
    setIsThinking(true);
    workerRef.current?.postMessage({ type: "generate", data: nextHistory });
  };

  const interruptGeneration = () => {
    workerRef.current?.postMessage({ type: "interrupt" });
  };

  const resetChat = () => {
    setInput("");
    setMessages([]);
    messagesHistoryRef.current = [];
    setIsThinking(false);
    setIsStreaming(false);
    setTps(null);
    autoScrollRef.current = true;
    workerRef.current?.postMessage({ type: "reset" });
    setTimeout(() => inputRef.current?.focus(), 0);
  };

  const handleMessagesScroll = useCallback(() => {
    const container = messagesRef.current;
    if (!container) return;

    const distanceFromBottom =
      container.scrollHeight - container.scrollTop - container.clientHeight;
    autoScrollRef.current = distanceFromBottom < 96;
  }, []);

  useEffect(() => {
    if (!autoScrollRef.current) return;

    const container = messagesRef.current;
    if (!container) return;

    const id = requestAnimationFrame(() => {
      container.scrollTo({
        top: container.scrollHeight,
        behavior: isStreaming ? "auto" : "smooth",
      });
    });

    return () => cancelAnimationFrame(id);
  }, [messages, isThinking, isStreaming]);

  const SUGGESTIONS = [
    "Who are you?",
    "Write a short poem about AI.",
    "What is the capital of France?",
    "Solve x^2 - 5x + 6 = 0.",
  ];

  const handleKeyDown = (e) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      sendMessage(input);
    }
  };

  let tpsDisplay = "—";
  if (isThinking) tpsDisplay = "···";
  else if (tps !== null) tpsDisplay = tps.toFixed(1);

  return (
    <>
      <div className="prism-root">
        {/* ============= HERO ============= */}
        <section className="hero">
          <canvas ref={canvasRef} className="digit-canvas" />
          <div className="grain" />
          <div className="vignette" />

          <div className={`hero-content ${fontsReady ? "is-ready" : ""}`}>
            <div className="hero-badge">
              14× less memory · 8× faster · 5× less energy
            </div>

            <h1>
              1-bit LLMs,
              <br />
              in your <span className="accent italic-d">browser.</span>
            </h1>

            <p className="lede">
              Run 1-bit Bonsai LLMs (1.7B, 4B, 8B) entirely locally in your
              browser on WebGPU, powered by Transformers.js.
            </p>

            <button className="btn btn-primary" onClick={scrollToPlayground}>
              Try the demo
              <span className="arrow">↓</span>
            </button>
          </div>
        </section>

        {/* ============= PLAYGROUND ============= */}
        <section className="playground" ref={playgroundRef}>
          {stage === "select" && (
            <div className="select-view">
              <div className="select-head">
                <div className="eyebrow">Choose a model</div>
                <h2>
                  Load <span className="em">locally.</span>
                </h2>
                <p>
                  Each Bonsai model runs entirely in your browser via WebGPU.
                  Pick a size — smaller loads faster, larger reasons better.
                </p>
              </div>

              <div className="model-grid">
                {MODELS.map((m) => (
                  <div
                    key={m.id}
                    className={`model-card ${selectedId === m.id ? "selected" : ""} ${m.comingSoon ? "disabled" : ""}`}
                    onClick={() => !m.comingSoon && setSelectedId(m.id)}
                  >
                    {m.comingSoon && (
                      <div className="mc-ribbon">Coming soon</div>
                    )}
                    <div className="check">
                      {selectedId === m.id ? "✓" : ""}
                    </div>
                    <div className="mc-size">
                      {m.params}
                      <span className="gb">{m.size}</span>
                    </div>
                    <div className="mc-name">{m.name}</div>
                    <div className="mc-blurb">{m.blurb}</div>
                  </div>
                ))}
              </div>

              <div className="select-actions">
                <button
                  className="btn btn-primary"
                  onClick={startLoading}
                  disabled={!selectedId}
                >
                  Load {selected.name}
                  <span className="arrow">→</span>
                </button>
                <div className="note">No data leaves your device</div>
              </div>
            </div>
          )}

          {stage === "loading" && (
            <div className="load-view">
              <div className="load-card">
                <div
                  className={`${PRISM_GLYPH_CLASS} mx-auto mb-7 animate-spin [animation-duration:8s]`}
                />
                <div className="label">Initializing</div>
                <h3>
                  {selected.name.split(" ")[0]}{" "}
                  <span className="em">{selected.name.split(" ")[1]}</span>
                </h3>
                <div className="progress">
                  <div className="fill" style={{ width: `${loadProgress}%` }} />
                </div>
                <div className="load-row">
                  <span>
                    {loadTotal > 0 && loadProgress < 100
                      ? `${formatBytes(loadLoaded)} / ${formatBytes(loadTotal)}`
                      : loadMessage}
                  </span>
                  <span className="pct">{Math.round(loadProgress)}%</span>
                </div>
              </div>
            </div>
          )}

          {stage === "error" && (
            <div className="load-view">
              <div className="load-card">
                <div className="label">Error</div>
                <h3>
                  Could not <span className="em">load.</span>
                </h3>
                <p
                  style={{
                    color: "var(--cream-dim)",
                    fontSize: 14,
                    margin: "0 0 18px",
                  }}
                >
                  {errorMessage}
                </p>
                <button
                  className="btn btn-primary"
                  onClick={() => {
                    setErrorMessage(null);
                    setStage("select");
                  }}
                >
                  Back
                </button>
              </div>
            </div>
          )}

          {stage === "chat" && (
            <div className="chat-view">
              <div className="pg-header-bar">
                <h2 className="pg-title">
                  The <span className="em">playground.</span>
                </h2>
                <div className="pg-mini">
                  <span className="chip">
                    <span className="k">model</span>
                    {selected.name}
                  </span>
                  <span className="chip">
                    <span className="k">size</span>
                    {selected.size}
                  </span>
                </div>
              </div>

              <div className="chat">
                <div className="chat-bar">
                  <div className="left">
                    <span className="dots">
                      <span />
                      <span />
                      <span />
                    </span>
                    <span>BONSAI WebGPU</span>
                  </div>
                  <div className="right">
                    <span className="tps-value">{tpsDisplay}</span>
                    <span className="tps-label">Tokens / sec</span>
                  </div>
                </div>

                <div
                  ref={messagesRef}
                  className="messages"
                  onScroll={handleMessagesScroll}
                >
                  {messages.length === 0 && !isThinking ? (
                    <div className="flex flex-1 flex-col items-center justify-center gap-5 px-0 py-6 text-center">
                      <div className={PRISM_GLYPH_CLASS} />
                      <div
                        className="text-[32px] leading-none tracking-[-0.02em] text-[var(--cream)]"
                        style={{ fontFamily: '"Instrument Serif", serif' }}
                      >
                        How can I help you?
                      </div>
                      <div
                        className="mt-[-6px] text-[12px] leading-[1.25] tracking-[0.06em] text-[var(--muted)] uppercase"
                        style={{ fontFamily: '"JetBrains Mono", monospace' }}
                      >
                        Talk to a 1-bit model
                      </div>
                      <div className="mt-1 grid w-full max-w-[540px] grid-cols-1 gap-2.5 sm:grid-cols-2">
                        {SUGGESTIONS.map((s) => (
                          <button
                            key={s}
                            className="rounded-md border border-[var(--line)] bg-[var(--bg-3)] px-4 py-[13px] text-left text-[13px] leading-[1.3] text-[var(--cream-dim)] transition-all duration-200 hover:translate-y-[-1px] hover:border-[var(--line-2)] hover:bg-[rgba(235,229,216,0.015)] hover:text-[var(--cream)]"
                            style={{ fontFamily: '"Geist", sans-serif' }}
                            onClick={() => sendMessage(s)}
                          >
                            {s}
                          </button>
                        ))}
                      </div>
                    </div>
                  ) : (
                    <>
                      {messages.map((m, i) => {
                        return (
                          <div key={i} className={`message ${m.role}`}>
                            <span className="role">
                              {m.role === "user" ? "You" : selected.name}
                            </span>
                            <div className="bubble">
                              {m.role === "assistant" ? (
                                <Streamdown
                                  className="streamdown-content"
                                  plugins={STREAMDOWN_PLUGINS}
                                  controls={false}
                                  parseIncompleteMarkdown
                                >
                                  {m.content}
                                </Streamdown>
                              ) : (
                                m.content
                              )}
                            </div>
                          </div>
                        );
                      })}
                      {isThinking && (
                        <div className="message assistant">
                          <span className="role">{selected.name}</span>
                          <div className="thinking">
                            <span>thinking</span>
                            <span className="bits">
                              <span>1</span>
                              <span>0</span>
                              <span>1</span>
                              <span>1</span>
                              <span>0</span>
                            </span>
                          </div>
                        </div>
                      )}
                    </>
                  )}
                </div>

                <div className="composer">
                  <textarea
                    ref={inputRef}
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    onKeyDown={handleKeyDown}
                    placeholder="Ask the 1-bit model anything…"
                    rows={1}
                  />
                  {isStreaming || isThinking ? (
                    <button className="send" onClick={interruptGeneration}>
                      Stop
                      <span className="send-icon">
                        <StopIcon />
                      </span>
                    </button>
                  ) : !input.trim() ? (
                    <button className="send" onClick={resetChat}>
                      Reset
                      <span className="send-icon">
                        <ResetIcon />
                      </span>
                    </button>
                  ) : (
                    <button
                      className="send"
                      onClick={() => sendMessage(input)}
                    >
                      Send
                      <span className="send-icon">
                        <SendIcon />
                      </span>
                    </button>
                  )}
                </div>
              </div>
            </div>
          )}
        </section>
      </div>
    </>
  );
}
