// API Portal — three auth options, all stored in localStorage:
//   1. Graphite key (signed up via /signup, used against our /chat etc.)
//   2. Anthropic API key (BYO — pay your own Anthropic bill)
//   3. OpenAI API key (BYO — pay your own OpenAI bill)
//
// Chat in the portal UI works with the graphite key (server-side agent
// loop calls Claude with our key + MCP tools). For BYO Anthropic/OpenAI
// users we surface MCP config snippets — they run their own LLM client
// (Claude Code, Codex CLI, Cursor, etc.) and connect graphite as a tool.
//
// All API URLs come from window.__ENV.CENTRAL_API_URL — no hardcoded
// hostnames here.

const PROVIDERS = {
  graphite: { id: 'graphite', label: 'Graphite key', storage: 'graphite.key', prefix: 'sk-' },
  anthropic: { id: 'anthropic', label: 'Anthropic key', storage: 'graphite.anthropicKey', prefix: 'sk-ant-' },
  openai: { id: 'openai', label: 'OpenAI key', storage: 'graphite.openaiKey', prefix: 'sk-' },
};
const ACTIVE_PROVIDER_KEY = 'graphite.activeProvider';

function loadKey(provider) {
  try { return localStorage.getItem(PROVIDERS[provider].storage) || ''; } catch { return ''; }
}
function saveKey(provider, k) {
  try { localStorage.setItem(PROVIDERS[provider].storage, k); } catch { }
}
function clearStoredKey(provider) {
  try { localStorage.removeItem(PROVIDERS[provider].storage); } catch { }
}
function loadActiveProvider() {
  try { return localStorage.getItem(ACTIVE_PROVIDER_KEY) || 'graphite'; } catch { return 'graphite'; }
}
function saveActiveProvider(p) {
  try { localStorage.setItem(ACTIVE_PROVIDER_KEY, p); } catch { }
}

// Graphite endpoints — built relative to CENTRAL_API_URL at call time so
// env.js changes don't require a rebuild.
function apiUrl(path) {
  const base = (window.__ENV && window.__ENV.CENTRAL_API_URL || '').replace(/\/$/, '');
  return base + path;
}

async function callGraphite(path, { key, method = 'GET', body } = {}) {
  try {
    const res = await fetch(apiUrl(path), {
      method,
      headers: {
        ...(key ? { 'X-API-Key': key } : {}),
        ...(body ? { 'Content-Type': 'application/json' } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!res.ok) {
      let detail = `${res.status}`;
      try { const j = await res.json(); if (j.detail) detail = `${res.status}: ${j.detail}`; } catch { }
      return { ok: false, error: detail };
    }
    return { ok: true, data: await res.json() };
  } catch (e) {
    return { ok: false, error: e.message || 'Network error' };
  }
}

function useCopy() {
  const [copied, setCopied] = React.useState(false);
  const copy = React.useCallback((text) => {
    navigator.clipboard?.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1400);
  }, []);
  return [copied, copy];
}


// ──────────────────────── main ────────────────────────

window.Portal = function Portal({ navigate }) {
  // One key per provider, all persisted independently.
  const [keys, setKeys] = React.useState({
    graphite: loadKey('graphite'),
    anthropic: loadKey('anthropic'),
    openai: loadKey('openai'),
  });
  const [active, setActive] = React.useState(loadActiveProvider());

  function setKey(provider, value) {
    setKeys((k) => ({ ...k, [provider]: value }));
    if (value) saveKey(provider, value);
    else clearStoredKey(provider);
  }
  function pickProvider(provider) {
    setActive(provider);
    saveActiveProvider(provider);
  }

  const activeKey = keys[active];

  return (
    <div className="page">
      <section className="section" style={{ paddingTop: 96 }}>
        <div className="container">
          <div className="section-head" style={{ marginBottom: 40 }}>
            <h2>Use the graph without writing code.</h2>
            <p className="lede">
              Pick how you want to authenticate, ask the graph questions, and copy code samples — all from this page.
              Talks to <code className="mono" style={{ fontSize: 13, color: 'var(--fg-dim)' }}>{(window.__ENV && window.__ENV.CENTRAL_API_URL) || ''}</code> directly.
            </p>
          </div>

          <AuthTabs active={active} onPick={pickProvider} />
          <AuthPanel active={active} keys={keys} setKey={setKey} />

          <StatusRow />
          <ChatPanel active={active} apiKey={activeKey} />
          <McpSetup active={active} apiKey={activeKey} />
          <QuickStart active={active} apiKey={activeKey} />
        </div>
      </section>
      <Footer navigate={navigate} />
    </div>
  );
};


// ──────────────────────── MCP setup guide ────────────────────────
//
// Walks the user through wiring the Graphite MCP server into their own
// Claude Code / Claude Desktop / Cursor / Codex CLI. The MCP server lives
// in this repo at sn43_owner/mcp_server/ and exposes 7 tools that any
// MCP-compatible client can call. Once set up, the user's Claude can
// answer questions like "what's NVDA's exposure to TSMC" by calling
// these tools, without us proxying any LLM traffic.

const MCP_TOOLS = [
  { name: 'search_entities', desc: 'Free-text search across entity names, tickers, sectors, descriptions.' },
  { name: 'get_entity', desc: 'Fetch the full record for a specific entity by ID (e.g. company:NVDA).' },
  { name: 'get_relationships', desc: 'List every edge attached to an entity (employs, supplies, depends_on, …).' },
  { name: 'get_facts', desc: 'Retrieve the typed fact log for an entity (revenue, headcount, etc).' },
  { name: 'find_path', desc: 'Shortest path between two entities through the relationship graph.' },
  { name: 'exposure_analysis', desc: '1st + 2nd-degree neighborhood with sector breakdown — supply-chain risk view.' },
  { name: 'compare_entities', desc: 'Shared connections + path distance + direct relationships between two entities.' },
];

const SAMPLE_PROMPTS = [
  "What's NVIDIA's supply-chain exposure to TSMC?",
  "Find the shortest path from company:AAPL to company:ASML.",
  "Compare Microsoft and Google — who do they share board members with?",
  "Which companies are most affected by the EU AI Act?",
  "List every revenue-from edge for NVDA.",
];

function McpSetup({ active, apiKey }) {
  const apiBase = ((window.__ENV && window.__ENV.CENTRAL_API_URL) || '').replace(/\/$/, '');
  const k = (active === 'graphite' && apiKey) ? apiKey : 'YOUR_GRAPHITE_KEY';
  const [copiedConfig, copyConfig] = useCopy();

  // Same config works for Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json),
  // Claude Code (~/.claude/mcp.json), Cursor (~/.cursor/mcp.json), and
  // Codex CLI (~/.codex/config.json) — the JSON shape is identical, only
  // the file path differs.
  const config = `{
  "mcpServers": {
    "graphite": {
      "command": "graphite-mcp",
      "env": {
        "CENTRAL_SERVER_URL": "${apiBase}",
        "CUSTOMER_API_KEY": "${k}"
      }
    }
  }
}`;

  return (
    <div className="portal-section">
      <div className="portal-section-h">
        <div>
          <h3>Use with your own Claude</h3>
          <p className="portal-section-sub">
            Wire the Graphite MCP server into Claude Code, Claude Desktop, or Cursor. Your subscription LLM does the reasoning; Graphite just answers graph queries it asks for. Quota stays on your account.
          </p>
        </div>
      </div>

      <div className="mcp-steps">
        <McpStep n="1" title="Install the MCP server">
          <p className="mcp-p">
            One-time setup — installs the open-source <code className="mono">graphite-mcp</code> Python package. Adds a <code className="mono">graphite-mcp</code> command to your PATH that the MCP client launches as a subprocess.
          </p>
          <CodeBlock code={`pip install graphite-mcp

# or from source:
git clone https://github.com/GraphiteAI/graphite-mcp
cd graphite-mcp
pip install -e .`} />
        </McpStep>

        <McpStep n="2" title={`Get a Graphite key${active === 'graphite' && apiKey ? ' ✓' : ''}`}>
          <p className="mcp-p">
            {active === 'graphite' && apiKey
              ? 'You already have one selected — the config below has it pre-filled.'
              : 'Use the Graphite tab above to issue or paste a key. Without it, the MCP server can\'t authenticate to api.graphite-ai.net.'}
          </p>
        </McpStep>

        <McpStep n="3" title="Add Graphite to your Claude config">
          <p className="mcp-p">
            Drop this into the right config file for your client. Replace <code className="mono">/absolute/path/to/sn43_owner</code> with where you cloned the repo in step 1.
          </p>
          <ul className="mcp-paths mono">
            <li><b>Claude Code</b> → <code>~/.claude/mcp.json</code></li>
            <li><b>Claude Desktop</b> → <code>~/Library/Application Support/Claude/claude_desktop_config.json</code> (macOS)</li>
            <li><b>Cursor</b> → <code>~/.cursor/mcp.json</code></li>
            <li><b>Codex CLI</b> → <code>~/.codex/config.json</code></li>
          </ul>
          <div className="mcp-config-wrap">
            <button className="snippet-copy mono" onClick={() => copyConfig(config)} style={{ position: 'absolute', top: 10, right: 10 }}>
              {copiedConfig ? <><Icon name="check" size={13} /> Copied</> : 'Copy'}
            </button>
            <pre className="snippet-code mono"><code>{config}</code></pre>
          </div>
        </McpStep>

        <McpStep n="4" title="Restart your client and try a prompt">
          <p className="mcp-p">After restart, Claude can call the graph as tools. Sample prompts:</p>
          <div className="mcp-prompts">
            {SAMPLE_PROMPTS.map((p, i) => <span key={i} className="mcp-prompt mono">"{p}"</span>)}
          </div>
        </McpStep>
      </div>

      <div className="mcp-tools">
        <h4 className="mcp-tools-h">Available tools <span className="mono" style={{ color: 'var(--fg-mute)', fontWeight: 400, marginLeft: 8, fontSize: 12 }}>· {MCP_TOOLS.length} exposed</span></h4>
        <div className="mcp-tools-grid">
          {MCP_TOOLS.map((t) => (
            <div key={t.name} className="mcp-tool">
              <code className="mono mcp-tool-name">{t.name}</code>
              <span className="mcp-tool-desc">{t.desc}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function McpStep({ n, title, children }) {
  return (
    <div className="mcp-step">
      <div className="mcp-step-n mono">{n}</div>
      <div className="mcp-step-body">
        <h4 className="mcp-step-h">{title}</h4>
        {children}
      </div>
    </div>
  );
}

function CodeBlock({ code }) {
  const [copied, copy] = useCopy();
  return (
    <div className="mcp-config-wrap">
      <button className="snippet-copy mono" onClick={() => copy(code)} style={{ position: 'absolute', top: 10, right: 10 }}>
        {copied ? <><Icon name="check" size={13} /> Copied</> : 'Copy'}
      </button>
      <pre className="snippet-code mono"><code>{code}</code></pre>
    </div>
  );
}


// ──────────────────────── auth tabs ────────────────────────

function AuthTabs({ active, onPick }) {
  return (
    <div className="portal-auth-tabs">
      {Object.values(PROVIDERS).map((p) => (
        <button
          key={p.id}
          className={'portal-auth-tab' + (active === p.id ? ' on' : '')}
          onClick={() => onPick(p.id)}
        >
          {p.label}
        </button>
      ))}
    </div>
  );
}

function AuthPanel({ active, keys, setKey }) {
  if (active === 'graphite') return <GraphiteAuth currentKey={keys.graphite} setKey={(v) => setKey('graphite', v)} />;
  if (active === 'anthropic') return <ByoKeyAuth provider="anthropic" currentKey={keys.anthropic} setKey={(v) => setKey('anthropic', v)} />;
  if (active === 'openai') return <ByoKeyAuth provider="openai" currentKey={keys.openai} setKey={(v) => setKey('openai', v)} />;
  return null;
}


// ── graphite: signup or paste key ──

function GraphiteAuth({ currentKey, setKey }) {
  const [email, setEmail] = React.useState('');
  const [keyDraft, setKeyDraft] = React.useState('');
  const [issued, setIssued] = React.useState('');
  const [issueState, setIssueState] = React.useState('idle');

  async function requestKey(e) {
    e.preventDefault();
    if (!email) return;
    setIssueState('loading');
    const r = await callGraphite('/api/v1/signup', { method: 'POST', body: { email } });
    if (r.ok && r.data?.key) {
      setIssued(r.data.key);
      setIssueState('done');
    } else {
      setIssueState('error');
      alert("Couldn't issue a key: " + (r.error || 'unknown error'));
    }
  }
  function adoptIssued() { setKey(issued); setIssued(''); setIssueState('idle'); }
  function commitDraft() { if (keyDraft.trim()) { setKey(keyDraft.trim()); setKeyDraft(''); } }

  return (
    <div className="portal-grid">
      <PortalCard title="Get a Graphite key" subtitle="One key per email. Free tier: 100 queries/month">
        {issueState === 'done' ? (
          <div>
            <div className="portal-issued-warn">Save this key — it won't be shown again.</div>
            <KeyDisplay value={issued} />
            <button className="btn btn-accent" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }} onClick={adoptIssued}>
              Use this key on this page
            </button>
          </div>
        ) : (
          <form onSubmit={requestKey} className="portal-form">
            <label className="portal-label">Email address</label>
            <input
              type="email" required placeholder="you@company.com"
              value={email} onChange={(e) => setEmail(e.target.value)}
              className="portal-input"
            />
            <button type="submit" className="btn btn-accent" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }}>
              {issueState === 'loading' ? 'Issuing…' : 'Get API key'}
              <Icon name="arrow" />
            </button>
            <div className="portal-fine">By requesting a key you agree to fair-use. Keys are bound to your email and revocable.</div>
          </form>
        )}
      </PortalCard>

      <PortalCard title="Your Graphite key" subtitle="Stored locally in this browser only.">
        {currentKey ? (
          <div>
            <KeyDisplay value={currentKey} />
            <button className="btn btn-ghost" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }} onClick={() => setKey('')}>Remove</button>
          </div>
        ) : (
          <div className="portal-form">
            <label className="portal-label">Paste an existing key</label>
            <input
              type="text" placeholder="sk-…" value={keyDraft}
              onChange={(e) => setKeyDraft(e.target.value)}
              className="portal-input mono"
            />
            <button className="btn btn-ghost" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }} onClick={commitDraft}>
              Save key locally
            </button>
            <div className="portal-fine">Stored only in your browser — never sent to graphite servers from this page.</div>
          </div>
        )}
      </PortalCard>
    </div>
  );
}


// ── BYO Anthropic/OpenAI ──

const BYO_COPY = {
  anthropic: {
    title: 'Connect Anthropic',
    sub: 'Bring your own Anthropic API key. Pay-per-token on YOUR Anthropic account, not Graphite. Works with Claude Code + the Graphite MCP server.',
    placeholder: 'sk-ant-…',
    docs: 'https://console.anthropic.com/account/keys',
    docsLabel: 'Get an Anthropic key',
    note: 'This is the paid API, not the Claude Pro chat subscription. Subscriptions can\'t be used from a third-party site — only from Anthropic\'s own apps (claude.ai, Claude Code).',
  },
  openai: {
    title: 'Connect OpenAI',
    sub: 'Bring your own OpenAI API key. Pay-per-token on YOUR OpenAI account, not Graphite. Works with Codex CLI + the Graphite MCP server.',
    placeholder: 'sk-…',
    docs: 'https://platform.openai.com/api-keys',
    docsLabel: 'Get an OpenAI key',
    note: 'This is the paid API, not the ChatGPT Pro subscription. Subscriptions can\'t be used from a third-party site — only from OpenAI\'s own apps (chatgpt.com, Codex CLI).',
  },
};

function ByoKeyAuth({ provider, currentKey, setKey }) {
  const copy = BYO_COPY[provider];
  const [draft, setDraft] = React.useState('');
  function commit() { if (draft.trim()) { setKey(draft.trim()); setDraft(''); } }
  return (
    <div className="portal-grid" style={{ gridTemplateColumns: '1fr' }}>
      <PortalCard title={copy.title} subtitle={copy.sub}>
        {currentKey ? (
          <div>
            <KeyDisplay value={maskKey(currentKey)} />
            <button className="btn btn-ghost" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }} onClick={() => setKey('')}>Remove</button>
            <div className="portal-fine" style={{ marginTop: 14 }}>{copy.note}</div>
          </div>
        ) : (
          <div className="portal-form">
            <label className="portal-label">API key</label>
            <input
              type="password" placeholder={copy.placeholder} value={draft}
              onChange={(e) => setDraft(e.target.value)}
              className="portal-input mono"
              autoComplete="off"
            />
            <button className="btn btn-accent" style={{ marginTop: 14, width: '100%', justifyContent: 'center' }} onClick={commit}>
              Save locally
            </button>
            <div className="portal-fine" style={{ marginTop: 10, display: 'flex', justifyContent: 'space-between', gap: 12 }}>
              <span>{copy.note}</span>
              <a href={copy.docs} target="_blank" rel="noreferrer" style={{ color: 'var(--accent)', whiteSpace: 'nowrap' }}>{copy.docsLabel} →</a>
            </div>
          </div>
        )}
      </PortalCard>
    </div>
  );
}

function maskKey(k) {
  if (!k || k.length < 12) return k || '';
  return k.slice(0, 7) + '…' + k.slice(-4);
}


// ──────────────────────── shared bits ────────────────────────

function PortalCard({ title, subtitle, children }) {
  return (
    <div className="portal-card">
      <div className="portal-card-h">
        <h3>{title}</h3>
        {subtitle && <p>{subtitle}</p>}
      </div>
      <div className="portal-card-b">{children}</div>
    </div>
  );
}

function KeyDisplay({ value }) {
  const [copied, copy] = useCopy();
  return (
    <div className="portal-keybox">
      <code className="mono">{value}</code>
      <button className="portal-copy" onClick={() => copy(value)}>
        {copied ? <><Icon name="check" size={14} />Copied</> : <>Copy</>}
      </button>
    </div>
  );
}


// ──────────────────────── status row ────────────────────────

function StatusRow() {
  // Uses /health (public) for live entity/rel/fact counts. The previous
  // design also showed per-key usage but that endpoint isn't exposed
  // publicly on central — chat quota lives at /chat/quota and is
  // user-facing only when chatting.
  const [counts, setCounts] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    function refresh() {
      fetch(apiUrl('/health'))
        .then((r) => r.ok ? r.json() : null)
        .then((h) => { if (alive && h) setCounts(h); })
        .catch(() => { });
    }
    refresh();
    const id = setInterval(refresh, 60000);
    return () => { alive = false; clearInterval(id); };
  }, []);
  const fmt = (n) => n == null ? '—' : Number(n).toLocaleString();
  return (
    <div className="portal-section">
      <div className="portal-section-h">
        <div>
          <h3>Live network status</h3>
          <p className="portal-section-sub">Auto-refreshes every minute.</p>
        </div>
      </div>
      <div className="portal-counters">
        <Counter label="Entities" value={fmt(counts?.entities)} />
        <Counter label="Relationships" value={fmt(counts?.relationships)} />
        <Counter label="Facts" value={fmt(counts?.facts)} />
      </div>
    </div>
  );
}
function Counter({ label, value, accent }) {
  return (
    <div className="portal-counter">
      <div className={'portal-counter-v mono' + (accent ? ' accent' : '')}>{value}</div>
      <div className="portal-counter-l">{label}</div>
    </div>
  );
}


// ──────────────────────── chat panel ────────────────────────

function ChatPanel({ active, apiKey }) {
  const [messages, setMessages] = React.useState([
    { role: 'system', content: "Ask the graph anything. The server-side LLM uses MCP tools — search, relationships, path-finding, exposure analysis — to ground every answer in real graph data." },
  ]);
  const [input, setInput] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const scrollRef = React.useRef(null);

  const suggestions = [
    "What's NVIDIA's exposure to TSMC?",
    "Who sits on Apple's board?",
    "Show the supply-chain path from ASML to AAPL",
    "Which companies are most affected by EU AI Act?",
  ];

  React.useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages]);

  // Chat from this page only works with the graphite key. With Anthropic
  // or OpenAI keys, you run your own LLM client via the MCP server (see
  // QuickStart below) — we don't proxy 3rd-party LLM calls.
  const chatEnabled = active === 'graphite' && !!apiKey;
  const chatBlockedReason =
    active !== 'graphite'
      ? `Chat from this page uses the Graphite-managed LLM. With your ${active === 'anthropic' ? 'Anthropic' : 'OpenAI'} key, run a local LLM client (Claude Code / Codex CLI / Cursor) using the MCP config below — quota stays on your account.`
      : !apiKey
        ? 'Add a Graphite key above to start chatting (free tier: 2 chat queries).'
        : null;

  async function send(text) {
    const q = (text ?? input).trim();
    if (!q || busy || !chatEnabled) return;
    setInput('');
    setMessages((m) => [...m, { role: 'user', content: q }]);
    setBusy(true);

    const r = await callGraphite('/api/v1/chat', {
      key: apiKey, method: 'POST', body: { question: q },
    });
    if (r.ok) {
      setMessages((m) => [...m, {
        role: 'assistant',
        content: r.data?.answer || JSON.stringify(r.data, null, 2),
        tools: r.data?.tool_calls,
        sources: r.data?.sources,
      }]);
    } else {
      setMessages((m) => [...m, { role: 'assistant', error: true, content: `Couldn't reach the API: ${r.error}` }]);
    }
    setBusy(false);
  }

  return (
    <div className="portal-section">
      <div className="portal-section-h">
        <div>
          <h3>Chat with the graph</h3>
          <p className="portal-section-sub">Natural-language queries grounded by the same MCP tools an agent would call. 2 free queries lifetime per Graphite key.</p>
        </div>
      </div>
      <div className="chat">
        <div className="chat-thread" ref={scrollRef}>
          {messages.map((m, i) => <ChatBubble key={i} {...m} />)}
          {busy && <ChatBubble role="assistant" loading />}
        </div>
        {chatEnabled && (
          <div className="chat-suggest">
            {suggestions.map((s, i) => (
              <button key={i} className="chip" onClick={() => send(s)} disabled={busy}>
                {s}
              </button>
            ))}
          </div>
        )}
        <form className="chat-input" onSubmit={(e) => { e.preventDefault(); send(); }}>
          <input
            placeholder={chatBlockedReason || 'Ask a question about the graph…'}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            disabled={!chatEnabled || busy}
          />
          <button type="submit" className="btn btn-accent" disabled={!chatEnabled || busy || !input.trim()}>
            Send <Icon name="arrow" />
          </button>
        </form>
      </div>
    </div>
  );
}

function ChatBubble({ role, content, error, loading, tools, sources }) {
  if (role === 'system') return <div className="chat-system">{content}</div>;
  return (
    <div className={'chat-bubble chat-' + role + (error ? ' err' : '')}>
      <div className="chat-bubble-role mono">{role === 'user' ? 'YOU' : 'GRAPH'}</div>
      <div className="chat-bubble-body">
        {loading ? <ChatLoading /> : content}
        {tools && tools.length > 0 && (
          <div className="chat-tools">
            <div className="mono" style={{ fontSize: 10, letterSpacing: '0.12em', color: 'var(--fg-mute)', marginBottom: 6 }}>TOOLS USED</div>
            {tools.map((t, i) => <span key={i} className="chip-tool mono">{t.name || t}</span>)}
          </div>
        )}
        {sources && sources.length > 0 && (
          <div className="chat-sources mono">{sources.length} source{sources.length === 1 ? '' : 's'} cited</div>
        )}
      </div>
    </div>
  );
}
function ChatLoading() {
  return <span className="chat-loading"><span /><span /><span /></span>;
}


// ──────────────────────── quick start ────────────────────────

function QuickStart({ active, apiKey }) {
  const [tab, setTab] = React.useState('curl');
  const k = apiKey || (active === 'anthropic' ? 'ANTHROPIC_API_KEY' : active === 'openai' ? 'OPENAI_API_KEY' : 'YOUR_GRAPHITE_KEY');
  const apiBase = ((window.__ENV && window.__ENV.CENTRAL_API_URL) || '').replace(/\/$/, '') + '/api/v1';

  // Snippets adapt to which provider is active. Graphite snippets show
  // direct API calls. BYO snippets show the MCP config the user drops
  // into their Claude Code or Codex CLI to talk to the graph.
  const snippets = active === 'graphite'
    ? graphiteSnippets(k, apiBase)
    : active === 'anthropic'
      ? anthropicMcpSnippets(k, apiBase)
      : openaiMcpSnippets(k, apiBase);

  const tabs = active === 'graphite'
    ? [['curl', 'cURL'], ['python', 'Python'], ['js', 'JavaScript'], ['mcp', 'MCP config']]
    : [['mcp', 'MCP config'], ['cli', 'CLI usage']];

  React.useEffect(() => {
    if (!snippets[tab]) setTab(tabs[0][0]);
  }, [active]); // eslint-disable-line

  const [copied, copy] = useCopy();

  return (
    <div className="portal-section">
      <div className="portal-section-h">
        <div>
          <h3>Quick start</h3>
          <p className="portal-section-sub">{apiKey ? 'Snippets pre-filled with your key.' : 'Add a key above and these will be ready to paste.'}</p>
        </div>
      </div>
      <div className="snippet">
        <div className="snippet-tabs">
          {tabs.map(([id, label]) => (
            <button key={id} className={'snippet-tab mono' + (tab === id ? ' on' : '')} onClick={() => setTab(id)}>
              {label}
            </button>
          ))}
          <button className="snippet-copy mono" onClick={() => copy(snippets[tab])}>
            {copied ? <><Icon name="check" size={13} /> Copied</> : 'Copy'}
          </button>
        </div>
        <pre className="snippet-code mono"><code>{snippets[tab]}</code></pre>
      </div>
    </div>
  );
}

function graphiteSnippets(k, apiBase) {
  return {
    curl: `# Search for a company
curl -H "X-API-Key: ${k}" \\
  "${apiBase}/search?q=NVIDIA"

# Get entity relationships
curl -H "X-API-Key: ${k}" \\
  "${apiBase}/entities/company:NVDA/relationships"

# Find path between two companies
curl -H "X-API-Key: ${k}" \\
  "${apiBase}/graph/path?source=company:AAPL&target=company:TSM"

# Exposure analysis
curl -H "X-API-Key: ${k}" \\
  "${apiBase}/graph/exposure?entity=company:TSM"`,
    python: `import requests

API = "${apiBase}"
headers = {"X-API-Key": "${k}"}

# Search for a company
r = requests.get(f"{API}/search", params={"q": "NVIDIA"}, headers=headers)
print(r.json())

# Path between two companies
r = requests.get(f"{API}/graph/path", params={
    "source": "company:AAPL",
    "target": "company:TSM",
}, headers=headers)
for hop in r.json()["path"]:
    print(hop)`,
    js: `const API = "${apiBase}";
const headers = { "X-API-Key": "${k}" };

const search = await fetch(\`\${API}/search?q=NVIDIA\`, { headers })
  .then(r => r.json());

const exposure = await fetch(
  \`\${API}/graph/exposure?entity=company:TSM\`,
  { headers }
).then(r => r.json());

console.log(exposure);`,
    mcp: `// ~/.cursor/mcp.json or Claude Desktop config
{
  "mcpServers": {
    "graphite": {
      "command": "python",
      "args": ["-m", "mcp_server"],
      "env": {
        "CUSTOMER_API_KEY": "${k}"
      }
    }
  }
}`,
  };
}

function anthropicMcpSnippets(anthKey, apiBase) {
  return {
    mcp: `// ~/.claude/mcp.json (Claude Code)
{
  "mcpServers": {
    "graphite": {
      "command": "npx",
      "args": ["-y", "@graphite-ai/mcp-server"],
      "env": {
        "GRAPHITE_API_BASE": "${apiBase}",
        "ANTHROPIC_API_KEY": "${anthKey}"
      }
    }
  }
}`,
    cli: `# 1. Install Claude Code if you haven't:
curl -fsSL https://claude.ai/install.sh | bash

# 2. Add Graphite as an MCP server (config above)

# 3. Use it from Claude Code:
claude
> /mcp graphite search NVIDIA
> what are NVDA's supplier dependencies?

# Quota stays on YOUR Anthropic account — Graphite never sees the
# LLM tokens, only graph queries.`,
  };
}

function openaiMcpSnippets(openaiKey, apiBase) {
  return {
    mcp: `// ~/.codex/config.json (Codex CLI)
{
  "mcpServers": {
    "graphite": {
      "command": "npx",
      "args": ["-y", "@graphite-ai/mcp-server"],
      "env": {
        "GRAPHITE_API_BASE": "${apiBase}",
        "OPENAI_API_KEY": "${openaiKey}"
      }
    }
  }
}`,
    cli: `# 1. Install Codex CLI if you haven't:
npm install -g @openai/codex

# 2. Add Graphite as an MCP server (config above)

# 3. Use it from Codex CLI:
codex
> /mcp graphite search NVIDIA
> what are NVDA's supplier dependencies?

# Quota stays on YOUR OpenAI account — Graphite never sees the
# LLM tokens, only graph queries.`,
  };
}
