/* crabcc landing — page sections. Exports to window. */
const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;
const DS = window.CrabccDesignSystem_325a37;

/* scroll reveal — adds .is-in when element scrolls into view */
function useReveal() {
  useEffectS(() => {
    const root = document.documentElement;
    root.classList.add("has-js");
    const els = document.querySelectorAll(".reveal");
    if (!("IntersectionObserver" in window)) {
      els.forEach((e) => e.classList.add("is-in"));
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((en) => {
          if (en.isIntersecting) { en.target.classList.add("is-in"); io.unobserve(en.target); }
        });
      },
      { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
    );
    els.forEach((e) => io.observe(e));
    // safety net: never leave content hidden (e.g. throttled/background tab)
    const safety = setTimeout(() => els.forEach((e) => e.classList.add("is-in")), 2600);
    return () => { io.disconnect(); clearTimeout(safety); };
  }, []);
}

/* ---------------- Header ---------------- */
function Header({ dark, onToggle }) {
  const { Button, Switch } = DS;
  return (
    <header className="site-header">
      <div className="wrap site-header__inner">
        <a className="brand" href="#top">
          <img className="brand__logo" src={dark ? "assets/logo-holo.svg" : "assets/logo.svg"} alt="" />
          <span className="brand__name">crabcc</span>
        </a>
        <nav className="nav">
          <a className="nav__link" href="#primitives">primitives</a>
          <a className="nav__link" href="#speed">benchmarks</a>
          <a className="nav__link" href="#how">how it works</a>
          <a className="nav__link" href="#agents">mcp</a>
        </nav>
        <div className="header-actions">
          <Switch checked={dark} onChange={onToggle} label={dark ? "dark" : "light"} />
          <Button as="a" href="https://github.com/crabcc-labs/crabcc" variant="secondary" size="sm"
            iconLeft={<GitHubIcon />}>
            star
          </Button>
        </div>
      </div>
    </header>
  );
}

/* ---------------- Section heading ---------------- */
function Head({ eyebrow, title, sub, id, copy }) {
  return (
    <div className={"section__head reveal" + (copy ? " section__head--row" : "")} id={id}>
      <div>
        <span className="eyebrow">{eyebrow}</span>
        <h2 className="section__title">{title}</h2>
        {sub && <p className="section__sub">{sub}</p>}
      </div>
      {copy && window.SectionCopy ? <window.SectionCopy mdKey={copy} /> : null}
    </div>
  );
}

/* ---------------- Primitives ---------------- */
function Primitives() {
  const { Badge } = DS;
  const items = [
    {
      cmd: "sym", arg: "Foo",
      kind: "lookup",
      desc: "Find a symbol by name. Returns its kind, file and line — not every text match, the actual definition.",
      ex: <><span className="tk-prompt">$ </span><span className="tk-cmd">crabcc sym Store</span>{"\n"}<span className="tk-punc">{"{ "}</span><span className="tk-key">"kind"</span><span className="tk-punc">: </span><span className="tk-str">"class"</span><span className="tk-punc">, </span><span className="tk-key">"line"</span><span className="tk-punc">: </span><span className="tk-num">28</span><span className="tk-punc">{" }"}</span></>,
    },
    {
      cmd: "refs", arg: "Foo",
      kind: "usages",
      desc: "Every place a symbol is referenced, deduped by file. Pair with --files-only to collapse to a path list.",
      ex: <><span className="tk-prompt">$ </span><span className="tk-cmd">crabcc refs Store </span><span className="tk-flag">--count</span>{"\n"}<span className="tk-punc">{"{ "}</span><span className="tk-key">"count"</span><span className="tk-punc">: </span><span className="tk-num">23</span><span className="tk-punc">{" }"}</span></>,
    },
    {
      cmd: "callers", arg: "fn",
      kind: "call-graph",
      desc: "Who calls this function. Walk the call graph upward without loading a single file into the model.",
      ex: <><span className="tk-prompt">$ </span><span className="tk-cmd">crabcc callers handleAuth</span>{"\n"}<span className="tk-str">"login"</span><span className="tk-punc">, </span><span className="tk-str">"refresh"</span><span className="tk-punc">, </span><span className="tk-str">"middleware"</span></>,
    },
    {
      cmd: "outline", arg: "file.rs",
      kind: "structure",
      desc: "The shape of a file — its symbols in order, nested. The cheapest way to orient before reading.",
      ex: <><span className="tk-prompt">$ </span><span className="tk-cmd">crabcc outline store.rs</span>{"\n"}<span className="tk-dim">class Store</span>{"\n"}<span className="tk-dim">  fn open · fn get · fn put</span></>,
    },
  ];
  return (
    <section className="section" id="primitives">
      <div className="wrap">
        <Head
          eyebrow="four primitives"
          title="Four questions an agent actually asks"
          copy="primitives"
          sub="No regex dialect, no flag soup. crabcc exposes exactly the four queries that matter when you're navigating code by symbol — each one returns typed JSON, shaped to the smallest answer."
        />
        <div className="prim-grid">
          {items.map((it, i) => (
            <div className="prim reveal" key={it.cmd} style={{ transitionDelay: (i * 70) + "ms" }}>
              <div className="prim__head">
                <span className="prim__cmd"><span className="accent">crabcc</span> {it.cmd}</span>
                <Badge tone="neutral">{it.kind}</Badge>
              </div>
              <p className="prim__desc">{it.desc}</p>
              <pre className="prim__ex">{it.ex}</pre>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------------- Benchmark ---------------- */
function Benchmark() {
  const rows = [
    { task: "find all callers of a hot function", q: "crabcc callers", grep: "1,840ms", crab: "0.4ms", x: "4412×", w: 100 },
    { task: "locate a class definition", q: "crabcc sym", grep: "612ms", crab: "1.9ms", x: "322×", w: 62 },
    { task: "list every reference, deduped", q: "crabcc refs", grep: "988ms", crab: "2.1ms", x: "470×", w: 74 },
    { task: "outline a 2k-line file", q: "crabcc outline", grep: "47ms", crab: "1.0ms", x: "47×", w: 30 },
  ];
  return (
    <section className="section" id="speed">
      <div className="wrap">
        <Head
          eyebrow="benchmarks"
          title="Up to 4412× faster than grep -rn"
          copy="speed"
          sub="Measured on a 13,000-file Rust monorepo, warm index. grep -rn and find . -name are the wrong defaults for an LLM — they re-scan the world on every question. crabcc answers from a SQLite symbol store."
        />
        <div className="bench reveal">
          <div className="bench__row bench__row--head">
            <span>task</span>
            <span className="bench__num bench__head-num" style={{ textAlign: "right" }}>grep -rn</span>
            <span className="bench__num bench__head-num" style={{ textAlign: "right" }}>crabcc</span>
            <span style={{ textAlign: "right" }}>speedup</span>
          </div>
          {rows.map((r) => (
            <div className="bench__row" key={r.task}>
              <span className="bench__task">{r.task}<code>{r.q}</code></span>
              <span className="bench__num">{r.grep}</span>
              <span className="bench__crab">{r.crab}</span>
              <span className="bench__speed">
                <b>{r.x}</b>
                <div className="bench__bar reveal-bar" style={{ width: r.w + "%", marginLeft: "auto" }}></div>
              </span>
            </div>
          ))}
        </div>
        <p className="footnote reveal">
          <span className="footnote__mark">how</span>
          <span><b>Method:</b> median of 20 warm-cache runs on a 13,142-file Rust monorepo (M2 Pro, NVMe). <code>grep -rn</code> re-walks the tree each call; crabcc answers from a pre-built SQLite symbol store, so its cost is a single indexed lookup. The headline <b>4412×</b> is the best case — a hot-function caller query; smaller jobs like a file outline land nearer <b>47×</b>. Cold-index runs are excluded.</span>
        </p>
      </div>
    </section>
  );
}

/* ---------------- Token savings ---------------- */
function Savings() {
  return (
    <section className="section section--tight" id="savings">
      <div className="wrap">
        <Head
          eyebrow="token economy"
          title="85% fewer bytes to the model"
          copy="savings"
          sub="The win isn't just latency — it's what you don't send. A grep dump pours thousands of lines into the context window. crabcc hands back the answer, and only the answer."
        />
        <div className="savings reveal">
          <div className="save-card save-card--bad">
            <div className="save-card__label"><span>✕</span> grep -rn "handleAuth"</div>
            <div className="save-card__big">62,541<span className="save-card__unit"> bytes</span></div>
            <div className="save-card__meta">1,204 matching lines · most irrelevant · re-read every turn</div>
            <div className="save-card__track"><div className="save-card__fill" style={{ width: "100%" }}></div></div>
          </div>
          <div className="save-card save-card--good">
            <div className="save-card__label"><span>✓</span> crabcc callers handleAuth --count</div>
            <div className="save-card__big">253<span className="save-card__unit"> bytes</span></div>
            <div className="save-card__meta">the exact answer · typed JSON · −99.6%</div>
            <div className="save-card__track"><div className="save-card__fill" style={{ width: "4%" }}></div></div>
          </div>
        </div>
        <p className="footnote reveal">
          <span className="footnote__mark">how</span>
          <span><b>Byte count</b> is the raw payload handed to the model. The <code>grep</code> figure is the actual stdout for that query on the same monorepo (1,204 matching lines); the crabcc figure is its JSON response. Across a 50-query agent trace the mean reduction was <b>−85%</b> — this single call is a best case at <b>−99.6%</b>. Fewer bytes in means fewer tokens billed and more context budget left for reasoning.</span>
        </p>
      </div>
    </section>
  );
}

/* ---------------- How it works ---------------- */
function HowItWorks() {
  const steps = [
    { n: "01", t: "index", d: <>Run <code>crabcc index</code> once. A Rust walker parses every file with tree-sitter and writes symbols, refs and a call graph into a local SQLite store. ~250ms no-op on re-runs.</> },
    { n: "02", t: "query", d: <>Ask one of the four primitives. crabcc resolves it against the store in single-digit milliseconds — never re-reading source unless you ask it to.</> },
    { n: "03", t: "shape", d: <>Add <code>--count</code>, <code>--files-only</code> or <code>--limit</code> to collapse a 16k-token result to ~3 tokens. Pick the smallest shape the question allows.</> },
  ];
  return (
    <section className="section" id="how">
      <div className="wrap">
        <Head
          eyebrow="how it works"
          title="Index once. Query in milliseconds."
          sub={"crabcc understands symbols \u2014 class User and the string \u201CUser\u201D are not the same thing. That understanding is built once, at index time, and reused on every query."}
        />
        <div className="steps">
          {steps.map((s, i) => (
            <div className="step reveal" key={s.n} style={{ transitionDelay: (i * 80) + "ms" }}>
              <div className="step__n">{s.n}</div>
              <div className="step__bar"></div>
              <div className="step__title">{s.t}</div>
              <p className="step__desc">{s.d}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------------- MCP / agents ---------------- */

/* tiny JSON syntax highlighter -> HTML string */
function highlightJSON(obj) {
  const json = JSON.stringify(obj, null, 2);
  const esc = json.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  return esc.replace(
    /("(\\.|[^"\\])*")(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d+)?/g,
    (m, str, _g2, colon, kw) => {
      if (str !== undefined && colon) return '<span class="jk">' + str + '</span><span class="jp">' + colon + '</span>';
      if (str !== undefined) return '<span class="js">' + str + '</span>';
      if (kw !== undefined) return '<span class="jb">' + m + '</span>';
      return '<span class="jn">' + m + '</span>';
    }
  );
}

const SCHEMA_FALLBACK = {
  $schema: "https://modelcontextprotocol.io/schema/2025-06/tools.json",
  server: { name: "crabcc", version: "0.4.0", protocol: "jsonrpc-2.0", transport: "stdio" },
  capabilities: { tools: { listChanged: false }, stateless: true, readOnly: true },
  tools: [
    { name: "sym", description: "Look up a symbol by name. Returns kind, file and line.", inputSchema: { type: "object", properties: { name: { type: "string" }, limit: { type: "integer", minimum: 1 } }, required: ["name"] } },
    { name: "refs", description: "Every reference to a symbol, deduped by file.", inputSchema: { type: "object", properties: { name: { type: "string" }, files_only: { type: "boolean" }, count: { type: "boolean" } }, required: ["name"] } },
    { name: "callers", description: "Who calls this function. Walks the call graph upward.", inputSchema: { type: "object", properties: { name: { type: "string" }, count: { type: "boolean" }, depth: { type: "integer" } }, required: ["name"] } },
    { name: "outline", description: "The symbols of a file, in order.", inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } }
  ]
};

function SchemaPreview() {
  const [schema, setSchema] = useStateS(null);
  useEffectS(() => {
    let alive = true;
    fetch("mcp-schema.json")
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((j) => { if (alive) setSchema(j); })
      .catch(() => { if (alive) setSchema(SCHEMA_FALLBACK); });
    return () => { alive = false; };
  }, []);
  const data = schema || SCHEMA_FALLBACK;
  const toolCount = (data.tools || []).length;
  return (
    <div className="schema reveal">
      <div className="schema__bar">
        <span className="term__dot term__dot--r"></span>
        <span className="term__dot term__dot--y"></span>
        <span className="term__dot term__dot--g"></span>
        <span className="schema__path"><span className="dim">crabcc-labs/crabcc/</span>mcp-schema.json</span>
        <span className="schema__branch">⎇ main</span>
        <span className="schema__spacer"></span>
        <a className="schema__raw" href="mcp-schema.json" target="_blank" rel="noopener">raw ↗</a>
      </div>
      <pre className="schema__body" dangerouslySetInnerHTML={{ __html: highlightJSON(data) }}></pre>
      <div className="schema__foot">
        <span style={{ color: "var(--c-str)" }}>●</span> served statically with the site · {toolCount} tools · JSON-RPC 2.0 · read-only
      </div>
    </div>
  );
}

function Agents() {
  const { Badge, LiveDot } = DS;
  const map = [
    ["where is this defined?", "crabcc sym"],
    ["who uses it?", "crabcc refs"],
    ["who calls this function?", "crabcc callers"],
    ["just give me the count", "--count"],
    ["just the file list", "--files-only"],
  ];
  return (
    <section className="section" id="agents">
      <div className="wrap">
        <div className="mcp-grid">
          <div className="reveal">
            <span className="eyebrow">mcp server</span>
            <h2 className="section__title">Built for the agent, not the human</h2>
            <p className="section__sub" style={{ marginBottom: "var(--space-6)" }}>
              The same four code paths ship over JSON-RPC 2.0 as an MCP server. Point Claude Code, Cursor, or
              any MCP client at it and the model maps a <em>want</em> straight to a <em>command</em> — no
              shell parsing, no token-bloated grep dumps in the loop.
            </p>
            <div style={{ display: "flex", gap: "var(--space-3)", alignItems: "center", flexWrap: "wrap" }}>
              <LiveDot live={true} label="mcp · stdio" />
              <Badge tone="accent">JSON-RPC 2.0</Badge>
              <Badge tone="neutral">stateless</Badge>
            </div>
            <p className="footnote" style={{ marginTop: "var(--space-5)" }}>
              <span className="footnote__mark">why</span>
              <span>Same Rust code paths as the CLI, exposed as four MCP <b>tools</b> over JSON-RPC 2.0 on stdio. The client sends <code>tools/call</code> with a tool name and typed args; crabcc replies with the same compact JSON the CLI prints. <b>Stateless</b> means no session to warm up and nothing to invalidate — every call is an independent indexed lookup, so it drops cleanly into an agent loop without shell parsing or grep dumps.</span>
            </p>
          </div>
          <table className="maptable reveal">
            <thead>
              <tr><th>the agent wants…</th><th>it runs</th></tr>
            </thead>
            <tbody>
              {map.map(([want, cmd]) => (
                <tr key={cmd}><td>{want}</td><td>{cmd}</td></tr>
              ))}
            </tbody>
          </table>
        </div>
        <SchemaPreview />
      </div>
    </section>
  );
}

/* ---------------- Honest losses (removed) ---------------- */

/* ---------------- Future tasks (frontend) ---------------- */
function FutureTasks() {
  const { Badge } = DS;
  const tasks = [
    {
      n: "01",
      files: ["llms.txt"],
      title: "Agent discovery",
      desc: <>Publish <code>/llms.txt</code> (and <code>/llms-full.txt</code>) at the site root so coding agents can discover the page structure, the four primitives and the install one-liner without scraping HTML.</>,
      path: "peterl.dev/llms.txt",
    },
    {
      n: "02",
      files: ["claude.md", "agents.md", "mcp.json"],
      title: "Hosted agent-context bundle",
      desc: <>Host the crabcc agent-context files at a dedicated, stable path so any MCP client — Claude Code, Cursor — can point at crabcc in one line. Mirror them from the crabcc repo on deploy so they never drift.</>,
      path: "peterl.dev/{XYZ}/",
    },
    {
      n: "03",
      files: ["flake.nix"],
      title: "Reproducible install",
      desc: <>Reference a Nix flake for a reproducible <code>crabcc</code> install plus a dev shell, surfaced from the hero install block as an alternative to <code>cargo install</code>.</>,
      path: "peterl.dev/{XYZ}/flake.nix",
    },
  ];
  return (
    <section className="section" id="roadmap">
      <div className="wrap">
        <Head
          eyebrow="future tasks · frontend"
          title="Ship list for the build team"
          sub="What's left to wire before launch. Each lands as a small, self-contained PR — the full brief lives in the repo's AGENTS.md and KICKOFF.md."
        />
        <div className="tasks">
          {tasks.map((t) => (
            <div className="task reveal" key={t.n}>
              <div className="task__rail">
                <span className="task__n">{t.n}</span>
                <Badge tone="warn">planned</Badge>
              </div>
              <div className="task__main">
                <div className="task__title">
                  {t.title}
                  <span className="task__files">
                    {t.files.map((f) => <code key={f}>{f}</code>)}
                  </span>
                </div>
                <p className="task__desc">{t.desc}</p>
                <div className="task__path"><span className="task__path-k">host</span> {t.path}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------------- CTA band ---------------- */
function CtaBand({ dark }) {
  const { Button, ShaderBackground } = DS;
  return (
    <section className="section">
      <div className="wrap">
        <div className="cta-band reveal">
          <div className="cta-band__shader">
            <ShaderBackground theme={dark ? "dark" : "light"} intensity={0.3} />
          </div>
          <h2>Stop grepping. Start asking.</h2>
          <p>Index your repo in seconds and give your agent a symbol-aware map of the codebase — fast, exact, and frugal with every token.</p>
          <InstallLine cmd="cargo install crabcc" />
          <div className="cta-band__row">
            <Button as="a" href="https://github.com/crabcc-labs/crabcc" variant="primary" size="lg" iconLeft={<GitHubIcon />}>view on github</Button>
            <Button as="a" href="#how" variant="secondary" size="lg">read the docs</Button>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------------- Footer ---------------- */
function Footer({ dark }) {
  const cols = [
    { h: "product", links: ["primitives", "benchmarks", "mcp server", "changelog"] },
    { h: "docs", links: ["install", "cli reference", "agents.md", "ghostty theme"] },
    { h: "project", links: ["github", "issues", "license · mit", "@crabcc"] },
  ];
  return (
    <footer className="site-footer">
      <div className="wrap">
        <div className="foot-grid">
          <div className="foot-brand">
            <a className="brand" href="#top">
              <img className="brand__logo" src={dark ? "assets/logo-holo.svg" : "assets/logo.svg"} alt="" />
              <span className="brand__name">crabcc</span>
            </a>
            <p>Symbol index for AI coding agents. A small Rust CLI + MCP server that makes your codebase fast and cheap to navigate. 🦀</p>
          </div>
          {cols.map((c) => (
            <div className="foot-col" key={c.h}>
              <h5>{c.h}</h5>
              {c.links.map((l) => <a href="#" key={l}>{l}</a>)}
            </div>
          ))}
        </div>
        <div className="foot-bottom">
          <span>© 2026 crabcc-labs · built in Rust + SQLite + Tantivy</span>
          <span>fast · exact · frugal</span>
        </div>
      </div>
    </footer>
  );
}

/* ---------------- GitHub icon ---------------- */
function GitHubIcon() {
  return (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true">
      <path d="M12 .5C5.37.5 0 5.78 0 12.29c0 5.2 3.44 9.6 8.21 11.16.6.11.82-.25.82-.56 0-.28-.01-1.02-.02-2-3.34.71-4.04-1.58-4.04-1.58-.55-1.37-1.34-1.74-1.34-1.74-1.09-.73.08-.72.08-.72 1.2.08 1.84 1.21 1.84 1.21 1.07 1.8 2.81 1.28 3.5.98.11-.76.42-1.28.76-1.58-2.67-.3-5.47-1.31-5.47-5.81 0-1.28.47-2.33 1.23-3.15-.12-.3-.53-1.5.12-3.13 0 0 1.01-.32 3.3 1.2a11.6 11.6 0 0 1 6 0c2.29-1.52 3.3-1.2 3.3-1.2.65 1.63.24 2.83.12 3.13.77.82 1.23 1.87 1.23 3.15 0 4.51-2.81 5.5-5.49 5.79.43.37.81 1.1.81 2.22 0 1.6-.01 2.89-.01 3.28 0 .31.21.68.83.56A12.01 12.01 0 0 0 24 12.29C24 5.78 18.63.5 12 .5z"/>
    </svg>
  );
}

Object.assign(window, { Header, Head, Primitives, Benchmark, Savings, HowItWorks, Agents, FutureTasks, CtaBand, Footer, GitHubIcon, useReveal });
