"use client";

/**
 * Workspace settings panel: shows workspace identity + API keys
 * (mint / list / revoke), and renders ready-to-paste config for
 * Cursor and other VS Code-style IDEs so an AI agent can act on
 * behalf of this workspace.
 *
 * The full plaintext token is shown ONCE in the post-create modal
 * and never again — `key_hash` is all we persist server-side.
 */

import { useCallback, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { toast } from "sonner";
import { Copy, Download, Eye, EyeOff, KeyRound, Loader2, Plus, RefreshCw, Trash2 } from "lucide-react";

interface WorkspaceSummary {
  id: string;
  slug: string;
  name: string;
  coolifyProjectUuid: string | null;
  coolifyTeamId: number | null;
  giteaOrg: string | null;
  giteaBotUsername?: string | null;
  giteaBotReady?: boolean;
  provisionStatus: "pending" | "partial" | "ready" | "error";
  provisionError: string | null;
}

interface GiteaBotCreds {
  bot: { username: string; token: string };
  gitea: { apiBase: string; host: string; cloneUrlTemplate: string; sshRemoteTemplate: string; webUrlTemplate: string };
  workspace: { slug: string; giteaOrg: string };
}

interface ApiKey {
  id: string;
  name: string;
  prefix: string;
  scopes: string[];
  created_by: string;
  last_used_at: string | null;
  revoked_at: string | null;
  created_at: string;
  /**
   * True if the server still has the encrypted plaintext and can reveal
   * it again on demand. False for legacy keys minted before the
   * key_encrypted column was added — those can only be rotated.
   */
  revealable: boolean;
}

interface MintedKey {
  id: string;
  name: string;
  prefix: string;
  token: string;
  createdAt: string;
}

const APP_BASE =
  typeof window !== "undefined" ? window.location.origin : "https://vibnai.com";

/**
 * The URL param (e.g. `/mark-account/settings`) is treated as a hint —
 * the actual workspace is resolved via `GET /api/workspaces`, which is
 * keyed off the signed-in user. That endpoint also lazy-creates the
 * workspace row for users who signed in before the workspace hook
 * landed, so this panel never gets stuck on a missing row.
 */
export function WorkspaceKeysPanel({ workspaceSlug: _urlHint }: { workspaceSlug?: string }) {
  const [workspace, setWorkspace] = useState<WorkspaceSummary | null>(null);
  const [keys, setKeys] = useState<ApiKey[]>([]);
  const [loading, setLoading] = useState(true);
  const [errorMsg, setErrorMsg] = useState<string | null>(null);
  const [provisioning, setProvisioning] = useState(false);

  const [showCreate, setShowCreate] = useState(false);
  const [newName, setNewName] = useState("");
  const [creating, setCreating] = useState(false);

  const [minted, setMinted] = useState<MintedKey | null>(null);
  const [keyToRevoke, setKeyToRevoke] = useState<ApiKey | null>(null);
  const [revoking, setRevoking] = useState(false);

  const refresh = useCallback(async () => {
    setLoading(true);
    setErrorMsg(null);
    try {
      const listRes = await fetch(`/api/workspaces`, { credentials: "include" });
      if (!listRes.ok) {
        const body = listRes.status === 401
          ? "Sign in to view your workspace."
          : `Couldn't load workspace (HTTP ${listRes.status}).`;
        setErrorMsg(body);
        setWorkspace(null);
        setKeys([]);
        return;
      }
      const list = (await listRes.json()) as { workspaces: WorkspaceSummary[] };
      const ws = list.workspaces?.[0] ?? null;
      setWorkspace(ws);
      if (!ws) {
        setKeys([]);
        return;
      }
      const keysRes = await fetch(`/api/workspaces/${ws.slug}/keys`, { credentials: "include" });
      if (keysRes.ok) {
        const j = (await keysRes.json()) as { keys: ApiKey[] };
        setKeys(j.keys ?? []);
      }
    } catch (err) {
      console.error("[workspace-keys] refresh failed", err);
      setErrorMsg(err instanceof Error ? err.message : String(err));
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    refresh();
  }, [refresh]);

  const provision = useCallback(async () => {
    if (!workspace) return;
    setProvisioning(true);
    try {
      const res = await fetch(`/api/workspaces/${workspace.slug}/provision`, {
        method: "POST",
        credentials: "include",
      });
      if (!res.ok) throw new Error(await res.text());
      toast.success("Provisioning re-run");
      await refresh();
    } catch (err) {
      toast.error(`Provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setProvisioning(false);
    }
  }, [workspace, refresh]);

  const createKey = useCallback(async () => {
    if (!workspace) return;
    if (!newName.trim()) {
      toast.error("Give the key a name");
      return;
    }
    setCreating(true);
    try {
      const res = await fetch(`/api/workspaces/${workspace.slug}/keys`, {
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name: newName.trim() }),
      });
      const j = await res.json();
      if (!res.ok) throw new Error(j.error ?? "Failed");
      setShowCreate(false);
      setNewName("");
      setMinted(j as MintedKey);
      await refresh();
    } catch (err) {
      toast.error(`Could not mint key: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setCreating(false);
    }
  }, [workspace, newName, refresh]);

  const revokeKey = useCallback(async () => {
    if (!workspace || !keyToRevoke) return;
    setRevoking(true);
    try {
      const res = await fetch(
        `/api/workspaces/${workspace.slug}/keys/${keyToRevoke.id}`,
        { method: "DELETE", credentials: "include" }
      );
      if (!res.ok) throw new Error(await res.text());
      toast.success("Key revoked");
      setKeyToRevoke(null);
      await refresh();
    } catch (err) {
      toast.error(`Revoke failed: ${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setRevoking(false);
    }
  }, [workspace, keyToRevoke, refresh]);

  if (loading && !workspace) {
    return (
      <section style={cardStyle}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, color: "#a09a90", fontSize: 13 }}>
          <Loader2 className="animate-spin" size={14} /> Loading workspace…
        </div>
      </section>
    );
  }

  if (!workspace) {
    return (
      <section style={cardStyle}>
        <header style={cardHeaderStyle}>
          <div>
            <h2 style={cardTitleStyle}>Workspace</h2>
            <p style={cardSubtitleStyle}>
              {errorMsg ?? "No workspace yet — this usually means you signed in before AI access was rolled out."}
            </p>
          </div>
          <Button variant="outline" size="sm" onClick={refresh} disabled={loading}>
            {loading ? <Loader2 className="animate-spin" size={14} /> : <RefreshCw size={14} />}
            Try again
          </Button>
        </header>
        <p style={{ fontSize: 12.5, color: "#a09a90", margin: 0 }}>
          Try signing out and back in, then refresh this page. If the problem
          persists, the API may be down or your account may need to be
          provisioned manually.
        </p>
      </section>
    );
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 28 }}>
      <WorkspaceIdentityCard workspace={workspace} onProvision={provision} provisioning={provisioning} />

      <KeysCard
        workspace={workspace}
        keys={keys}
        onCreateClick={() => setShowCreate(true)}
        onRevokeClick={k => setKeyToRevoke(k)}
        onRefresh={refresh}
      />

      <AiAccessBundleCard workspace={workspace} />

      {/* ── Create key modal ─────────────────────────────────────── */}
      <Dialog open={showCreate} onOpenChange={setShowCreate}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Create workspace API key</DialogTitle>
            <DialogDescription>
              Used by AI agents (Cursor, Claude, scripts) to act on
              behalf of <code>{workspace.slug}</code>. You&apos;ll be able
              to reveal and copy the token again later from this page.
            </DialogDescription>
          </DialogHeader>
          <div style={{ display: "grid", gap: 8 }}>
            <Label htmlFor="key-name">Key name</Label>
            <Input
              id="key-name"
              placeholder="e.g. Cursor on my MacBook"
              value={newName}
              onChange={e => setNewName(e.target.value)}
              autoFocus
              onKeyDown={e => {
                if (e.key === "Enter") createKey();
              }}
            />
          </div>
          <DialogFooter>
            <Button variant="ghost" onClick={() => setShowCreate(false)} disabled={creating}>
              Cancel
            </Button>
            <Button onClick={createKey} disabled={creating || !newName.trim()}>
              {creating && <Loader2 className="animate-spin" size={14} />}
              Mint key
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ── Show minted secret ONCE ──────────────────────────────── */}
      <Dialog open={!!minted} onOpenChange={open => !open && setMinted(null)}>
        <DialogContent style={{ maxWidth: 640 }}>
          <DialogHeader>
            <DialogTitle>Your new API key</DialogTitle>
            <DialogDescription>
              Copy this into your AI tool now, or come back to this page
              later and click <em>Show key</em> to reveal it again.
            </DialogDescription>
          </DialogHeader>
          {minted && <MintedKeyView workspace={workspace} minted={minted} />}
          <DialogFooter>
            <Button onClick={() => setMinted(null)}>Done</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ── Revoke confirm ───────────────────────────────────────── */}
      <AlertDialog open={!!keyToRevoke} onOpenChange={open => !open && setKeyToRevoke(null)}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Revoke this key?</AlertDialogTitle>
            <AlertDialogDescription>
              Any agent using <code>{keyToRevoke?.prefix}…</code> will
              immediately lose access to {workspace.slug}. This cannot be undone.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel disabled={revoking}>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={revokeKey} disabled={revoking}>
              {revoking && <Loader2 className="animate-spin" size={14} />}
              Revoke
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  );
}

// ──────────────────────────────────────────────────
// Sub-components
// ──────────────────────────────────────────────────

function WorkspaceIdentityCard({
  workspace,
  onProvision,
  provisioning,
}: {
  workspace: WorkspaceSummary;
  onProvision: () => void;
  provisioning: boolean;
}) {
  const status = workspace.provisionStatus;
  const statusColor =
    status === "ready" ? "#10b981" : status === "partial" ? "#f59e0b" : status === "error" ? "#ef4444" : "#9ca3af";

  return (
    <section style={cardStyle}>
      <header style={cardHeaderStyle}>
        <div>
          <h2 style={cardTitleStyle}>Workspace</h2>
          <p style={cardSubtitleStyle}>
            Your tenant boundary on Coolify and Gitea. All AI access is scoped to this workspace.
          </p>
        </div>
        <Button
          variant="outline"
          size="sm"
          onClick={onProvision}
          disabled={provisioning}
        >
          {provisioning ? <Loader2 className="animate-spin" size={14} /> : <RefreshCw size={14} />}
          Re-provision
        </Button>
      </header>
      <dl style={kvGrid}>
        <Kv label="Slug" value={<code>{workspace.slug}</code>} />
        <Kv
          label="Status"
          value={
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
              <span style={{ width: 8, height: 8, borderRadius: "50%", background: statusColor }} />
              {status}
            </span>
          }
        />
        <Kv
          label="Coolify Project"
          value={
            workspace.coolifyProjectUuid ? (
              <code style={{ fontSize: 11 }}>{workspace.coolifyProjectUuid}</code>
            ) : (
              <span style={{ color: "#a09a90" }}>not provisioned</span>
            )
          }
        />
        <Kv
          label="Gitea Org"
          value={
            workspace.giteaOrg ? (
              <code>{workspace.giteaOrg}</code>
            ) : (
              <span style={{ color: "#a09a90" }}>not provisioned</span>
            )
          }
        />
      </dl>
      {workspace.provisionError && (
        <div
          style={{
            marginTop: 12,
            padding: "10px 12px",
            background: "#fef2f2",
            border: "1px solid #fecaca",
            borderRadius: 8,
            color: "#991b1b",
            fontSize: 12,
            fontFamily: "monospace",
          }}
        >
          {workspace.provisionError}
        </div>
      )}
    </section>
  );
}

function KeysCard({
  workspace,
  keys,
  onCreateClick,
  onRevokeClick,
  onRefresh,
}: {
  workspace: WorkspaceSummary;
  keys: ApiKey[];
  onCreateClick: () => void;
  onRevokeClick: (k: ApiKey) => void;
  onRefresh: () => void;
}) {
  const active = useMemo(() => keys.filter(k => !k.revoked_at), [keys]);
  const revoked = useMemo(() => keys.filter(k => k.revoked_at), [keys]);

  return (
    <section style={cardStyle}>
      <header style={cardHeaderStyle}>
        <div>
          <h2 style={cardTitleStyle}>API keys</h2>
          <p style={cardSubtitleStyle}>
            Tokens scoped to <code>{workspace.slug}</code>. Use them in Cursor,
            Claude Code, the CLI, or any HTTP client. Click <em>Show key</em> on
            any row to reveal the full token.
          </p>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <Button variant="ghost" size="sm" onClick={onRefresh}>
            <RefreshCw size={14} />
          </Button>
          <Button size="sm" onClick={onCreateClick}>
            <Plus size={14} />
            New key
          </Button>
        </div>
      </header>

      {active.length === 0 ? (
        <EmptyKeysState onCreateClick={onCreateClick} />
      ) : (
        <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 8 }}>
          {active.map(k => (
            <KeyRow
              key={k.id}
              k={k}
              workspaceSlug={workspace.slug}
              onRevoke={() => onRevokeClick(k)}
            />
          ))}
        </ul>
      )}

      {revoked.length > 0 && (
        <details style={{ marginTop: 16 }}>
          <summary style={{ fontSize: 12, color: "#a09a90", cursor: "pointer" }}>
            {revoked.length} revoked
          </summary>
          <ul style={{ listStyle: "none", margin: "8px 0 0", padding: 0, display: "flex", flexDirection: "column", gap: 6, opacity: 0.6 }}>
            {revoked.map(k => (
              <KeyRow key={k.id} k={k} workspaceSlug={workspace.slug} />
            ))}
          </ul>
        </details>
      )}
    </section>
  );
}

function KeyRow({
  k,
  workspaceSlug,
  onRevoke,
}: {
  k: ApiKey;
  workspaceSlug: string;
  onRevoke?: () => void;
}) {
  const [token, setToken] = useState<string | null>(null);
  const [revealing, setRevealing] = useState(false);
  const [visible, setVisible] = useState(false);
  const isActive = !k.revoked_at;

  const reveal = useCallback(async () => {
    setRevealing(true);
    try {
      const res = await fetch(
        `/api/workspaces/${workspaceSlug}/keys/${k.id}/reveal`,
        { method: "POST", credentials: "include" },
      );
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        throw new Error(body?.error ?? `HTTP ${res.status}`);
      }
      setToken(body.token as string);
      setVisible(true);
    } catch (err) {
      toast.error(
        `Couldn't reveal key: ${err instanceof Error ? err.message : String(err)}`,
      );
    } finally {
      setRevealing(false);
    }
  }, [k.id, workspaceSlug]);

  const copy = useCallback(() => {
    if (!token) return;
    navigator.clipboard.writeText(token).then(
      () => toast.success("Key copied"),
      () => toast.error("Copy failed"),
    );
  }, [token]);

  const masked = token
    ? `${token.slice(0, 12)}${"•".repeat(24)}${token.slice(-4)}`
    : null;

  return (
    <li
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 8,
        padding: "10px 12px",
        background: "#fff",
        border: "1px solid #e8e4dc",
        borderRadius: 8,
      }}
    >
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <KeyRound size={16} style={{ color: "#a09a90" }} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: "#1a1a1a" }}>
            {k.name}
            {!k.revealable && isActive && (
              <span
                style={{
                  marginLeft: 8,
                  fontSize: 10,
                  fontWeight: 600,
                  letterSpacing: "0.04em",
                  textTransform: "uppercase",
                  color: "#a09a90",
                  border: "1px solid #e8e4dc",
                  padding: "1px 6px",
                  borderRadius: 4,
                }}
                title="Minted before reveal was enabled — rotate to get a revealable key"
              >
                legacy
              </span>
            )}
          </div>
          <div style={{ fontSize: 11, color: "#a09a90", fontFamily: "monospace" }}>
            {k.prefix}…
            {k.last_used_at
              ? ` · last used ${new Date(k.last_used_at).toLocaleString()}`
              : " · never used"}
          </div>
        </div>

        {isActive && k.revealable && !token && (
          <Button
            variant="outline"
            size="sm"
            onClick={reveal}
            disabled={revealing}
            aria-label="Show key"
          >
            {revealing ? <Loader2 className="animate-spin" size={14} /> : <Eye size={14} />}
            Show key
          </Button>
        )}

        {isActive && token && (
          <>
            <Button
              variant="ghost"
              size="sm"
              onClick={() => setVisible(v => !v)}
              aria-label={visible ? "Hide key" : "Show key"}
            >
              {visible ? <EyeOff size={14} /> : <Eye size={14} />}
            </Button>
            <Button variant="ghost" size="sm" onClick={copy} aria-label="Copy key">
              <Copy size={14} />
            </Button>
          </>
        )}

        {onRevoke && isActive && (
          <Button variant="ghost" size="sm" onClick={onRevoke} aria-label="Revoke">
            <Trash2 size={14} />
          </Button>
        )}
      </div>

      {token && (
        <code
          style={{
            display: "block",
            padding: "8px 10px",
            background: "#0f172a",
            color: "#e2e8f0",
            borderRadius: 6,
            fontFamily: "monospace",
            fontSize: 12,
            wordBreak: "break-all",
            userSelect: "all",
          }}
        >
          {visible ? token : masked}
        </code>
      )}
    </li>
  );
}

function EmptyKeysState({ onCreateClick }: { onCreateClick: () => void }) {
  return (
    <div
      style={{
        padding: "24px 16px",
        textAlign: "center",
        background: "#f9fafb",
        border: "1px dashed #e8e4dc",
        borderRadius: 8,
      }}
    >
      <KeyRound size={20} style={{ margin: "0 auto 8px", color: "#a09a90" }} />
      <div style={{ fontSize: 13, fontWeight: 600, color: "#1a1a1a" }}>No API keys yet</div>
      <div style={{ fontSize: 12, color: "#a09a90", marginTop: 4 }}>
        Mint one to let Cursor or any AI agent push code and deploy on your behalf.
      </div>
      <Button size="sm" onClick={onCreateClick} style={{ marginTop: 12 }}>
        <Plus size={14} />
        Create first key
      </Button>
    </div>
  );
}

// ──────────────────────────────────────────────────
// AI access bundle — one card that replaces all the scattered snippets
// ──────────────────────────────────────────────────

function AiAccessBundleCard({ workspace }: { workspace: WorkspaceSummary }) {
  const [botCreds, setBotCreds] = useState<GiteaBotCreds | null>(null);
  const [revealingBot, setRevealingBot] = useState(false);
  const [hideToken, setHideToken] = useState(true);

  const revealBotCreds = useCallback(async () => {
    setRevealingBot(true);
    try {
      const res = await fetch(`/api/workspaces/${workspace.slug}/gitea-credentials`, {
        credentials: "include",
      });
      if (!res.ok) {
        const body = await res.text();
        throw new Error(body || `HTTP ${res.status}`);
      }
      const j = (await res.json()) as GiteaBotCreds;
      setBotCreds(j);
      setHideToken(true);
    } catch (err) {
      toast.error(
        `Couldn't fetch Gitea bot: ${err instanceof Error ? err.message : String(err)}`
      );
    } finally {
      setRevealingBot(false);
    }
  }, [workspace.slug]);

  const promptBlock = useMemo(() => buildPromptBlock(workspace), [workspace]);
  const cursorRule = useMemo(() => buildCursorRule(workspace), [workspace]);
  const mcpJson = useMemo(
    () => buildMcpJson(workspace, "<paste-your-vibn_sk_-token>"),
    [workspace]
  );
  const envSnippet = useMemo(
    () => buildEnvSnippet(workspace, "<paste-your-vibn_sk_-token>"),
    [workspace]
  );
  const bootstrapCmd = useMemo(
    () =>
      `curl -sSfL -H "Authorization: Bearer $VIBN_API_KEY" ${APP_BASE}/api/workspaces/${workspace.slug}/bootstrap.sh | sh`,
    [workspace.slug]
  );

  return (
    <section style={cardStyle}>
      <header style={cardHeaderStyle}>
        <div>
          <h2 style={cardTitleStyle}>AI access bundle</h2>
          <p style={cardSubtitleStyle}>
            Everything an AI agent needs to act on this workspace: a
            system prompt, a one-line installer, and the low-level
            snippets if you prefer to wire it up by hand.
          </p>
        </div>
      </header>

      <FileBlock
        title="1. Paste into your AI's system prompt"
        description="Tells the agent how this workspace works and which endpoints to call. Works in ChatGPT, Claude, Cursor agent, etc."
        filename={`vibn-${workspace.slug}-prompt.md`}
        contents={promptBlock}
        language="markdown"
      />

      <FileBlock
        title="2. One-line setup inside any repo"
        description="Mint a key above, export it as VIBN_API_KEY, then run this. It writes .cursor/rules, .cursor/mcp.json, and .env.local."
        filename="vibn-bootstrap.sh"
        contents={bootstrapCmd}
        language="bash"
      />

      <GiteaBotReveal
        workspace={workspace}
        creds={botCreds}
        revealing={revealingBot}
        hideToken={hideToken}
        onReveal={revealBotCreds}
        onToggleHide={() => setHideToken(h => !h)}
      />

      <details style={{ marginTop: 18 }}>
        <summary style={{ cursor: "pointer", fontSize: 12.5, fontWeight: 600, color: "#1a1a1a" }}>
          Manual setup — individual files
        </summary>
        <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 12 }}>
          <FileBlock
            title=".cursor/rules/vibn-workspace.mdc"
            description="Tells the agent it can use the Vibn API and which workspace it's bound to."
            filename="vibn-workspace.mdc"
            contents={cursorRule}
            language="markdown"
          />
          <FileBlock
            title=".cursor/mcp.json"
            description="Registers Vibn as an MCP server. Paste your minted key in place of the placeholder."
            filename="mcp.json"
            contents={mcpJson}
            language="json"
          />
          <FileBlock
            title=".env.local"
            description="Expose the key as an env var for shell scripts."
            filename="vibn.env"
            contents={envSnippet}
            language="bash"
          />
        </div>
      </details>
    </section>
  );
}

function GiteaBotReveal({
  workspace,
  creds,
  revealing,
  hideToken,
  onReveal,
  onToggleHide,
}: {
  workspace: WorkspaceSummary;
  creds: GiteaBotCreds | null;
  revealing: boolean;
  hideToken: boolean;
  onReveal: () => void;
  onToggleHide: () => void;
}) {
  const cloneExample = creds
    ? creds.gitea.cloneUrlTemplate.replace("{{repo}}", "example-repo")
    : "";
  const displayedToken = creds
    ? hideToken
      ? `${creds.bot.token.slice(0, 6)}${"•".repeat(24)}${creds.bot.token.slice(-4)}`
      : creds.bot.token
    : "";

  return (
    <div
      style={{
        marginTop: 18,
        border: "1px solid #e8e4dc",
        borderRadius: 8,
        background: "#fff",
        padding: 14,
      }}
    >
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, color: "#1a1a1a" }}>
            3. Gitea bot credentials
          </div>
          <div style={{ fontSize: 12, color: "#a09a90", marginTop: 2, lineHeight: 1.5 }}>
            A dedicated Gitea user scoped to the <code>{workspace.giteaOrg ?? "(unprovisioned)"}</code> org.
            Use this to <code>git clone</code> / push without exposing any root token.
          </div>
        </div>
        {creds ? (
          <Button variant="ghost" size="sm" onClick={onToggleHide}>
            {hideToken ? "Show token" : "Hide"}
          </Button>
        ) : (
          <Button size="sm" onClick={onReveal} disabled={revealing}>
            {revealing ? <Loader2 className="animate-spin" size={14} /> : <KeyRound size={14} />}
            Reveal bot PAT
          </Button>
        )}
      </div>

      {creds && (
        <div style={{ marginTop: 12, display: "grid", gap: 8, fontSize: 12.5 }}>
          <KV k="Username" v={<code>{creds.bot.username}</code>} />
          <KV
            k="PAT"
            v={
              <code
                style={{ fontFamily: "monospace", wordBreak: "break-all", userSelect: "all" }}
              >
                {displayedToken}
              </code>
            }
          />
          <KV k="Org" v={<code>{creds.workspace.giteaOrg}</code>} />
          <KV k="API base" v={<code>{creds.gitea.apiBase}</code>} />
          <div style={{ marginTop: 6 }}>
            <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "#a09a90", marginBottom: 4 }}>
              Clone URL (example)
            </div>
            <code style={{ fontFamily: "monospace", fontSize: 11.5, color: "#1a1a1a", wordBreak: "break-all", userSelect: "all" }}>
              {hideToken
                ? cloneExample.replace(creds.bot.token, `${creds.bot.token.slice(0, 6)}…`)
                : cloneExample}
            </code>
          </div>
        </div>
      )}
    </div>
  );
}

function KV({ k, v }: { k: string; v: React.ReactNode }) {
  return (
    <div style={{ display: "flex", gap: 12, alignItems: "baseline", flexWrap: "wrap" }}>
      <div
        style={{
          fontSize: 10.5,
          fontWeight: 700,
          letterSpacing: "0.06em",
          textTransform: "uppercase",
          color: "#a09a90",
          minWidth: 80,
        }}
      >
        {k}
      </div>
      <div style={{ color: "#1a1a1a" }}>{v}</div>
    </div>
  );
}

function FileBlock({
  title,
  description,
  filename,
  contents,
  language,
}: {
  title: string;
  description: string;
  filename: string;
  contents: string;
  language: string;
}) {
  const copy = useCallback(() => {
    navigator.clipboard.writeText(contents).then(
      () => toast.success(`Copied ${title}`),
      () => toast.error("Copy failed")
    );
  }, [contents, title]);

  const download = useCallback(() => {
    const blob = new Blob([contents], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }, [contents, filename]);

  return (
    <div style={{ marginTop: 14, border: "1px solid #e8e4dc", borderRadius: 8, overflow: "hidden" }}>
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          padding: "10px 12px",
          background: "#f9fafb",
          borderBottom: "1px solid #e8e4dc",
          gap: 12,
        }}
      >
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 12.5, fontWeight: 600, color: "#1a1a1a", fontFamily: "monospace" }}>{title}</div>
          <div style={{ fontSize: 11.5, color: "#a09a90", marginTop: 2 }}>{description}</div>
        </div>
        <div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
          <Button variant="ghost" size="sm" onClick={copy} aria-label={`Copy ${filename}`}>
            <Copy size={14} />
          </Button>
          <Button variant="ghost" size="sm" onClick={download} aria-label={`Download ${filename}`}>
            <Download size={14} />
          </Button>
        </div>
      </div>
      <pre
        style={{
          margin: 0,
          padding: 12,
          background: "#0f172a",
          color: "#e2e8f0",
          fontSize: 12,
          lineHeight: 1.55,
          overflowX: "auto",
          fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
        }}
      >
        <code data-language={language}>{contents}</code>
      </pre>
    </div>
  );
}

function MintedKeyView({ workspace, minted }: { workspace: WorkspaceSummary; minted: MintedKey }) {
  const bootstrapCmd = `export VIBN_API_KEY=${minted.token}
curl -sSfL -H "Authorization: Bearer $VIBN_API_KEY" ${APP_BASE}/api/workspaces/${workspace.slug}/bootstrap.sh | sh`;
  const cursorRule = buildCursorRule(workspace);
  const mcpJson = buildMcpJson(workspace, minted.token);
  const envSnippet = buildEnvSnippet(workspace, minted.token);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <FileBlock
        title="Your key"
        description="Copy now, or reveal it later from the API keys list above."
        filename={`${workspace.slug}-${minted.name.replace(/\s+/g, "-")}.txt`}
        contents={minted.token}
        language="text"
      />
      <FileBlock
        title="One-line setup (recommended)"
        description="Paste into a terminal from the root of any repo. It writes .cursor/rules, .cursor/mcp.json, and .env.local for you."
        filename="vibn-bootstrap.sh"
        contents={bootstrapCmd}
        language="bash"
      />
      <details>
        <summary style={{ cursor: "pointer", fontSize: 12.5, fontWeight: 600, color: "#1a1a1a" }}>
          Manual setup — individual files
        </summary>
        <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 14 }}>
          <FileBlock
            title=".cursor/rules/vibn-workspace.mdc"
            description="Drop into your repo so Cursor agents know about the Vibn API."
            filename="vibn-workspace.mdc"
            contents={cursorRule}
            language="markdown"
          />
          <FileBlock
            title=".cursor/mcp.json (key embedded)"
            description="Paste into Cursor's MCP config to register Vibn as a tool source."
            filename="mcp.json"
            contents={mcpJson}
            language="json"
          />
          <FileBlock
            title=".env.local"
            description="For shell / script use."
            filename="vibn.env"
            contents={envSnippet}
            language="bash"
          />
        </div>
      </details>
    </div>
  );
}

function buildPromptBlock(w: WorkspaceSummary): string {
  return `You are acting on the Vibn workspace "${w.slug}".

API base: ${APP_BASE}
Authorization header: Bearer $VIBN_API_KEY (set in .env.local, never print it)

Before doing any git work call:
  GET ${APP_BASE}/api/workspaces/${w.slug}/gitea-credentials
That returns a workspace-scoped bot username, PAT, and a \`cloneUrlTemplate\`
with \`{{repo}}\` placeholder. Use that template for all \`git clone\` / push
remotes. Never use any other git credentials.

For deploys, logs, and env vars, use the workspace-scoped Coolify endpoints:
  GET   /api/workspaces/${w.slug}/apps
  GET   /api/workspaces/${w.slug}/apps/{uuid}
  POST  /api/workspaces/${w.slug}/apps/{uuid}/deploy
  GET   /api/workspaces/${w.slug}/apps/{uuid}/envs
  PATCH /api/workspaces/${w.slug}/apps/{uuid}/envs   body: {"key","value",...}
  DELETE /api/workspaces/${w.slug}/apps/{uuid}/envs?key=FOO

All responses are tenant-scoped — a 403 means you're touching another
workspace's resources, which is not allowed. Stop and ask the user.

Workspace identity:
- Gitea org:              ${w.giteaOrg ?? "(unprovisioned)"}
- Coolify project uuid:   ${w.coolifyProjectUuid ?? "(unprovisioned)"}
- Provision status:       ${w.provisionStatus}

Rules:
1. Ask before creating new projects, repos, or deployments.
2. Prefer PRs over force-pushing main.
3. Treat VIBN_API_KEY like a password — never echo or commit it.
`;
}

// ──────────────────────────────────────────────────
// File generators
// ──────────────────────────────────────────────────

function buildCursorRule(w: WorkspaceSummary): string {
  return `---
description: Vibn workspace "${w.slug}" — REST API for git/deploy via Coolify + Gitea
alwaysApply: true
---

# Vibn workspace: ${w.slug}

You have access to the Vibn workspace **${w.slug}** through the REST API
at \`${APP_BASE}\`. The API is scoped to this workspace only — the bearer
token cannot reach any other tenant.

## Auth

All requests must include:

\`\`\`
Authorization: Bearer $VIBN_API_KEY
\`\`\`

The token lives in \`.env.local\` as \`VIBN_API_KEY\`. Never print it.

## Workspace identity

- Coolify Project UUID: \`${w.coolifyProjectUuid ?? "(not provisioned)"}\`
- Gitea organization:   \`${w.giteaOrg ?? "(not provisioned)"}\`
- Provision status:     \`${w.provisionStatus}\`

All git operations should target the \`${w.giteaOrg ?? "(unset)"}\` org on
\`git.vibnai.com\`. All deployments live under the Coolify Project above.

## Useful endpoints (all under ${APP_BASE})

| Method | Path                                       | Purpose                  |
|-------:|--------------------------------------------|--------------------------|
| GET    | /api/workspaces/${w.slug}                  | Workspace details         |
| GET    | /api/workspaces/${w.slug}/keys             | List API keys             |
| POST   | /api/workspaces/${w.slug}/provision        | Re-run Coolify+Gitea provisioning |
| GET    | /api/projects                              | Projects in this workspace |
| POST   | /api/projects/create                       | Create a new project (provisions repo + Coolify apps) |
| GET    | /api/projects/{projectId}                  | Project details           |
| GET    | /api/projects/{projectId}/preview-url      | Live deploy URLs          |
| GET    | /api/projects/{projectId}/file?path=...    | Read a file from the repo |

## House rules for AI agents

1. Confirm with the user before creating new projects, repos, or deployments.
2. Use \`git.vibnai.com/${w.giteaOrg ?? "<org>"}/<repo>\` for git remotes.
3. Prefer issuing PRs over force-pushing to \`main\`.
4. If a request is rejected with 403, re-check that the key is for
   workspace \`${w.slug}\` (it cannot act on others).
5. Treat \`VIBN_API_KEY\` like a password — never echo, log, or commit it.
`;
}

function buildMcpJson(w: WorkspaceSummary, token: string): string {
  const config = {
    mcpServers: {
      [`vibn-${w.slug}`]: {
        url: `${APP_BASE}/api/mcp`,
        headers: {
          Authorization: `Bearer ${token}`,
        },
      },
    },
  };
  return JSON.stringify(config, null, 2);
}

function buildEnvSnippet(w: WorkspaceSummary, token: string): string {
  return `# Vibn workspace: ${w.slug}
# Generated ${new Date().toISOString()}
VIBN_API_BASE=${APP_BASE}
VIBN_WORKSPACE=${w.slug}
VIBN_API_KEY=${token}

# Quick smoke test:
#   curl -H "Authorization: Bearer $VIBN_API_KEY" $VIBN_API_BASE/api/workspaces/$VIBN_WORKSPACE
`;
}

// ──────────────────────────────────────────────────
// Inline styles (matches the rest of the dashboard's plain-CSS look)
// ──────────────────────────────────────────────────

const cardStyle: React.CSSProperties = {
  background: "var(--card-bg, #fff)",
  border: "1px solid #e8e4dc",
  borderRadius: 12,
  padding: 20,
};

const cardHeaderStyle: React.CSSProperties = {
  display: "flex",
  alignItems: "flex-start",
  justifyContent: "space-between",
  gap: 16,
  marginBottom: 16,
};

const cardTitleStyle: React.CSSProperties = {
  fontSize: 16,
  fontWeight: 700,
  color: "#1a1a1a",
  margin: 0,
  letterSpacing: "-0.01em",
};

const cardSubtitleStyle: React.CSSProperties = {
  fontSize: 12.5,
  color: "#a09a90",
  margin: "4px 0 0",
  lineHeight: 1.5,
  maxWidth: 520,
};

const kvGrid: React.CSSProperties = {
  display: "grid",
  gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
  gap: 12,
  margin: 0,
};

function Kv({ label, value }: { label: string; value: React.ReactNode }) {
  return (
    <div>
      <dt style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "#a09a90" }}>
        {label}
      </dt>
      <dd style={{ margin: "4px 0 0", fontSize: 13, color: "#1a1a1a", wordBreak: "break-all" }}>{value}</dd>
    </div>
  );
}
