"use client";
/* Connect (MCP) screen - expose this project's tools as an MCP server for external clients.
   (Consuming external MCP servers lives in the BUILD → External MCP tab.) */
import { ReactNode, useCallback, useEffect, useState } from "react";
import { Icon } from "../icons";
import { CodeBlock, Field, Segmented, Toggle } from "../primitives";
import { api, type McpToken, type MyConnection, type ToolSet } from "@/lib/api";
import { EmbedPanel } from "./embed";

/* Collapsible detail section - keeps the deep integration reference tucked away so the
   Connect screen stays scannable; expand only what you need. */
function Collapse({ title, sub, defaultOpen = false, children }: { title: string; sub?: string; defaultOpen?: boolean; children: ReactNode }) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div className="card" style={{ padding: 0, marginBottom: 10, overflow: "hidden" }}>
      <button
        type="button"
        onClick={() => setOpen((o) => !o)}
        aria-expanded={open}
        className="row spread"
        style={{ width: "100%", background: "none", border: "none", cursor: "pointer", padding: "12px 16px", textAlign: "left", fontFamily: "var(--font-ui)", color: "inherit" }}
      >
        <div>
          <div className="t-h3">{title}</div>
          {sub && <div className="t-caption fg-2" style={{ marginTop: 2 }}>{sub}</div>}
        </div>
        <Icon name={open ? "chevdown" : "chevright"} size={16} style={{ color: "var(--fg-2)", flex: "none", marginLeft: 12 }} />
      </button>
      {open && <div style={{ padding: "0 16px 16px" }}>{children}</div>}
    </div>
  );
}

/* One SSE frame documented in the streaming reference: event name + what its data carries. */
function FrameRow({ event, children }: { event: string; children: ReactNode }) {
  return (
    <div className="row gap2" style={{ alignItems: "baseline", padding: "5px 0", borderTop: "1px solid var(--line)" }}>
      <span className="mono-sm" style={{ minWidth: 92, flex: "none", color: "var(--fg-2)" }}>{event}</span>
      <span className="t-caption fg-1">{children}</span>
    </div>
  );
}

/* Left secondary-nav sections (mirrors the Settings screen layout) so the Connect screen
   is navigable instead of one long scroll. */
type ConnSection = "run" | "reference" | "mcp" | "embed";
const CONN_SECTIONS: { id: ConnSection; label: string; icon: string; sub: string; child?: boolean }[] = [
  { id: "run", label: "Run API", icon: "bolt", sub: "Call this project's workflow from your backend over one endpoint." },
  { id: "reference", label: "Integration reference", icon: "traces", sub: "The wire format for streaming and non-streaming responses.", child: true },
  { id: "mcp", label: "MCP server", icon: "connect", sub: "Expose this project's tools to Claude Desktop, Cursor, or VS Code." },
  { id: "embed", label: "Embed", icon: "grid", sub: "Drop this project's chatbot into any website as a widget." },
];

/* A per-user credential connect row: the CURRENT user pastes their own downstream token for a
   per-user auth provider. Stored server-side keyed by their user id (the identity their MCP PAT
   resolves to), so tool calls act as them without a shared secret. */
function MyConnectionCard({ project, ap }: { project: any; ap: MyConnection }) {
  const [status, setStatus] = useState<{ connected: boolean } | null>(null);
  const [token, setToken] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState<string | null>(null);
  const refresh = useCallback(() => { api.getMyConnection(project.id, ap.id).then(setStatus).catch(() => setStatus(null)); }, [project.id, ap.id]);
  useEffect(() => { refresh(); }, [refresh]);

  async function save() {
    setErr(null); setBusy(true);
    try {
      const res = await api.setMyConnection(project.id, ap.id, token.trim());
      if (!res.ok) throw new Error("Could not save token.");
      setToken(""); refresh();
    } catch (e: any) { setErr(e?.message || String(e)); } finally { setBusy(false); }
  }
  async function clear() { try { await api.clearMyConnection(project.id, ap.id); } catch { /* best-effort */ } refresh(); }

  return (
    <div className="card" style={{ padding: 12, background: "var(--bg-2)" }}>
      <div className="row spread" style={{ marginBottom: 8 }}>
        <div className="row gap2" style={{ alignItems: "center", minWidth: 0 }}>
          <Icon name="auth" size={13} style={{ color: "var(--fg-2)" }} />
          <span className="mono-sm truncate">{ap.name}</span>
          {status?.connected ? <span className="pill pill-ok" style={{ height: 16 }}>connected</span> : <span className="pill pill-muted" style={{ height: 16 }}>not connected</span>}
        </div>
        {status?.connected && <button className="btn btn-ghost btn-sm" onClick={clear}>Clear</button>}
      </div>
      <div className="row gap2">
        <input className="input mono" type="password" value={token} onChange={(e) => setToken(e.target.value)} placeholder="paste your token…" style={{ flex: 1 }} />
        <button className="btn btn-primary btn-sm" onClick={save} disabled={busy || !token.trim()}>{busy ? "Saving…" : "Save"}</button>
      </div>
      {err && <div className="t-caption" style={{ color: "var(--err)", marginTop: 6 }}>{err}</div>}
    </div>
  );
}

/* ============ CONNECT (MCP) ============ */
export function ConnectScreen({ project }: { project: any }) {
  const [section, setSection] = useState<ConnSection>("run");
  const [tools, setTools] = useState<any[]>([]);
  const [toolSets, setToolSets] = useState<ToolSet[]>([]);
  const [tsSave, setTsSave] = useState<"idle" | "saving" | "saved">("idle");
  const [excluded, setExcluded] = useState<string[]>([]);
  const [openSets, setOpenSets] = useState<Set<string>>(new Set());
  const [mcpTokens, setMcpTokens] = useState<McpToken[]>([]);
  // Per-user ("external") auth providers for this project - each lets the current user connect their
  // own downstream token so MCP tool calls act as them (see the "Connect your accounts" card).
  const [perUserAps, setPerUserAps] = useState<MyConnection[]>([]);
  const [newToken, setNewToken] = useState("");
  const [apiKey, setApiKey] = useState("");
  const [save, setSave] = useState<"idle" | "saving" | "saved">("idle");
  const [credTab, setCredTab] = useState<"key" | "pat">("key"); // which credential to paste - it's either/or
  // Project-level MCP tools (mirror the server's project.config flags in mcp_server.py): the whole
  // workflow as one tool, plus knowledge-base + curated-Q&A search. Independent of toolsets.
  const [exposeWf, setExposeWf] = useState(false);
  const [wfToolName, setWfToolName] = useState("run_workflow");
  const [exposeKnowledge, setExposeKnowledge] = useState(false);
  const [exposeFaq, setExposeFaq] = useState(false);
  const [cfgSave, setCfgSave] = useState<"idle" | "saving" | "saved">("idle");
  // Run-API panel: pick the workflow this project's API runs (a saved setting) + the
  // backend-facing base URL, then show the one ready-to-copy endpoint.
  const [workflows, setWorkflows] = useState<any[]>([]);
  const [wfId, setWfId] = useState("");
  const [apiSave, setApiSave] = useState<"idle" | "saving" | "saved">("idle");
  const [apiBase, setApiBase] = useState(
    (process.env.NEXT_PUBLIC_FORGE_API_URL || "http://localhost:8000").replace(/\/$/, ""),
  );
  useEffect(() => {
    if (!project?.id) return;
    api.listTools(project.id).then(setTools).catch(() => {});
    api.listToolSets(project.id).then(setToolSets).catch(() => {});
    api.listMcpTokens(project.id).then(setMcpTokens).catch(() => {});
    api.listMyConnections(project.id).then(setPerUserAps).catch(() => {});
  }, [project?.id]);
  useEffect(() => {
    if (!project?.id) return;
    Promise.all([api.getProject(project.id), api.listWorkflows(project.id)]).then(([p, ws]) => {
      setApiKey((p.config as any)?.mcp_api_key || "");
      setExcluded(((p.config as any)?.mcp_excluded_tools as string[]) || []);
      const cfg = (p.config as any) || {};
      setExposeWf(!!cfg.mcp_expose_workflow);
      setWfToolName(cfg.mcp_workflow_tool_name || "run_workflow");
      setExposeKnowledge(!!cfg.mcp_expose_knowledge);
      setExposeFaq(!!cfg.mcp_expose_faq);
      setWorkflows(ws);
      // Default the picker to the saved API workflow; else the active one, else the first.
      const saved = (p.config as any)?.api_workflow_id;
      const chosen = ws.find((w: any) => w.id === saved) || ws.find((w: any) => w.status === "active") || ws[0];
      setWfId(chosen ? chosen.id : "");
    }).catch(() => {});
  }, [project?.id]);

  // MCP clients must reach the API DIRECTLY, not the same-origin /api/forge console proxy: the
  // OAuth discovery + dynamic-registration endpoints live at the API root (/.well-known/*,
  // /v1/oauth/*), which the proxy does NOT forward. A proxied :3000 URL works for API-key/PAT
  // (the JSON-RPC tunnels through the proxy) but breaks the OAuth sign-in flow - the client
  // tries to discover the auth server at :3000 and gives up. Use the same base as the Run API.
  const mcpBase = (apiBase || "http://localhost:8000").replace(/\/$/, "");
  const url = `${mcpBase}/v1/mcp/${project?.id || "<project>"}`;
  const claudeConfig = JSON.stringify({ mcpServers: { [project?.slug || "forge"]: { url, headers: { Authorization: "Bearer <PAT or API key>" } } } }, null, 2);
  // The MCP surface = enabled tools of EXPOSED sets, minus individually excluded ones (mirrors
  // the server; see mcp_server.py._exposed_names).
  const toolById = new Map(tools.map((t) => [t.id, t]));
  const exposedToolIds = new Set(toolSets.filter((s) => s.exposed).flatMap((s) => s.tool_ids).filter((id) => !excluded.includes(id)));
  const exposedTools = tools.filter((t) => t.enabled && exposedToolIds.has(t.id));
  // Project-level tools published alongside the toolset tools (see mcp_server.py._capability_tools
  // + _workflow_tool_name). Shown in the "Currently exposed" summary so the whole surface is visible.
  const projectTools = [
    ...(exposeWf ? [{ name: wfToolName || "run_workflow", kind: "workflow" }] : []),
    ...(exposeKnowledge ? [{ name: "search_knowledge_base", kind: "knowledge" }] : []),
    ...(exposeFaq ? [{ name: "lookup_faq", kind: "qa" }] : []),
  ];

  // Run API (server-to-server): a backend hits the Forge API DIRECTLY, not the web proxy.
  // ONE endpoint per project - it runs the workflow chosen above; `stream` is the only
  // per-request knob, and HITL flows through the same call (workflow-driven).
  const base = (apiBase || "http://localhost:8000").replace(/\/$/, "");
  const pid = project?.id || "<projectId>";
  const runUrl = `${base}/v1/projects/${pid}/run`;
  const curl = [
    "# ONE endpoint. Auth with the service token; pass the caller's per-user secrets in",
    "# X-Forge-Context (used by tools as {{ctx.*}}) - never put secrets in the body.",
    `curl -sN "${runUrl}" \\`,
    `  -H "Authorization: Bearer $FORGE_SERVICE_API_TOKEN" \\`,
    `  -H "Content-Type: application/json" \\`,
    `  -H 'X-Forge-Context: {"jsessionid":"<user session>","csrf":"<user csrf>"}' \\`,
    `  -H "Accept: text/event-stream" \\`,
    `  -d '{"input":{"messages":[{"role":"user","content":"hello"}]},"end_user":{"id":"user-123"},"stream":true}'`,
    '# -> SSE frames; the "ready" frame gives you a thread_id to continue the conversation.',
    "",
    "# stream:false returns a single JSON reply instead of SSE.",
    "# answer a human-in-the-loop step the workflow raised (reuse the thread_id):",
    `#   -d '{"thread_id":"<thread>","resume":{"value":"approve"}}'`,
  ].join("\n");

  // --- Reference payloads for the "How the integration works" section ---
  // A trimmed SSE transcript: one `ready` frame, a couple of token deltas, then `done`.
  const sampleStream = [
    "event: ready",
    'data: {"run_id":"run_a1b2","thread_id":"thr_x9"}',
    "",
    "event: node_start",
    'data: {"node":"assistant"}',
    "",
    "event: messages",
    'data: {"content":"Hi","type":"AIMessageChunk","node":"assistant"}',
    "",
    "event: messages",
    'data: {"content":" there!","type":"AIMessageChunk","node":"assistant"}',
    "",
    "event: done",
    'data: {"status":"done","answer":"Hi there!","total_tokens":812,"total_cost_usd":0.0021}',
  ].join("\n");
  // The single object returned when stream:false (thread_id is added by the endpoint).
  const sampleJson = JSON.stringify(
    {
      run_id: "run_a1b2", thread_id: "thr_x9", status: "done",
      answer: "Hi there!", components: [], interrupted: false, interrupts: [],
      total_tokens: 812, total_cost_usd: 0.0021,
    },
    null,
    2,
  );

  async function saveApiWorkflow(next: string) {
    setWfId(next);
    setApiSave("saving");
    const p = await api.getProject(project.id);
    await api.updateProject(project.id, { config: { ...(p.config || {}), api_workflow_id: next || undefined } });
    setApiSave("saved");
    setTimeout(() => setApiSave("idle"), 1200);
  }

  async function saveKey(next: string) {
    setApiKey(next); setSave("saving");
    const p = await api.getProject(project.id);
    await api.updateProject(project.id, { config: { ...(p.config || {}), mcp_api_key: next || undefined } });
    setSave("saved"); setTimeout(() => setSave("idle"), 1200);
  }
  // Merge a patch into project.config (undefined values drop the key). Used by the project-tool
  // toggles; re-reads config first so concurrent edits to other keys aren't clobbered.
  async function saveCfg(patch: Record<string, unknown>) {
    setCfgSave("saving");
    const p = await api.getProject(project.id);
    await api.updateProject(project.id, { config: { ...(p.config || {}), ...patch } });
    setCfgSave("saved"); setTimeout(() => setCfgSave("idle"), 1200);
  }
  const genKey = () => {
    // A shared MCP API key is a credential, so use the CSPRNG (crypto.getRandomValues),
    // never Math.random() - the latter is predictable and unsafe for secret material.
    const bytes = new Uint8Array(24);
    crypto.getRandomValues(bytes);
    saveKey("fmcp_" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""));
  };

  // Publish a subset of tool sets on the base MCP endpoint (project.config.mcp_published_toolsets,
  // a list of set slugs). Empty => the base endpoint exposes every enabled tool (prior behavior).
  async function toggleExposed(ts: ToolSet) {
    setTsSave("saving");
    const updated = await api.updateToolSet(project.id, ts.id, { exposed: !ts.exposed });
    setToolSets((prev) => prev.map((x) => (x.id === ts.id ? updated : x)));
    setTsSave("saved");
    setTimeout(() => setTsSave("idle"), 1200);
  }
  async function saveExcluded(next: string[]) {
    setExcluded(next);
    setTsSave("saving");
    const p = await api.getProject(project.id);
    await api.updateProject(project.id, { config: { ...(p.config || {}), mcp_excluded_tools: next.length ? next : undefined } });
    setTsSave("saved");
    setTimeout(() => setTsSave("idle"), 1200);
  }
  const toggleToolExcluded = (tid: string) =>
    saveExcluded(excluded.includes(tid) ? excluded.filter((x) => x !== tid) : [...excluded, tid]);
  const toggleOpenSet = (id: string) =>
    setOpenSets((prev) => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; });

  async function genToken() {
    const t = await api.createMcpToken(project.id, {});
    setNewToken(t.token || "");
    api.listMcpTokens(project.id).then(setMcpTokens).catch(() => {});
  }
  async function revokeToken(id: string) {
    await api.revokeMcpToken(project.id, id);
    setMcpTokens((prev) => prev.filter((t) => t.id !== id));
  }

  const activeMeta = CONN_SECTIONS.find((s) => s.id === section)!;
  return (
    <div className="col" style={{ flex: 1, minHeight: 0 }}>
      <div className="row" style={{ flex: 1, minHeight: 0, alignItems: "stretch" }}>
        {/* secondary nav (same pattern as Settings) */}
        <nav className="scroll-y" style={{ width: 224, flex: "none", borderRight: "1px solid var(--line)", background: "var(--bg-1)", padding: 10 }}>
          <div className="t-micro" style={{ padding: "6px 8px 8px" }}>Connect</div>
          {CONN_SECTIONS.map((s) => {
            const on = section === s.id;
            return (
              <button key={s.id} onClick={() => setSection(s.id)} className={"sidenav-item" + (on ? " active" : "")}
                style={{ display: "flex", alignItems: "center", gap: 10, width: "100%", height: 34, padding: "0 10px", paddingLeft: s.child ? 20 : 10, marginBottom: 1, borderRadius: 7, border: "none", cursor: "pointer", textAlign: "left", color: on ? "var(--accent)" : "var(--fg-1)", fontSize: 13, fontWeight: on ? 600 : 500, fontFamily: "var(--font-ui)" }}>
                {s.child && <span aria-hidden style={{ color: "var(--fg-2)", fontSize: 13, lineHeight: 1, marginRight: -4, flex: "none" }}>└</span>}
                <Icon name={s.icon as any} size={16} style={{ flex: "none" }} />
                <span className="grow truncate">{s.label}</span>
              </button>
            );
          })}
        </nav>

        {/* content */}
        <div className="scroll-y grow" style={{ minWidth: 0 }}>
          <div className="fade-up" style={{ maxWidth: 960, margin: "0 auto", padding: "24px 28px" }}>
            <div style={{ marginBottom: 18 }}>
              <div className="t-display">{activeMeta.label}</div>
              <div className="fg-1" style={{ marginTop: 3 }}>{activeMeta.sub}</div>
            </div>

            {section === "run" && (
              <>
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="row gap3" style={{ flexWrap: "wrap" }}>
            <div style={{ flex: 1, minWidth: 240 }}>
              <Field label="Forge API base URL" help="Where your backend reaches the Forge API directly (NOT the web console). Dev: http://localhost:8000. From another container on Forge's network: http://api:8000.">
                <input className="input mono" value={apiBase} onChange={(e) => setApiBase(e.target.value)} placeholder="http://localhost:8000" />
              </Field>
            </div>
            <div style={{ flex: 1, minWidth: 240 }}>
              <Field label="Workflow this API runs" help="Saved on the project. The /run endpoint always executes this workflow — callers never pick one.">
                <div className="row gap2" style={{ alignItems: "center" }}>
                  <select className="select" style={{ flex: 1 }} value={wfId} onChange={(e) => saveApiWorkflow(e.target.value)}>
                    {workflows.length === 0 && <option value="">No workflows yet</option>}
                    {workflows.map((w) => <option key={w.id} value={w.id}>{w.name}{w.status !== "active" ? ` (${w.status})` : ""}</option>)}
                  </select>
                  <span className="t-caption fg-2" style={{ minWidth: 52 }}>{apiSave === "saving" ? "Saving…" : apiSave === "saved" ? "Saved ✓" : ""}</span>
                </div>
              </Field>
            </div>
          </div>
        </div>
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="t-h3" style={{ marginBottom: 8 }}>Endpoint · POST</div>
          <CodeBlock code={runUrl} />
          <div className="field-help" style={{ marginTop: 8 }}>
            One call does everything. Send <span className="mono-sm">{"{ input, stream }"}</span> for a new turn (reuse the returned <span className="mono-sm">thread_id</span> to continue a conversation), or <span className="mono-sm">{"{ thread_id, resume }"}</span> to answer a human-in-the-loop step the workflow raised. <span className="mono-sm">stream: true</span> streams SSE (tokens, steps, tools); <span className="mono-sm">false</span> returns one JSON reply. Authenticate with <span className="mono-sm">Authorization: Bearer &lt;FORGE_SERVICE_API_TOKEN&gt;</span>; pass the caller&apos;s per-user session/CSRF as <span className="mono-sm">X-Forge-Context</span> so tools act on their behalf — never put secrets in the body.
          </div>
        </div>
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="t-h3" style={{ marginBottom: 8 }}>Example (curl)</div>
          <CodeBlock code={curl} />
        </div>
              </>
            )}

            {section === "reference" && (
              <>
        <div className="fg-2 t-caption" style={{ marginBottom: 10 }}>The same endpoint responds two ways depending on the <span className="mono-sm">stream</span> flag. Expand a section for the wire format.</div>

        <Collapse title="Streaming — stream: true" sub="Server-Sent Events (text/event-stream): tokens, steps and tool activity as they happen." defaultOpen>
          <div className="field-help" style={{ margin: "10px 0" }}>
            The response stays open and emits <span className="mono-sm">event:</span> / <span className="mono-sm">data:</span> frames (data is JSON). Read it with any SSE client and keep the connection until a <span className="mono-sm">done</span>, <span className="mono-sm">error</span> or <span className="mono-sm">interrupt</span> frame arrives. Build the reply by concatenating each <span className="mono-sm">messages</span> frame&apos;s <span className="mono-sm">content</span> in order; the final <span className="mono-sm">done</span> frame also carries the whole <span className="mono-sm">answer</span> (authoritative — it covers non-LLM steps that never stream tokens).
          </div>
          <div style={{ margin: "10px 0" }}>
            <FrameRow event="ready">First frame. <span className="mono-sm">{"{ run_id, thread_id }"}</span> — save <span className="mono-sm">thread_id</span> to continue this conversation.</FrameRow>
            <FrameRow event="node_start">A workflow step began. <span className="mono-sm">{"{ node }"}</span>.</FrameRow>
            <FrameRow event="messages">Assistant answer token delta. <span className="mono-sm">{"{ content, type, node }"}</span> — concatenate <span className="mono-sm">content</span>.</FrameRow>
            <FrameRow event="updates">Top-level step output/state change (nested sub-steps are omitted to keep it clean).</FrameRow>
            <FrameRow event="custom">App-emitted data a node chose to stream (e.g. rich components).</FrameRow>
            <FrameRow event="interrupt">A human-in-the-loop step is waiting. Answer it with <span className="mono-sm">{"{ thread_id, resume }"}</span>.</FrameRow>
            <FrameRow event="node_error">A step failed. <span className="mono-sm">{"{ node, message }"}</span>.</FrameRow>
            <FrameRow event="done">Terminal. <span className="mono-sm">{"{ status, answer, total_tokens, total_cost_usd }"}</span>.</FrameRow>
            <FrameRow event="error">Terminal error. <span className="mono-sm">{"{ message }"}</span>.</FrameRow>
          </div>
          <CodeBlock code={sampleStream} />
        </Collapse>

        <Collapse title="Non-streaming — stream: false" sub="One JSON object, returned once the run finishes.">
          <div className="field-help" style={{ margin: "10px 0" }}>
            Simplest to consume: a normal <span className="mono-sm">application/json</span> response after the run completes. <span className="mono-sm">status</span> is one of <span className="mono-sm">done · interrupted · error · busy</span>. When <span className="mono-sm">interrupted</span>, <span className="mono-sm">interrupts</span> holds the human-in-the-loop payload — answer it by re-calling with <span className="mono-sm">{"{ thread_id, resume }"}</span>. <span className="mono-sm">answer</span> is the full reply; <span className="mono-sm">components</span> carries any structured UI a node produced.
          </div>
          <CodeBlock code={sampleJson} />
        </Collapse>

        <Collapse title="Continue a conversation & human-in-the-loop" sub="Reuse thread_id across turns; resume interrupts on the same thread.">
          <div className="field-help" style={{ margin: "10px 0" }}>
            Chat memory is keyed by <span className="mono-sm">thread_id</span>. Take it from the <span className="mono-sm">ready</span> frame (streaming) or the JSON reply (non-streaming) and send it back in the next request&apos;s body to keep context across turns — omit it to start fresh. To answer an interrupt the workflow raised, POST the same endpoint with the interrupted thread and a resume value:
          </div>
          <CodeBlock code={'{ "thread_id": "thr_x9", "resume": { "value": "approve" } }'} />
        </Collapse>

        <Collapse title="Per-user identity — X-Forge-Context" sub="Pass the caller's secrets out-of-band; tools read them as {{ctx.*}}.">
          <div className="field-help" style={{ margin: "10px 0" }}>
            Authenticate the call itself with the service token (<span className="mono-sm">Authorization: Bearer &lt;FORGE_SERVICE_API_TOKEN&gt;</span>). Anything the workflow&apos;s tools need to act <em>as the end user</em> — a session cookie, CSRF token, downstream bearer — goes in the <span className="mono-sm">X-Forge-Context</span> header as a JSON object, and tools reference it with <span className="mono-sm">{"{{ctx.*}}"}</span>. It is never written to the body, never persisted, and never echoed back.
          </div>
          <CodeBlock code={"X-Forge-Context: {\"jsessionid\":\"<user session>\",\"csrf\":\"<user csrf>\"}"} />
          <div className="field-help" style={{ marginTop: 8 }}>Identify the end user for quotas/analytics with <span className="mono-sm">{"{ \"end_user\": { \"id\": \"user-123\" } }"}</span> in the body.</div>
        </Collapse>

              </>
            )}

            {section === "mcp" && (
              <>
        <Collapse title="How to use this MCP server" sub="Publish toolsets, connect a client, and pick how each user authenticates.">
          <ol className="field-help" style={{ margin: "10px 0", paddingLeft: 18, lineHeight: 1.75 }}>
            <li><b>Curate what&apos;s exposed.</b> Everything is published by default over the single endpoint below — under <b>Toolsets</b>, untick a whole set, or open a set and untick individual tools, to leave them out. (MCP shows the client a flat tool list; toolsets are just how you organize it.)</li>
            <li><b>Set an API key</b> below — the shared server-to-server credential (<span className="mono-sm">Authorization: Bearer &lt;key&gt;</span>). Without a key the server is closed.</li>
            <li><b>Add the endpoint</b> to your MCP client (Claude Desktop, Cursor, VS Code) with the config block below — that one URL is all a client needs.</li>
            <li><b>Authenticate each user</b> — pick one: a <b>personal access token</b> (each user generates one below and pastes it into their client); <b>OAuth 2.1</b> (when enabled, the client discovers Forge and the user logs in — nothing to copy); or <b>your own backend</b> (mint a session token / use Connect and pass the user&apos;s session in <span className="mono-sm">X-Forge-Context</span>).</li>
            <li><b>Act as the user downstream.</b> So a tool calls <em>your</em> app as that user, connect each user&apos;s account under <b>Auth providers</b> (Forge stores a per-user credential) or inject their session via <span className="mono-sm">{"{{ctx.*}}"}</span>. The MCP token itself is never forwarded. Your app owns its users &amp; sessions; Forge only carries the identity.</li>
          </ol>
        </Collapse>
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="t-h3" style={{ marginBottom: 8 }}>MCP endpoint</div>
          <CodeBlock code={url} />
          <div className="field-help">JSON-RPC over HTTP (initialize / tools/list / tools/call) · this is the Forge API host directly (not the console) so OAuth sign-in / auto-registration work · authenticate with the API key or a personal token below.</div>
        </div>
        {/* Authentication: the API key and personal access token are alternatives - a client
            pastes ONE of them as its Bearer token - so they live behind a two-tab switch. */}
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="row spread" style={{ marginBottom: 10, alignItems: "center" }}>
            <div className="t-h3">Authentication</div>
            <Segmented options={[{ value: "key", label: "API key" }, { value: "pat", label: "Personal access token" }]} value={credTab} onChange={(v) => setCredTab(v as "key" | "pat")} />
          </div>
          <div className="field-help" style={{ marginBottom: 12 }}>Use <b>one</b> of these as the <span className="mono-sm">Bearer</span> token in the config below — the shared <b>API key</b> (server-to-server, one identity) <b>or</b> your own <b>personal access token</b> (acts as you).</div>
          {credTab === "key" ? (
            <div className="fade-in">
              <div className="row gap2">
                <input className="input mono" style={{ flex: 1 }} type="text" value={apiKey} onChange={(e) => setApiKey(e.target.value)} onBlur={(e) => saveKey(e.target.value)} placeholder="Set a key to expose the server" />
                <button className="btn btn-secondary btn-sm" onClick={genKey}><Icon name="refresh" size={13} />Generate</button>
                <span className="t-caption fg-2" style={{ alignSelf: "center", whiteSpace: "nowrap" }}>{save === "saving" ? "Saving…" : save === "saved" ? "Saved ✓" : ""}</span>
              </div>
              <div className="field-help">Shared server-to-server credential. Without a key the endpoint is closed to everyone.</div>
            </div>
          ) : (
            <div className="fade-in">
              <div className="field-help" style={{ marginBottom: 10 }}>
                A per-user token to paste into your own MCP client instead of the shared key — the server then acts as <b>you</b> (your entitlements). Shown once on creation; store it safely.
              </div>
              {newToken && (
                <div className="card" style={{ padding: 10, marginBottom: 10, background: "var(--bg-2)" }}>
                  <div className="t-caption fg-2" style={{ marginBottom: 4 }}>New token — copy now, it won&apos;t be shown again:</div>
                  <CodeBlock code={newToken} />
                </div>
              )}
              <div className="row gap2" style={{ marginBottom: 10 }}>
                <button className="btn btn-secondary btn-sm" onClick={genToken}><Icon name="plus" size={13} />Generate token</button>
              </div>
              <div className="col gap1">
                {mcpTokens.map((t) => (
                  <div key={t.id} className="row spread" style={{ padding: "6px 0", borderTop: "1px solid var(--line)" }}>
                    <div className="row gap2" style={{ alignItems: "center", minWidth: 0 }}>
                      <Icon name="auth" size={13} style={{ color: "var(--fg-2)" }} />
                      <span className="mono-sm truncate">{t.name}</span>
                      <span className="typechip">{t.prefix}…</span>
                      {t.status !== "active" && <span className="pill pill-muted" style={{ height: 16 }}>{t.status}</span>}
                    </div>
                    {t.status === "active" && <button className="iconbtn" title="Revoke token" onClick={() => revokeToken(t.id)}><Icon name="trash" size={14} /></button>}
                  </div>
                ))}
                {mcpTokens.length === 0 && <div className="fg-2 t-caption">No personal tokens yet.</div>}
              </div>
            </div>
          )}
        </div>
        {perUserAps.length > 0 && (
          <div className="card" style={{ padding: 16, marginBottom: 14 }}>
            <div className="t-h3" style={{ marginBottom: 6 }}>Connect your accounts</div>
            <div className="field-help" style={{ marginBottom: 12 }}>
              These tools call downstream systems <b>as you</b>. Paste your own token for each — stored per-user and encrypted, used only for your calls. This is what lets your MCP tool calls act as you, with no shared secret and no admin setup.
            </div>
            <div className="col gap2">
              {perUserAps.map((ap) => <MyConnectionCard key={ap.id} project={project} ap={ap} />)}
            </div>
          </div>
        )}
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="t-h3" style={{ marginBottom: 8 }}>Claude Desktop / Cursor config</div>
          <CodeBlock code={claudeConfig} />
        </div>
        {/* Project-level tools: the whole workflow, knowledge-base search, and curated Q&A lookup,
            each a project.config flag on the server (mcp_server.py). Independent of toolsets. */}
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="row spread" style={{ marginBottom: 8 }}>
            <div className="t-h3">Project tools</div>
            <span className="t-caption fg-2">{cfgSave === "saving" ? "Saving…" : cfgSave === "saved" ? "Saved ✓" : ""}</span>
          </div>
          <div className="field-help" style={{ marginBottom: 12 }}>
            Publish this project&apos;s built-in capabilities as MCP tools, independent of toolsets. Knowledge and Q&amp;A search this project&apos;s knowledge base; the workflow tool runs the workflow chosen under <b>Run API</b>.
          </div>
          <div className="col gap1">
            <div className="row spread" style={{ padding: "10px 12px", border: "1px solid var(--line)", borderRadius: 9, gap: 12, alignItems: "flex-start" }}>
              <div className="col" style={{ gap: 2, minWidth: 0 }}>
                <span className="mono-sm" style={{ fontWeight: 700, color: exposeWf ? "var(--fg-0)" : "var(--fg-2)" }}>{wfToolName || "run_workflow"}</span>
                <span className="t-caption fg-2">Run the whole configured workflow as a single tool.</span>
                {exposeWf && (
                  <div className="row gap2" style={{ marginTop: 6, alignItems: "center" }}>
                    <span className="t-caption fg-2">Tool name</span>
                    <input className="input mono" style={{ maxWidth: 240, height: 30 }} value={wfToolName}
                      onChange={(e) => setWfToolName(e.target.value)}
                      onBlur={(e) => { const v = e.target.value.trim().replace(/[^a-zA-Z0-9_-]/g, "_"); setWfToolName(v || "run_workflow"); saveCfg({ mcp_workflow_tool_name: v && v !== "run_workflow" ? v : undefined }); }}
                      placeholder="run_workflow" />
                  </div>
                )}
              </div>
              <Toggle on={exposeWf} onChange={(v) => { setExposeWf(v); saveCfg({ mcp_expose_workflow: v || undefined }); }} />
            </div>
            <div className="row spread" style={{ padding: "10px 12px", border: "1px solid var(--line)", borderRadius: 9, gap: 12, alignItems: "flex-start" }}>
              <div className="col" style={{ gap: 2, minWidth: 0 }}>
                <span className="mono-sm" style={{ fontWeight: 700, color: exposeKnowledge ? "var(--fg-0)" : "var(--fg-2)" }}>search_knowledge_base</span>
                <span className="t-caption fg-2">Vector search over this project&apos;s knowledge-base documents.</span>
              </div>
              <Toggle on={exposeKnowledge} onChange={(v) => { setExposeKnowledge(v); saveCfg({ mcp_expose_knowledge: v || undefined }); }} />
            </div>
            <div className="row spread" style={{ padding: "10px 12px", border: "1px solid var(--line)", borderRadius: 9, gap: 12, alignItems: "flex-start" }}>
              <div className="col" style={{ gap: 2, minWidth: 0 }}>
                <span className="mono-sm" style={{ fontWeight: 700, color: exposeFaq ? "var(--fg-0)" : "var(--fg-2)" }}>lookup_faq</span>
                <span className="t-caption fg-2">Semantic match over this project&apos;s curated Q&amp;A / FAQ pairs.</span>
              </div>
              <Toggle on={exposeFaq} onChange={(v) => { setExposeFaq(v); saveCfg({ mcp_expose_faq: v || undefined }); }} />
            </div>
          </div>
        </div>
        <div className="card" style={{ padding: 16, marginBottom: 14 }}>
          <div className="row spread" style={{ marginBottom: 8 }}>
            <div className="t-h3">Toolsets</div>
            <span className="t-caption fg-2">{tsSave === "saving" ? "Saving…" : tsSave === "saved" ? "Saved ✓" : ""}</span>
          </div>
          <div className="field-help" style={{ marginBottom: 10 }}>
            Everything is exposed by default over the single MCP endpoint above — untick a toolset, or open one and untick individual tools, to leave them out. Create and fill sets on the <b>Tools</b> screen.
          </div>
          {toolSets.length === 0 && <div className="fg-2 t-caption">No tool sets yet — create one on the Tools screen.</div>}
          <div className="col gap1">
            {toolSets.map((ts) => {
              const open = openSets.has(ts.id);
              const members = ts.tool_ids.map((id) => toolById.get(id)).filter(Boolean) as typeof tools;
              const shown = ts.exposed ? members.filter((t) => !excluded.includes(t.id)).length : 0;
              return (
                <div key={ts.id} className="card" style={{ padding: 0, overflow: "hidden" }}>
                  <div className="row spread" style={{ padding: "8px 10px", cursor: "pointer" }} onClick={() => toggleOpenSet(ts.id)}>
                    <div className="row gap2" style={{ alignItems: "center", minWidth: 0 }}>
                      <Icon name={open ? "chevdown" : "chevright"} size={14} style={{ color: "var(--fg-2)", flex: "none" }} />
                      <span className="mono-sm truncate">{ts.name}</span>
                      <span className="t-caption fg-2">{shown}/{members.length}</span>
                      {!ts.exposed && <span className="pill pill-muted" style={{ height: 16 }}>excluded</span>}
                    </div>
                    <label className="row gap1" style={{ alignItems: "center", cursor: "pointer", flex: "none" }} onClick={(e) => e.stopPropagation()} title="Expose this whole toolset over MCP">
                      <input type="checkbox" checked={ts.exposed} onChange={() => toggleExposed(ts)} />
                      <span className="t-caption fg-2">Expose</span>
                    </label>
                  </div>
                  {open && (
                    <div className="col gap1" style={{ padding: "6px 12px 10px 30px", borderTop: "1px solid var(--line)" }}>
                      {members.length === 0 && <div className="t-caption fg-2">No tools in this set yet.</div>}
                      {members.map((t) => (
                        <label key={t.id} className="row gap2" style={{ alignItems: "center", cursor: ts.exposed ? "pointer" : "default", opacity: ts.exposed ? 1 : 0.5 }}>
                          <input type="checkbox" disabled={!ts.exposed} checked={ts.exposed && !excluded.includes(t.id)} onChange={() => toggleToolExcluded(t.id)} />
                          <span className="mono-sm">{t.name}</span><span className="typechip">{t.kind}</span>
                        </label>
                      ))}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
        <div className="card" style={{ padding: 16 }}>
          <div className="t-h3" style={{ marginBottom: 10 }}>Currently exposed tools ({exposedTools.length + projectTools.length})</div>
          <div className="col gap2">
            {projectTools.map((t) => (
              <div key={t.name} className="row gap2"><Icon name="connect" size={14} style={{ color: "var(--accent)" }} /><span className="mono-sm">{t.name}</span><span className="typechip">{t.kind}</span></div>
            ))}
            {exposedTools.map((t) => (
              <div key={t.id} className="row gap2"><Icon name="tools" size={14} style={{ color: "var(--fg-2)" }} /><span className="mono-sm">{t.name}</span><span className="typechip">{t.kind}</span></div>
            ))}
            {exposedTools.length === 0 && projectTools.length === 0 && <div className="fg-2 t-caption">Nothing exposed yet — enable a project tool above, or put tools in a set and toggle it on.</div>}
          </div>
        </div>
              </>
            )}

            {section === "embed" && <EmbedPanel project={project} />}
          </div>
        </div>
      </div>
    </div>
  );
}
