import { useCallback, useEffect, useMemo, useState } from "react"
import {
  AlertTriangle,
  Clock,
  History,
  KeyRound,
  Pause,
  Play,
  Plus,
  RefreshCw,
  RotateCw,
  Trash2,
} from "lucide-react"

import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client"
import {
  ActionsCell,
  BadgeCell,
  DataTable,
  DateCell,
  Pagination,
  useTable,
  type ActionItem,
  type BadgeTone,
  type Column,
} from "@crema/table-ui"
import { SearchInput } from "@crema/search-ui"
import { AlertBanner, ConfirmDialog, EmptyState, LoadingOverlay } from "@crema/feedback-ui"

import { AppShell } from "~/components/layout/app-shell"
import { Button } from "~/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "~/components/ui/card"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "~/components/ui/dialog"
import { Input } from "~/components/ui/input"
import { Label } from "~/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "~/components/ui/select"
import { Switch } from "~/components/ui/switch"
import { Textarea } from "~/components/ui/textarea"
import { Badge } from "~/components/ui/badge"
import {
  createSecret,
  deleteSecret,
  disableSecret,
  enableSecret,
  generateSecretValue,
  listSecretVersions,
  listSecrets,
  rollbackSecret,
  rotateSecret,
  SECRET_CATEGORIES,
  SECRET_ENVIRONMENTS,
  updateSecretMeta,
  type Secret,
  type SecretCategory,
  type SecretCreateInput,
  type SecretEnvironment,
  type SecretVersion,
} from "~/lib/arcadia/secrets"
import { pageTitle } from "~/lib/page-meta"
import { useSession } from "~/lib/session"
import { useRegisterAdminContext } from "~/lib/admin-context"

export const meta = () => pageTitle("Secrets")

type EditorState =
  | { mode: "create" }
  | { mode: "edit"; secret: Secret }
  | { mode: "rotate"; secret: Secret }
  | { mode: "versions"; secret: Secret }
  | null

export default function SecretsRoute() {
  const session = useSession()
  const arcadia = useArcadiaClient()

  const [secrets, setSecrets] = useState<Secret[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [info, setInfo] = useState<string | null>(null)
  const [search, setSearch] = useState("")
  const [categoryFilter, setCategoryFilter] = useState<"all" | SecretCategory>("all")
  const [editor, setEditor] = useState<EditorState>(null)
  const [pendingDelete, setPendingDelete] = useState<Secret | null>(null)

  const refresh = useCallback(async () => {
    setError(null)
    setLoading(true)
    try {
      setSecrets(await listSecrets(arcadia))
    } catch (err) {
      setError(err instanceof ArcadiaError ? err.message : "Failed to load secrets.")
    } finally {
      setLoading(false)
    }
  }, [arcadia])

  useEffect(() => {
    if (session) refresh()
  }, [session, refresh])

  const filtered = useMemo(
    () =>
      categoryFilter === "all"
        ? secrets
        : secrets.filter((s) => s.category === categoryFilter),
    [secrets, categoryFilter],
  )

  const columns = useMemo<Column<Secret>[]>(
    () => [
      {
        id: "name",
        header: "Name",
        accessor: "name",
        sortable: true,
        cell: (s) => (
          <span className="flex items-center gap-2 font-medium">
            <KeyRound className="size-3.5 text-muted-foreground" />
            <code className="font-mono text-xs">{s.name}</code>
          </span>
        ),
      },
      {
        id: "category",
        header: "Category",
        accessor: "category",
        sortable: true,
        cell: (s) => (
          <Badge variant="secondary" className="font-mono text-xs">
            {s.category}
          </Badge>
        ),
      },
      {
        id: "environment",
        header: "Env",
        accessor: "environment",
        sortable: true,
        cell: (s) => (
          <span className="text-muted-foreground text-xs uppercase">{s.environment}</span>
        ),
      },
      {
        id: "scope",
        header: "Scope",
        cell: (s) => (
          <span className="text-muted-foreground text-xs">
            {s.tenant_id ? "tenant" : "platform"}
          </span>
        ),
      },
      {
        id: "status",
        header: "Status",
        cell: (s) => <BadgeCell label={statusLabel(s)} tone={statusTone(s)} />,
      },
      {
        id: "rotated",
        header: "Last rotated",
        accessor: "last_rotated_at",
        sortable: true,
        cell: (s) =>
          s.last_rotated_at ? (
            <DateCell value={s.last_rotated_at} format="short" />
          ) : (
            <span className="text-muted-foreground">never</span>
          ),
      },
      {
        id: "expires",
        header: "Expires",
        accessor: "expires_at",
        sortable: true,
        cell: (s) =>
          s.expires_at ? <DateCell value={s.expires_at} format="short" /> : <span>—</span>,
      },
      {
        id: "actions",
        header: "",
        align: "right",
        cell: (s) => (
          <ActionsCell
            items={rowActions(s, {
              arcadia,
              refresh,
              setEditor,
              setPendingDelete,
              setError,
              setInfo,
            })}
            triggerDataAction={`secret-${s.name}-actions`}
          />
        ),
      },
    ],
    [arcadia, refresh],
  )

  const summary = useMemo(
    () => ({
      total: secrets.length,
      byCategory: countBy(secrets, (s) => s.category),
      disabled: secrets.filter((s) => !s.enabled).length,
      rotation_due: secrets.filter((s) => s.rotation_due).length,
      expired: secrets.filter((s) => s.expired).length,
      secrets: secrets.map((s) => ({
        name: s.name,
        category: s.category,
        environment: s.environment,
        scope: s.tenant_id ? "tenant" : "platform",
        enabled: s.enabled,
        rotation_due: s.rotation_due,
        expired: s.expired,
      })),
    }),
    [secrets],
  )
  useRegisterAdminContext("secrets", summary)

  const table = useTable<Secret>({
    data: filtered,
    columns,
    getRowId: (s) => s.id,
    initialPageSize: 25,
    initialSearch: search,
  })
  useEffect(() => {
    table.setSearch(search)
  }, [search, table])

  return (
    <AppShell>
      <div className="flex flex-col gap-4">
        <header className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <div>
            <h1 className="text-2xl font-semibold tracking-tight">Secrets</h1>
            <p className="text-sm text-muted-foreground">
              Encrypted credentials and tokens. Values are write-only once stored.
            </p>
          </div>
          <div className="flex items-center gap-2">
            <Button
              variant="outline"
              size="sm"
              onClick={refresh}
              disabled={loading}
              data-action="secrets-refresh"
            >
              <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
              Refresh
            </Button>
            <Button
              size="sm"
              onClick={() => setEditor({ mode: "create" })}
              data-action="secrets-create"
            >
              <Plus className="size-4" />
              New secret
            </Button>
          </div>
        </header>

        {error ? (
          <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
            {error}
          </AlertBanner>
        ) : null}
        {info ? (
          <AlertBanner variant="success" dismissible onDismiss={() => setInfo(null)}>
            {info}
          </AlertBanner>
        ) : null}

        <Card>
          <CardHeader className="flex flex-row flex-wrap items-center gap-3">
            <SearchInput
              value={search}
              onValueChange={setSearch}
              placeholder="Search by name or tag"
              data-action="secrets-search"
              className="max-w-sm flex-1"
            />
            <Select
              value={categoryFilter}
              onValueChange={(v) => setCategoryFilter(v as typeof categoryFilter)}
            >
              <SelectTrigger className="w-44" data-action="secrets-category-filter">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="all">All categories</SelectItem>
                {SECRET_CATEGORIES.map((c) => (
                  <SelectItem key={c.value} value={c.value}>
                    {c.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            <div className="ml-auto text-xs text-muted-foreground">
              {table.total} of {secrets.length}
            </div>
          </CardHeader>

          <CardContent className="relative p-0">
            <LoadingOverlay active={loading && secrets.length === 0} label="Loading secrets…" />
            {table.total === 0 && !loading ? (
              <EmptyState
                title={
                  search || categoryFilter !== "all"
                    ? "No secrets match those filters."
                    : "No secrets yet."
                }
                description={
                  search || categoryFilter !== "all"
                    ? "Try a different search or category."
                    : "Create your first secret. The value is encrypted at rest and never returned by the API."
                }
                className="py-12"
              />
            ) : (
              <>
                <DataTable
                  columns={columns}
                  rows={table.pageRows}
                  getRowId={(s) => s.id}
                  sort={table.sort}
                  onSortToggle={table.toggleSort}
                  loading={loading && secrets.length > 0}
                  stickyHeader
                />
                <Pagination
                  page={table.page}
                  pageSize={table.pageSize}
                  total={table.total}
                  onPageChange={table.setPage}
                  onPageSizeChange={table.setPageSize}
                />
              </>
            )}
          </CardContent>
        </Card>
      </div>

      <ConfirmDialog
        open={pendingDelete !== null}
        onOpenChange={(o) => !o && setPendingDelete(null)}
        title="Delete secret?"
        description={
          pendingDelete
            ? `${pendingDelete.name} and all its versions will be permanently removed. Any system referencing this secret will fail.`
            : ""
        }
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          try {
            await deleteSecret(arcadia, pendingDelete.id)
            setPendingDelete(null)
            setInfo("Secret deleted.")
            await refresh()
          } catch (err) {
            setError(err instanceof ArcadiaError ? err.message : "Delete failed.")
            setPendingDelete(null)
          }
        }}
      />

      <SecretEditorDialog
        state={editor}
        onClose={() => setEditor(null)}
        onSaved={async (msg) => {
          setEditor(null)
          if (msg) setInfo(msg)
          await refresh()
        }}
        onError={setError}
      />
    </AppShell>
  )
}

function statusLabel(s: Secret): string {
  if (s.expired) return "expired"
  if (!s.enabled) return "disabled"
  if (s.rotation_due) return "rotate"
  return "active"
}

function statusTone(s: Secret): BadgeTone {
  if (s.expired) return "danger"
  if (!s.enabled) return "default"
  if (s.rotation_due) return "warning"
  return "success"
}

function rowActions(
  s: Secret,
  ctx: {
    arcadia: ReturnType<typeof useArcadiaClient>
    refresh: () => Promise<void>
    setEditor: (e: EditorState) => void
    setPendingDelete: (s: Secret | null) => void
    setError: (m: string | null) => void
    setInfo: (m: string | null) => void
  },
): ActionItem[] {
  const { arcadia, refresh, setEditor, setPendingDelete, setError, setInfo } = ctx
  const items: ActionItem[] = []

  items.push({
    id: "rotate",
    label: "Rotate value",
    icon: <RotateCw className="size-4" />,
    dataAction: `secret-${s.name}-rotate`,
    onSelect: () => setEditor({ mode: "rotate", secret: s }),
  })
  items.push({
    id: "edit",
    label: "Edit metadata",
    dataAction: `secret-${s.name}-edit`,
    onSelect: () => setEditor({ mode: "edit", secret: s }),
  })
  items.push({
    id: "versions",
    label: "View versions",
    icon: <History className="size-4" />,
    dataAction: `secret-${s.name}-versions`,
    onSelect: () => setEditor({ mode: "versions", secret: s }),
  })

  if (s.enabled) {
    items.push({
      id: "disable",
      label: "Disable",
      icon: <Pause className="size-4" />,
      dataAction: `secret-${s.name}-disable`,
      onSelect: async () => {
        try {
          await disableSecret(arcadia, s.id)
          setInfo(`${s.name} disabled.`)
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Disable failed.")
        }
      },
    })
  } else {
    items.push({
      id: "enable",
      label: "Enable",
      icon: <Play className="size-4" />,
      dataAction: `secret-${s.name}-enable`,
      onSelect: async () => {
        try {
          await enableSecret(arcadia, s.id)
          setInfo(`${s.name} enabled.`)
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Enable failed.")
        }
      },
    })
  }

  items.push({
    id: "delete",
    label: "Delete",
    icon: <Trash2 className="size-4" />,
    destructive: true,
    dataAction: `secret-${s.name}-delete`,
    onSelect: () => setPendingDelete(s),
  })

  return items
}

function SecretEditorDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: EditorState
  onClose: () => void
  onSaved: (info?: string) => Promise<void>
  onError: (msg: string | null) => void
}) {
  if (state?.mode === "versions") {
    return <VersionsDialog state={state} onClose={onClose} onSaved={onSaved} onError={onError} />
  }
  if (state?.mode === "rotate") {
    return <RotateDialog state={state} onClose={onClose} onSaved={onSaved} onError={onError} />
  }
  return <UpsertDialog state={state} onClose={onClose} onSaved={onSaved} onError={onError} />
}

function UpsertDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: EditorState
  onClose: () => void
  onSaved: (info?: string) => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const open = state?.mode === "create" || state?.mode === "edit"
  const isEdit = state?.mode === "edit"
  const initial = isEdit ? state.secret : null

  const [name, setName] = useState("")
  const [value, setValue] = useState("")
  const [category, setCategory] = useState<SecretCategory>("api_key")
  const [environment, setEnvironment] = useState<SecretEnvironment>("all")
  const [description, setDescription] = useState("")
  const [tagsText, setTagsText] = useState("")
  const [usedByText, setUsedByText] = useState("")
  const [allowedIpsText, setAllowedIpsText] = useState("")
  const [readOnce, setReadOnce] = useState(false)
  const [expiresAt, setExpiresAt] = useState("")
  const [rotationDays, setRotationDays] = useState("")
  const [generating, setGenerating] = useState(false)
  const [saving, setSaving] = useState(false)

  useEffect(() => {
    if (!open) return
    if (initial) {
      setName(initial.name)
      setValue("")
      setCategory(initial.category)
      setEnvironment(initial.environment)
      setDescription(initial.description ?? "")
      setTagsText(initial.tags.join(", "))
      setUsedByText(initial.used_by.join(", "))
      setAllowedIpsText(initial.allowed_ips.join(", "))
      setReadOnce(initial.read_once)
      setExpiresAt(initial.expires_at ? initial.expires_at.slice(0, 16) : "")
      setRotationDays(
        initial.rotation_interval_days == null ? "" : String(initial.rotation_interval_days),
      )
    } else {
      setName("")
      setValue("")
      setCategory("api_key")
      setEnvironment("all")
      setDescription("")
      setTagsText("")
      setUsedByText("")
      setAllowedIpsText("")
      setReadOnce(false)
      setExpiresAt("")
      setRotationDays("")
    }
  }, [open, initial])

  const generate = async () => {
    setGenerating(true)
    try {
      const v = await generateSecretValue(arcadia, { length: 48 })
      setValue(v)
    } catch (err) {
      onError(err instanceof ArcadiaError ? err.message : "Generate failed.")
    } finally {
      setGenerating(false)
    }
  }

  const submit = async () => {
    onError(null)
    setSaving(true)
    try {
      const tags = csv(tagsText)
      const used_by = csv(usedByText)
      const allowed_ips = csv(allowedIpsText)
      const rotation_interval_days =
        rotationDays.trim() === "" ? null : Number(rotationDays)
      if (rotation_interval_days != null && Number.isNaN(rotation_interval_days)) {
        throw new Error("Rotation interval must be a number of days.")
      }
      const expires_at = expiresAt ? new Date(expiresAt).toISOString() : null

      if (isEdit && initial) {
        await updateSecretMeta(arcadia, initial.id, {
          category,
          environment,
          description: description || null,
          tags,
          used_by,
          allowed_ips,
          read_once: readOnce,
          expires_at,
          rotation_interval_days,
        })
        await onSaved("Secret metadata updated.")
      } else {
        if (!value) throw new Error("A value is required for new secrets.")
        const input: SecretCreateInput = {
          name,
          value,
          category,
          environment,
          description: description || undefined,
          tags,
          used_by,
          allowed_ips,
          read_once: readOnce,
          expires_at,
          rotation_interval_days,
        }
        await createSecret(arcadia, input)
        await onSaved("Secret created.")
      }
    } catch (err) {
      onError(err instanceof ArcadiaError ? err.message : err instanceof Error ? err.message : "Save failed.")
    } finally {
      setSaving(false)
    }
  }

  return (
    <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>{isEdit ? `Edit ${initial?.name}` : "New secret"}</DialogTitle>
          <DialogDescription>
            {isEdit
              ? "Update metadata only. Use Rotate to change the stored value."
              : "Names allow letters, numbers, dots, underscores, and hyphens. Values are encrypted at rest and never returned by the API."}
          </DialogDescription>
        </DialogHeader>

        <div className="grid grid-cols-2 gap-3">
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="secret-name">Name</Label>
            <Input
              id="secret-name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="llm-openai-api-key"
              disabled={isEdit}
              data-action="secret-form-name"
            />
          </div>

          {!isEdit ? (
            <div className="col-span-2 flex flex-col gap-1.5">
              <div className="flex items-center justify-between">
                <Label htmlFor="secret-value">Value</Label>
                <Button
                  type="button"
                  variant="ghost"
                  size="sm"
                  onClick={generate}
                  disabled={generating}
                  data-action="secret-form-generate"
                >
                  {generating ? <RefreshCw className="size-3.5 animate-spin" /> : null}
                  Generate
                </Button>
              </div>
              <Input
                id="secret-value"
                type="password"
                value={value}
                onChange={(e) => setValue(e.target.value)}
                placeholder="Paste the secret value"
                data-action="secret-form-value"
              />
              <p className="text-xs text-muted-foreground">
                You won't see this value again after saving.
              </p>
            </div>
          ) : null}

          <div className="flex flex-col gap-1.5">
            <Label>Category</Label>
            <Select value={category} onValueChange={(v) => setCategory(v as SecretCategory)}>
              <SelectTrigger data-action="secret-form-category">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {SECRET_CATEGORIES.map((c) => (
                  <SelectItem key={c.value} value={c.value}>
                    {c.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="flex flex-col gap-1.5">
            <Label>Environment</Label>
            <Select
              value={environment}
              onValueChange={(v) => setEnvironment(v as SecretEnvironment)}
            >
              <SelectTrigger data-action="secret-form-environment">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {SECRET_ENVIRONMENTS.map((e) => (
                  <SelectItem key={e.value} value={e.value}>
                    {e.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="secret-description">Description</Label>
            <Input
              id="secret-description"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              data-action="secret-form-description"
            />
          </div>

          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="secret-tags">Tags (comma-separated)</Label>
            <Input
              id="secret-tags"
              value={tagsText}
              onChange={(e) => setTagsText(e.target.value)}
              placeholder="llm, openai, billing"
              data-action="secret-form-tags"
            />
          </div>

          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="secret-used-by">Used by (comma-separated services)</Label>
            <Input
              id="secret-used-by"
              value={usedByText}
              onChange={(e) => setUsedByText(e.target.value)}
              placeholder="arcadia-admin, vibespace"
              data-action="secret-form-used-by"
            />
          </div>

          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="secret-allowed-ips">Allowed IPs (CIDR, comma-separated)</Label>
            <Input
              id="secret-allowed-ips"
              value={allowedIpsText}
              onChange={(e) => setAllowedIpsText(e.target.value)}
              placeholder="10.0.0.0/8, 192.168.1.100"
              data-action="secret-form-allowed-ips"
            />
            <p className="text-xs text-muted-foreground">
              Empty means unrestricted. Non-empty denies any caller not matched.
            </p>
          </div>

          <div className="flex flex-col gap-1.5">
            <Label htmlFor="secret-expires">Expires at</Label>
            <Input
              id="secret-expires"
              type="datetime-local"
              value={expiresAt}
              onChange={(e) => setExpiresAt(e.target.value)}
              data-action="secret-form-expires"
            />
          </div>

          <div className="flex flex-col gap-1.5">
            <Label htmlFor="secret-rotation-days">Rotation interval (days)</Label>
            <Input
              id="secret-rotation-days"
              value={rotationDays}
              onChange={(e) => setRotationDays(e.target.value)}
              placeholder="90"
              data-action="secret-form-rotation-days"
            />
          </div>

          <div className="col-span-2 flex items-center justify-between rounded-md border px-3 py-2">
            <div>
              <div className="text-sm font-medium">Read-once</div>
              <div className="text-xs text-muted-foreground">
                The value can be retrieved exactly once. Useful for one-time bootstrap tokens.
              </div>
            </div>
            <Switch
              checked={readOnce}
              onCheckedChange={setReadOnce}
              data-action="secret-form-read-once"
            />
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={saving} data-action="secret-form-cancel">
            Cancel
          </Button>
          <Button
            onClick={submit}
            disabled={saving || !name.trim() || (!isEdit && !value)}
            data-action="secret-form-save"
          >
            {saving ? <RefreshCw className="size-4 animate-spin" /> : null}
            {isEdit ? "Save changes" : "Create secret"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function RotateDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: { mode: "rotate"; secret: Secret }
  onClose: () => void
  onSaved: (info?: string) => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const [value, setValue] = useState("")
  const [note, setNote] = useState("")
  const [saving, setSaving] = useState(false)
  const [generating, setGenerating] = useState(false)

  useEffect(() => {
    setValue("")
    setNote("")
  }, [state])

  const generate = async () => {
    setGenerating(true)
    try {
      setValue(await generateSecretValue(arcadia, { length: 48 }))
    } catch (err) {
      onError(err instanceof ArcadiaError ? err.message : "Generate failed.")
    } finally {
      setGenerating(false)
    }
  }

  const submit = async () => {
    onError(null)
    setSaving(true)
    try {
      await rotateSecret(arcadia, state.secret.id, { value, note: note || undefined })
      await onSaved(`${state.secret.name} rotated.`)
    } catch (err) {
      onError(err instanceof ArcadiaError ? err.message : err instanceof Error ? err.message : "Rotate failed.")
    } finally {
      setSaving(false)
    }
  }

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>Rotate {state.secret.name}</DialogTitle>
          <DialogDescription>
            The current value becomes a previous version you can roll back to. Read-once consumption
            is reset.
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-3">
          <div className="flex flex-col gap-1.5">
            <div className="flex items-center justify-between">
              <Label htmlFor="rotate-value">New value</Label>
              <Button
                type="button"
                variant="ghost"
                size="sm"
                onClick={generate}
                disabled={generating}
                data-action="secret-rotate-generate"
              >
                {generating ? <RefreshCw className="size-3.5 animate-spin" /> : null}
                Generate
              </Button>
            </div>
            <Input
              id="rotate-value"
              type="password"
              value={value}
              onChange={(e) => setValue(e.target.value)}
              data-action="secret-rotate-value"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="rotate-note">Note (optional)</Label>
            <Textarea
              id="rotate-note"
              value={note}
              onChange={(e) => setNote(e.target.value)}
              rows={2}
              placeholder="Reason for rotation"
              data-action="secret-rotate-note"
            />
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={saving} data-action="secret-rotate-cancel">
            Cancel
          </Button>
          <Button onClick={submit} disabled={saving || !value} data-action="secret-rotate-save">
            {saving ? <RefreshCw className="size-4 animate-spin" /> : <RotateCw className="size-4" />}
            Rotate
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function VersionsDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: { mode: "versions"; secret: Secret }
  onClose: () => void
  onSaved: (info?: string) => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const [versions, setVersions] = useState<SecretVersion[]>([])
  const [loading, setLoading] = useState(true)
  const [pendingRollback, setPendingRollback] = useState<SecretVersion | null>(null)

  useEffect(() => {
    let mounted = true
    setLoading(true)
    listSecretVersions(arcadia, state.secret.id)
      .then((v) => {
        if (mounted) setVersions(v.sort((a, b) => b.version - a.version))
      })
      .catch((err) => {
        if (mounted)
          onError(err instanceof ArcadiaError ? err.message : "Failed to load versions.")
      })
      .finally(() => {
        if (mounted) setLoading(false)
      })
    return () => {
      mounted = false
    }
  }, [arcadia, state.secret.id, onError])

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-lg">
        <DialogHeader>
          <DialogTitle>Versions of {state.secret.name}</DialogTitle>
          <DialogDescription>
            Each rotation creates a version. Roll back to restore a previous value.
          </DialogDescription>
        </DialogHeader>

        {loading ? (
          <div className="flex items-center justify-center py-6 text-sm text-muted-foreground">
            <RefreshCw className="mr-2 size-4 animate-spin" /> Loading…
          </div>
        ) : versions.length === 0 ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            No previous versions yet. Rotate the value to create one.
          </p>
        ) : (
          <ul className="flex flex-col divide-y rounded-md border">
            {versions.map((v) => (
              <li key={v.id} className="flex items-center justify-between gap-3 px-3 py-2">
                <div className="flex flex-col">
                  <span className="text-sm font-medium">Version {v.version}</span>
                  <span className="text-xs text-muted-foreground">
                    <Clock className="mr-1 inline size-3" />
                    {new Date(v.inserted_at).toLocaleString()}
                    {v.note ? ` — ${v.note}` : ""}
                  </span>
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setPendingRollback(v)}
                  data-action={`secret-version-${v.version}-rollback`}
                >
                  <RotateCw className="size-3.5" />
                  Roll back
                </Button>
              </li>
            ))}
          </ul>
        )}

        <DialogFooter>
          <Button variant="outline" onClick={onClose} data-action="secret-versions-close">
            Close
          </Button>
        </DialogFooter>

        <ConfirmDialog
          open={pendingRollback !== null}
          onOpenChange={(o) => !o && setPendingRollback(null)}
          title="Roll back to this version?"
          description={
            pendingRollback
              ? `${state.secret.name} will be reset to version ${pendingRollback.version}. The current value becomes a new version.`
              : ""
          }
          confirmLabel="Roll back"
          variant="default"
          onConfirm={async () => {
            if (!pendingRollback) return
            try {
              await rollbackSecret(arcadia, state.secret.id, pendingRollback.version)
              setPendingRollback(null)
              await onSaved(`Rolled back to version ${pendingRollback.version}.`)
            } catch (err) {
              onError(err instanceof ArcadiaError ? err.message : "Rollback failed.")
              setPendingRollback(null)
            }
          }}
        />
      </DialogContent>
    </Dialog>
  )
}

function csv(s: string): string[] {
  return s
    .split(",")
    .map((x) => x.trim())
    .filter(Boolean)
}

function countBy<T>(arr: T[], key: (x: T) => string): Record<string, number> {
  return arr.reduce<Record<string, number>>((acc, x) => {
    const k = key(x)
    acc[k] = (acc[k] ?? 0) + 1
    return acc
  }, {})
}
