import { useCallback, useEffect, useMemo, useState } from "react"
import {
  CheckCircle2,
  Clock,
  Copy,
  History,
  KeyRound,
  Pause,
  Play,
  Plus,
  RefreshCw,
  RotateCw,
  Send,
  Trash2,
  Webhook as WebhookIcon,
} 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 { Badge } from "~/components/ui/badge"
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 { Textarea } from "~/components/ui/textarea"
import {
  COMMON_WEBHOOK_EVENTS,
  createWebhook,
  deleteWebhook,
  listWebhookDeliveries,
  listWebhooks,
  pauseWebhook,
  regenerateWebhookSecret,
  resumeWebhook,
  testWebhook,
  updateWebhook,
  type Webhook,
  type WebhookDelivery,
  type WebhookInput,
  type WebhookRetryStrategy,
  type WebhookStatus,
} from "~/lib/arcadia/webhooks"
import { pageTitle } from "~/lib/page-meta"
import { useSession } from "~/lib/session"
import { useRegisterAdminContext } from "~/lib/admin-context"

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

type EditorState =
  | { mode: "create" }
  | { mode: "edit"; webhook: Webhook }
  | null

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

  const [webhooks, setWebhooks] = useState<Webhook[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [info, setInfo] = useState<string | null>(null)
  const [search, setSearch] = useState("")
  const [editor, setEditor] = useState<EditorState>(null)
  const [pendingDelete, setPendingDelete] = useState<Webhook | null>(null)
  const [deliveriesFor, setDeliveriesFor] = useState<Webhook | null>(null)
  const [revealedSecret, setRevealedSecret] = useState<{
    webhookId: string
    secret: string
    isNew?: boolean
  } | null>(null)

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

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

  const columns = useMemo<Column<Webhook>[]>(
    () => [
      {
        id: "url",
        header: "URL",
        accessor: "url",
        sortable: true,
        cell: (w) => (
          <div className="flex flex-col">
            <span className="font-mono text-xs">{w.url}</span>
            {w.description ? (
              <span className="text-xs text-muted-foreground">{w.description}</span>
            ) : null}
          </div>
        ),
      },
      {
        id: "status",
        header: "Status",
        accessor: "status",
        sortable: true,
        cell: (w) => <BadgeCell label={w.status} tone={statusTone(w.status)} />,
      },
      {
        id: "events",
        header: "Events",
        cell: (w) =>
          w.events.length === 0 ? (
            <span className="text-muted-foreground">all</span>
          ) : (
            <span className="text-xs">{w.events.length}</span>
          ),
      },
      {
        id: "success",
        header: "Success",
        accessor: "success_count",
        sortable: true,
        cell: (w) => <span className="font-mono text-xs">{w.success_count}</span>,
      },
      {
        id: "failure",
        header: "Failure",
        accessor: "failure_count",
        sortable: true,
        cell: (w) => (
          <span
            className={`font-mono text-xs ${
              w.failure_count > 0 ? "text-destructive" : "text-muted-foreground"
            }`}
          >
            {w.failure_count}
          </span>
        ),
      },
      {
        id: "last",
        header: "Last triggered",
        accessor: "last_triggered_at",
        sortable: true,
        cell: (w) =>
          w.last_triggered_at ? (
            <DateCell value={w.last_triggered_at} format="short" />
          ) : (
            <span className="text-muted-foreground">never</span>
          ),
      },
      {
        id: "actions",
        header: "",
        align: "right",
        cell: (w) => (
          <ActionsCell
            items={rowActions(w, {
              arcadia,
              refresh,
              setEditor,
              setPendingDelete,
              setDeliveriesFor,
              setRevealedSecret,
              setError,
              setInfo,
            })}
            triggerDataAction={`webhook-${w.id}-actions`}
          />
        ),
      },
    ],
    [arcadia, refresh],
  )

  const summary = useMemo(
    () => ({
      total: webhooks.length,
      byStatus: countBy(webhooks, (w) => w.status),
      total_failures: webhooks.reduce((a, w) => a + w.failure_count, 0),
      total_successes: webhooks.reduce((a, w) => a + w.success_count, 0),
      webhooks: webhooks.map((w) => ({
        url: w.url,
        status: w.status,
        events: w.events,
        success_count: w.success_count,
        failure_count: w.failure_count,
        last_triggered_at: w.last_triggered_at,
      })),
    }),
    [webhooks],
  )
  useRegisterAdminContext("webhooks", summary)

  const table = useTable<Webhook>({
    data: webhooks,
    columns,
    getRowId: (w) => w.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">Webhooks</h1>
            <p className="text-sm text-muted-foreground">
              Outbound HTTP callbacks for platform events. Each delivery is signed with the
              endpoint's secret.
            </p>
          </div>
          <div className="flex items-center gap-2">
            <Button
              variant="outline"
              size="sm"
              onClick={refresh}
              disabled={loading}
              data-action="webhooks-refresh"
            >
              <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
              Refresh
            </Button>
            <Button
              size="sm"
              onClick={() => setEditor({ mode: "create" })}
              data-action="webhooks-create"
            >
              <Plus className="size-4" />
              New webhook
            </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 items-center gap-3">
            <SearchInput
              value={search}
              onValueChange={setSearch}
              placeholder="Search by URL, description, or status"
              data-action="webhooks-search"
              className="max-w-sm flex-1"
            />
            <div className="ml-auto text-xs text-muted-foreground">
              {table.total} of {webhooks.length}
            </div>
          </CardHeader>

          <CardContent className="relative p-0">
            <LoadingOverlay active={loading && webhooks.length === 0} label="Loading webhooks…" />
            {table.total === 0 && !loading ? (
              <EmptyState
                icon={<WebhookIcon className="size-6" />}
                title={search ? "No webhooks match." : "No webhooks yet."}
                description={
                  search
                    ? "Try a different search."
                    : "Add an endpoint to receive event notifications from arcadia."
                }
                className="py-12"
              />
            ) : (
              <>
                <DataTable
                  columns={columns}
                  rows={table.pageRows}
                  getRowId={(w) => w.id}
                  sort={table.sort}
                  onSortToggle={table.toggleSort}
                  loading={loading && webhooks.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 webhook?"
        description={
          pendingDelete
            ? `${pendingDelete.url} will stop receiving events. Pending retries are abandoned.`
            : ""
        }
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          try {
            await deleteWebhook(arcadia, pendingDelete.id)
            setPendingDelete(null)
            setInfo("Webhook deleted.")
            await refresh()
          } catch (err) {
            setError(err instanceof ArcadiaError ? err.message : "Delete failed.")
            setPendingDelete(null)
          }
        }}
      />

      <WebhookEditorDialog
        state={editor}
        onClose={() => setEditor(null)}
        onSaved={async (created) => {
          setEditor(null)
          if (created?.secret) {
            setRevealedSecret({ webhookId: created.id, secret: created.secret, isNew: true })
          }
          await refresh()
        }}
        onError={setError}
      />

      <DeliveriesDialog
        webhook={deliveriesFor}
        onClose={() => setDeliveriesFor(null)}
        onError={setError}
      />

      <RevealSecretDialog reveal={revealedSecret} onClose={() => setRevealedSecret(null)} />
    </AppShell>
  )
}

function statusTone(s: WebhookStatus): BadgeTone {
  if (s === "active") return "success"
  if (s === "paused") return "warning"
  return "default"
}

function rowActions(
  w: Webhook,
  ctx: {
    arcadia: ReturnType<typeof useArcadiaClient>
    refresh: () => Promise<void>
    setEditor: (s: EditorState) => void
    setPendingDelete: (w: Webhook | null) => void
    setDeliveriesFor: (w: Webhook | null) => void
    setRevealedSecret: (
      r: { webhookId: string; secret: string; isNew?: boolean } | null,
    ) => void
    setError: (m: string | null) => void
    setInfo: (m: string | null) => void
  },
): ActionItem[] {
  const {
    arcadia,
    refresh,
    setEditor,
    setPendingDelete,
    setDeliveriesFor,
    setRevealedSecret,
    setError,
    setInfo,
  } = ctx
  const items: ActionItem[] = []

  items.push({
    id: "edit",
    label: "Edit",
    dataAction: `webhook-${w.id}-edit`,
    onSelect: () => setEditor({ mode: "edit", webhook: w }),
  })
  items.push({
    id: "deliveries",
    label: "View deliveries",
    icon: <History className="size-4" />,
    dataAction: `webhook-${w.id}-deliveries`,
    onSelect: () => setDeliveriesFor(w),
  })
  items.push({
    id: "test",
    label: "Send test event",
    icon: <Send className="size-4" />,
    dataAction: `webhook-${w.id}-test`,
    onSelect: async () => {
      try {
        const r = await testWebhook(arcadia, w.id)
        setInfo(r.ok === false ? r.message ?? "Test failed." : "Test event sent.")
      } catch (err) {
        setError(err instanceof ArcadiaError ? err.message : "Test failed.")
      }
    },
  })

  if (w.status === "active") {
    items.push({
      id: "pause",
      label: "Pause",
      icon: <Pause className="size-4" />,
      dataAction: `webhook-${w.id}-pause`,
      onSelect: async () => {
        try {
          await pauseWebhook(arcadia, w.id)
          setInfo("Webhook paused.")
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Pause failed.")
        }
      },
    })
  } else {
    items.push({
      id: "resume",
      label: "Resume",
      icon: <Play className="size-4" />,
      dataAction: `webhook-${w.id}-resume`,
      onSelect: async () => {
        try {
          await resumeWebhook(arcadia, w.id)
          setInfo("Webhook resumed.")
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Resume failed.")
        }
      },
    })
  }

  items.push({
    id: "regen-secret",
    label: "Regenerate secret",
    icon: <RotateCw className="size-4" />,
    dataAction: `webhook-${w.id}-regen-secret`,
    onSelect: async () => {
      try {
        const updated = await regenerateWebhookSecret(arcadia, w.id)
        if (updated.secret) {
          setRevealedSecret({ webhookId: updated.id, secret: updated.secret })
        }
        await refresh()
      } catch (err) {
        setError(err instanceof ArcadiaError ? err.message : "Regenerate failed.")
      }
    },
  })

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

  return items
}

function WebhookEditorDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: EditorState
  onClose: () => void
  onSaved: (created?: Webhook) => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const open = state !== null
  const isEdit = state?.mode === "edit"
  const initial = isEdit ? state.webhook : null

  const [url, setUrl] = useState("")
  const [description, setDescription] = useState("")
  const [eventsText, setEventsText] = useState("")
  const [headersText, setHeadersText] = useState("")
  const [maxRetries, setMaxRetries] = useState("3")
  const [retryStrategy, setRetryStrategy] = useState<WebhookRetryStrategy>("exponential")
  const [saving, setSaving] = useState(false)

  useEffect(() => {
    if (!open) return
    if (initial) {
      setUrl(initial.url)
      setDescription(initial.description ?? "")
      setEventsText(initial.events.join("\n"))
      setHeadersText(
        Object.entries(initial.headers ?? {})
          .map(([k, v]) => `${k}: ${v}`)
          .join("\n"),
      )
      setMaxRetries(String(initial.max_retries))
      setRetryStrategy(initial.retry_strategy)
    } else {
      setUrl("")
      setDescription("")
      setEventsText("")
      setHeadersText("")
      setMaxRetries("3")
      setRetryStrategy("exponential")
    }
  }, [open, initial])

  const submit = async () => {
    onError(null)
    setSaving(true)
    try {
      const events = eventsText
        .split(/\r?\n/)
        .map((s) => s.trim())
        .filter(Boolean)
      const headers: Record<string, string> = {}
      for (const line of headersText.split(/\r?\n/)) {
        const idx = line.indexOf(":")
        if (idx <= 0) continue
        const k = line.slice(0, idx).trim()
        const v = line.slice(idx + 1).trim()
        if (k) headers[k] = v
      }
      const input: WebhookInput = {
        url,
        description: description || null,
        events,
        headers,
        max_retries: Math.max(0, Number(maxRetries) || 0),
        retry_strategy: retryStrategy,
      }
      if (isEdit && initial) {
        const updated = await updateWebhook(arcadia, initial.id, input)
        await onSaved(updated)
      } else {
        const created = await createWebhook(arcadia, input)
        await onSaved(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 webhook" : "New webhook"}</DialogTitle>
          <DialogDescription>
            {isEdit
              ? "Update the destination and event filter."
              : "Arcadia POSTs JSON payloads to this URL when the listed events fire."}
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-3">
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="webhook-url">URL</Label>
            <Input
              id="webhook-url"
              value={url}
              onChange={(e) => setUrl(e.target.value)}
              placeholder="https://example.com/webhooks/arcadia"
              data-action="webhook-form-url"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="webhook-description">Description</Label>
            <Input
              id="webhook-description"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              data-action="webhook-form-description"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="webhook-events">Events (one per line, blank = all events)</Label>
            <Textarea
              id="webhook-events"
              rows={6}
              value={eventsText}
              onChange={(e) => setEventsText(e.target.value)}
              placeholder={COMMON_WEBHOOK_EVENTS.slice(0, 5).join("\n")}
              data-action="webhook-form-events"
            />
            <div className="flex flex-wrap gap-1">
              {COMMON_WEBHOOK_EVENTS.map((ev) => {
                const has = eventsText
                  .split(/\r?\n/)
                  .some((l) => l.trim() === ev)
                return (
                  <button
                    key={ev}
                    type="button"
                    onClick={() => {
                      setEventsText((prev) => {
                        const lines = prev.split(/\r?\n/).map((l) => l.trim()).filter(Boolean)
                        if (has) return lines.filter((l) => l !== ev).join("\n")
                        return [...lines, ev].join("\n")
                      })
                    }}
                    data-action={`webhook-form-event-${ev}`}
                    className={`rounded-full border px-2 py-0.5 text-xs font-medium transition-colors ${
                      has
                        ? "border-primary bg-primary/10 text-primary"
                        : "border-border text-muted-foreground hover:bg-accent"
                    }`}
                  >
                    {ev}
                  </button>
                )
              })}
            </div>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="webhook-headers">Custom headers (key: value, one per line)</Label>
            <Textarea
              id="webhook-headers"
              rows={3}
              value={headersText}
              onChange={(e) => setHeadersText(e.target.value)}
              placeholder="Authorization: Bearer xyz"
              data-action="webhook-form-headers"
            />
          </div>
          <div className="grid grid-cols-2 gap-3">
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="webhook-retries">Max retries</Label>
              <Input
                id="webhook-retries"
                type="number"
                min={0}
                value={maxRetries}
                onChange={(e) => setMaxRetries(e.target.value)}
                data-action="webhook-form-retries"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label>Retry strategy</Label>
              <Select
                value={retryStrategy}
                onValueChange={(v) => setRetryStrategy(v as WebhookRetryStrategy)}
              >
                <SelectTrigger data-action="webhook-form-retry-strategy">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="exponential">Exponential</SelectItem>
                  <SelectItem value="linear">Linear</SelectItem>
                </SelectContent>
              </Select>
            </div>
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={saving} data-action="webhook-form-cancel">
            Cancel
          </Button>
          <Button onClick={submit} disabled={saving || !url} data-action="webhook-form-save">
            {saving ? <RefreshCw className="size-4 animate-spin" /> : <CheckCircle2 className="size-4" />}
            {isEdit ? "Save" : "Create"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function DeliveriesDialog({
  webhook,
  onClose,
  onError,
}: {
  webhook: Webhook | null
  onClose: () => void
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const [deliveries, setDeliveries] = useState<WebhookDelivery[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    if (!webhook) return
    let mounted = true
    setLoading(true)
    listWebhookDeliveries(arcadia, webhook.id, { limit: 50 })
      .then((d) => mounted && setDeliveries(d))
      .catch((err) =>
        onError(err instanceof ArcadiaError ? err.message : "Failed to load deliveries."),
      )
      .finally(() => mounted && setLoading(false))
    return () => {
      mounted = false
    }
  }, [arcadia, webhook, onError])

  if (!webhook) return null

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-3xl max-h-[85vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Recent deliveries</DialogTitle>
          <DialogDescription>
            <span className="font-mono text-xs">{webhook.url}</span>
          </DialogDescription>
        </DialogHeader>

        {loading ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            <RefreshCw className="mr-1 inline size-3.5 animate-spin" /> Loading…
          </p>
        ) : deliveries.length === 0 ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            No deliveries recorded yet.
          </p>
        ) : (
          <ul className="flex flex-col divide-y rounded-md border">
            {deliveries.map((d) => (
              <li key={d.id} className="flex items-start justify-between gap-3 px-3 py-2 text-sm">
                <div className="flex flex-col gap-0.5">
                  <span className="flex items-center gap-2">
                    <Badge
                      variant={
                        d.status === "delivered"
                          ? "default"
                          : d.status === "failed"
                            ? "destructive"
                            : "secondary"
                      }
                    >
                      {d.status}
                    </Badge>
                    <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
                      {d.event_type}
                    </code>
                    {d.response_status ? (
                      <span className="text-xs text-muted-foreground">
                        HTTP {d.response_status}
                      </span>
                    ) : null}
                    {d.response_time_ms != null ? (
                      <span className="text-xs text-muted-foreground">
                        {d.response_time_ms}ms
                      </span>
                    ) : null}
                  </span>
                  <span className="text-xs text-muted-foreground">
                    <Clock className="mr-1 inline size-3" />
                    attempt {d.attempt} ·{" "}
                    {d.completed_at
                      ? new Date(d.completed_at).toLocaleString()
                      : new Date(d.inserted_at).toLocaleString()}
                  </span>
                  {d.error_message ? (
                    <span className="text-xs text-destructive">{d.error_message}</span>
                  ) : null}
                  {d.next_retry_at ? (
                    <span className="text-xs text-muted-foreground">
                      next retry {new Date(d.next_retry_at).toLocaleString()}
                    </span>
                  ) : null}
                </div>
              </li>
            ))}
          </ul>
        )}

        <DialogFooter>
          <Button variant="outline" onClick={onClose} data-action="webhook-deliveries-close">
            Close
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function RevealSecretDialog({
  reveal,
  onClose,
}: {
  reveal: { webhookId: string; secret: string; isNew?: boolean } | null
  onClose: () => void
}) {
  const [copied, setCopied] = useState(false)
  useEffect(() => {
    if (!reveal) setCopied(false)
  }, [reveal])

  if (!reveal) return null

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(reveal.secret)
      setCopied(true)
    } catch {}
  }

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <KeyRound className="size-5 text-amber-500" />
            {reveal.isNew ? "Webhook secret" : "New webhook secret"}
          </DialogTitle>
          <DialogDescription>
            <strong>This is the only time the secret will be shown.</strong> Copy it now — store it
            with your verifying code so you can validate the X-Signature header on incoming
            deliveries.
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-2 rounded-md border bg-muted/30 p-3">
          <code className="select-all break-all font-mono text-xs">{reveal.secret}</code>
          <Button size="sm" variant="outline" onClick={copy} data-action="webhook-secret-copy">
            {copied ? <CheckCircle2 className="size-3.5" /> : <Copy className="size-3.5" />}
            {copied ? "Copied" : "Copy"}
          </Button>
        </div>

        <DialogFooter>
          <Button onClick={onClose} data-action="webhook-secret-close">
            Done
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

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
  }, {})
}
