import { useEffect, useState, useCallback } from 'react'
import {
  X,
  RefreshCw,
  Wrench,
  Film,
  Image as ImageIcon,
  AlertTriangle,
  Database,
  Loader2,
  Cpu,
  AlertCircle,
  CheckCircle2,
  Copy,
  Sparkles,
} from 'lucide-react'
import clsx from 'clsx'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
  library,
  type MediaType,
} from '../../services/api'
import { toast } from '../ToastContainer'

// React Query keys for the settings panels. Kept here (not in a shared
// hook module) since they're internal to this dialog and used by the
// runAction refresh step to invalidate after mutations.
const SETTINGS_THUMB_STATS_KEY = ['settings', 'thumbnail-stats'] as const
const SETTINGS_LIB_STATS_KEY = ['settings', 'library-stats'] as const
const SETTINGS_WORKER_STATUS_KEY = ['settings', 'worker-status'] as const
const SETTINGS_MISSING_STATS_KEY = ['settings', 'missing-stats'] as const
// Shared with the DuplicatesView so a regroup invalidates the same cache
// the grid renders from. Imported via the canonical hook key.
import { DUPLICATE_GROUPS_QUERY_KEY } from '../../hooks/useDuplicateGroupsQuery'

interface SettingsDialogProps {
  isOpen: boolean
  onClose: () => void
}

/**
 * Catch-all "settings + admin" panel. Currently exposes the maintenance
 * endpoints exposed by /api/v1/library/maintenance/* — regenerate
 * thumbnails (with filters), run the data-integrity cleanup, and trigger
 * a full library re-scan. The thumbnail stats block is the entry point
 * users will look at to understand what's going on after a scan.
 *
 * Each action is gated by an in-flight flag so double-clicks don't
 * stack background jobs, and the stats block re-fetches whenever the
 * dialog opens or after any action completes.
 */
export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
  const queryClient = useQueryClient()
  const [showAllErrors, setShowAllErrors] = useState(false)
  // One key per action so each button has its own spinner without
  // blocking the others.
  const [busy, setBusy] = useState<Record<string, boolean>>({})

  // All four panels fetch through React Query so cached data shows
  // instantly on reopen while a background refetch updates the numbers.
  // `enabled: isOpen` avoids firing requests while the dialog is closed,
  // but the cache entries survive between opens (default gcTime = 5m).
  const thumbStatsQuery = useQuery({
    queryKey: SETTINGS_THUMB_STATS_KEY,
    queryFn: library.maintenance.thumbnailStats,
    enabled: isOpen,
    // Treat as stale immediately so reopening the dialog triggers a
    // background refetch on top of the cached view.
    staleTime: 0,
  })
  const libStatsQuery = useQuery({
    queryKey: SETTINGS_LIB_STATS_KEY,
    queryFn: library.stats,
    enabled: isOpen,
    staleTime: 0,
  })
  // Worker status polls every 5s while the dialog is open — `refetchInterval`
  // replaces the old setInterval loop. Missing-stats is relatively cheap
  // but shares the same 5s rhythm to keep the orphan banner live.
  const workerStatusQuery = useQuery({
    queryKey: SETTINGS_WORKER_STATUS_KEY,
    queryFn: library.maintenance.workerStatus,
    enabled: isOpen,
    refetchInterval: isOpen ? 5000 : false,
    staleTime: 0,
  })
  const missingStatsQuery = useQuery({
    queryKey: SETTINGS_MISSING_STATS_KEY,
    queryFn: library.maintenance.missingStats,
    enabled: isOpen,
    refetchInterval: isOpen ? 5000 : false,
    staleTime: 0,
  })
  // Duplicates: shares the cache with DuplicatesView so a regroup
  // triggered from Settings updates the grid view immediately.
  const duplicatesQuery = useQuery({
    queryKey: DUPLICATE_GROUPS_QUERY_KEY,
    queryFn: library.duplicates.groups,
    enabled: isOpen,
    staleTime: 0,
  })

  const thumbStats = thumbStatsQuery.data
  const libStats = libStatsQuery.data
  const workerStatus = workerStatusQuery.data
  const missingStats = missingStatsQuery.data
  // "loading" in the UI sense = fetching AND no cached data yet. Background
  // refetches on top of cached data shouldn't flip the refresh spinners.
  const loadingStats =
    (thumbStatsQuery.isFetching && !thumbStatsQuery.data) ||
    (libStatsQuery.isFetching && !libStatsQuery.data)
  const loadingWorkers =
    (workerStatusQuery.isFetching && !workerStatusQuery.data) ||
    (missingStatsQuery.isFetching && !missingStatsQuery.data)

  const refreshStats = useCallback(() => {
    queryClient.invalidateQueries({ queryKey: SETTINGS_THUMB_STATS_KEY })
    queryClient.invalidateQueries({ queryKey: SETTINGS_LIB_STATS_KEY })
    queryClient.invalidateQueries({ queryKey: DUPLICATE_GROUPS_QUERY_KEY })
  }, [queryClient])
  const refreshWorkers = useCallback(() => {
    queryClient.invalidateQueries({ queryKey: SETTINGS_WORKER_STATUS_KEY })
    queryClient.invalidateQueries({ queryKey: SETTINGS_MISSING_STATS_KEY })
  }, [queryClient])

  // Surface fetch errors once (React Query de-dupes retries but we still
  // want a single toast so the user knows something went wrong).
  useEffect(() => {
    if (!isOpen) return
    if (thumbStatsQuery.error || libStatsQuery.error) {
      console.error('Failed to load settings stats', thumbStatsQuery.error ?? libStatsQuery.error)
      toast.error('Could not load library stats')
    }
  }, [isOpen, thumbStatsQuery.error, libStatsQuery.error])
  useEffect(() => {
    if (!isOpen) return
    if (workerStatusQuery.error || missingStatsQuery.error) {
      console.error('Failed to load worker status', workerStatusQuery.error ?? missingStatsQuery.error)
      toast.error('Could not load worker status')
    }
  }, [isOpen, workerStatusQuery.error, missingStatsQuery.error])

  // Esc closes.
  useEffect(() => {
    if (!isOpen) return
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose()
    }
    window.addEventListener('keydown', handler)
    return () => window.removeEventListener('keydown', handler)
  }, [isOpen, onClose])

  const runAction = useCallback(
    async <T,>(
      key: string,
      fn: () => Promise<T>,
      successTitle: string,
      describe?: (result: T) => string | undefined
    ) => {
      if (busy[key]) return
      setBusy((b) => ({ ...b, [key]: true }))
      try {
        const result = await fn()
        toast.success(successTitle, describe?.(result))
        refreshStats()
        refreshWorkers()
      } catch (e: unknown) {
        const message = e instanceof Error ? e.message : String(e)
        toast.error(`${successTitle} failed`, message)
      } finally {
        setBusy((b) => ({ ...b, [key]: false }))
      }
    },
    [busy, refreshStats, refreshWorkers]
  )

  const regenerate = useCallback(
    (
      key: string,
      body: {
        media_types?: MediaType[]
        only_failed?: boolean
        only_pending?: boolean
      }
    ) =>
      runAction(
        key,
        () => library.maintenance.regenerateThumbnails(body),
        'Regeneration queued',
        (r) => `${r.queued} photos queued, ${r.cleared_dirs} thumb dirs cleared`
      ),
    [runAction]
  )

  if (!isOpen) return null

  return (
    <div className="fixed inset-0 z-50">
      <div
        className="absolute inset-0 bg-black/60 backdrop-blur-sm"
        onClick={onClose}
      />
      <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
        <div className="relative z-10 flex max-h-[85vh] w-[640px] flex-col rounded-lg border border-border bg-surface shadow-2xl">
          {/* Header */}
          <div className="flex items-center justify-between border-b border-border px-5 py-3">
            <h2 className="text-base font-semibold text-text">Settings</h2>
            <button
              onClick={onClose}
              className="rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
              title="Close (Esc)"
            >
              <X className="h-4 w-4" />
            </button>
          </div>

          <div className="flex-1 overflow-y-auto p-5">
            {/* ----------------------------------------------------- */}
            {/* Library overview                                       */}
            {/* ----------------------------------------------------- */}
            <Section
              icon={<Database className="h-4 w-4" />}
              title="Library"
              right={
                <button
                  onClick={refreshStats}
                  disabled={loadingStats}
                  className="flex items-center gap-1 rounded border border-border px-2 py-1 text-xs text-text-muted hover:bg-surface-2 disabled:opacity-50"
                >
                  {loadingStats ? (
                    <Loader2 className="h-3 w-3 animate-spin" />
                  ) : (
                    <RefreshCw className="h-3 w-3" />
                  )}
                  Refresh
                </button>
              }
            >
              <div className="grid grid-cols-3 gap-2 text-xs">
                <Stat label="Photos" value={libStats?.total_photos} />
                <Stat label="Videos" value={libStats?.total_videos} />
                <Stat
                  label="On disk"
                  value={
                    libStats ? `${libStats.total_size_gb.toFixed(1)} GB` : undefined
                  }
                />
              </div>
              <div className="mt-3">
                <ActionButton
                  loading={busy.scan}
                  onClick={() =>
                    runAction(
                      'scan',
                      () => library.scan(),
                      'Library scan started'
                    )
                  }
                >
                  <RefreshCw className="h-4 w-4" />
                  Re-scan source folders
                </ActionButton>
              </div>
            </Section>

            {/* ----------------------------------------------------- */}
            {/* Duplicate detection                                    */}
            {/* ----------------------------------------------------- */}
            <Section
              icon={<Copy className="h-4 w-4" />}
              title="Duplicates"
            >
              <div className="grid grid-cols-2 gap-2 text-xs">
                <Stat
                  label="Groups"
                  value={duplicatesQuery.data?.total_groups}
                />
                <Stat
                  label="Members"
                  value={duplicatesQuery.data?.total_members}
                />
              </div>
              <p className="mt-3 text-xs text-text-muted">
                Duplicates are detected by perceptual hash (pHash), which catches
                visually-identical photos even when their bytes differ — re-encoded
                JPEGs, screenshots, resized exports. Backfill computes hashes for
                photos that existed before pHash was added; Re-detect re-runs
                clustering across the whole library.
              </p>
              <div className="mt-2 flex flex-wrap gap-2">
                <ActionButton
                  loading={busy['backfill-phashes']}
                  onClick={() =>
                    runAction(
                      'backfill-phashes',
                      () => library.maintenance.backfillPhashes(),
                      'pHash backfill queued'
                    )
                  }
                >
                  <Sparkles className="h-4 w-4" />
                  Backfill perceptual hashes
                </ActionButton>
                <ActionButton
                  loading={busy['regroup-duplicates']}
                  onClick={() =>
                    runAction(
                      'regroup-duplicates',
                      () => library.maintenance.regroupDuplicates(),
                      'Duplicate detection queued'
                    )
                  }
                >
                  <Copy className="h-4 w-4" />
                  Re-detect duplicates
                </ActionButton>
              </div>
            </Section>

            {/* ----------------------------------------------------- */}
            {/* Thumbnail maintenance                                  */}
            {/* ----------------------------------------------------- */}
            <Section
              icon={<ImageIcon className="h-4 w-4" />}
              title="Thumbnails"
            >
              <div className="grid grid-cols-4 gap-2 text-xs">
                <Stat
                  label="Completed"
                  value={thumbStats?.completed}
                  tone="ok"
                />
                <Stat
                  label="Pending"
                  value={thumbStats?.pending}
                  tone="muted"
                />
                <Stat
                  label="Processing"
                  value={thumbStats?.processing}
                  tone="muted"
                />
                <Stat
                  label="Failed"
                  value={thumbStats?.failed}
                  tone={thumbStats && thumbStats.failed > 0 ? 'warn' : 'muted'}
                />
              </div>

              <p className="mt-3 text-xs text-text-muted">
                Reset on-disk thumbnails and re-queue generation. Use after
                upgrading the worker or to fix the gray placeholders left
                behind by an earlier failure.
              </p>

              <div className="mt-2 flex flex-wrap gap-2">
                <ActionButton
                  loading={busy['regen-videos']}
                  onClick={() =>
                    regenerate('regen-videos', { media_types: ['video'] })
                  }
                >
                  <Film className="h-4 w-4" />
                  Regenerate video thumbnails
                </ActionButton>

                <ActionButton
                  loading={busy['regen-failed']}
                  onClick={() =>
                    regenerate('regen-failed', { only_failed: true })
                  }
                  disabled={!!thumbStats && thumbStats.failed === 0}
                >
                  <AlertTriangle className="h-4 w-4" />
                  Retry failed
                  {thumbStats ? ` (${thumbStats.failed})` : ''}
                </ActionButton>

                <ActionButton
                  loading={busy['regen-pending']}
                  onClick={() =>
                    regenerate('regen-pending', { only_pending: true })
                  }
                  disabled={!!thumbStats && thumbStats.pending === 0}
                >
                  <RefreshCw className="h-4 w-4" />
                  Kick pending
                  {thumbStats ? ` (${thumbStats.pending})` : ''}
                </ActionButton>

                <ActionButton
                  loading={busy['regen-all']}
                  destructive
                  onClick={() => {
                    if (
                      !confirm(
                        'Regenerate thumbnails for the entire library? ' +
                          'This will queue every photo and may take a while.'
                      )
                    )
                      return
                    regenerate('regen-all', {})
                  }}
                >
                  <RefreshCw className="h-4 w-4" />
                  Regenerate all
                </ActionButton>
              </div>
            </Section>

            {/* ----------------------------------------------------- */}
            {/* Worker fleet diagnostics                               */}
            {/* ----------------------------------------------------- */}
            <Section
              icon={<Cpu className="h-4 w-4" />}
              title="Workers"
              right={
                <button
                  onClick={refreshWorkers}
                  disabled={loadingWorkers}
                  className="flex items-center gap-1 rounded border border-border px-2 py-1 text-xs text-text-muted hover:bg-surface-2 disabled:opacity-50"
                >
                  {loadingWorkers ? (
                    <Loader2 className="h-3 w-3 animate-spin" />
                  ) : (
                    <RefreshCw className="h-3 w-3" />
                  )}
                  Refresh
                </button>
              }
            >
              {/* Top-line health */}
              <div className="grid grid-cols-3 gap-2 text-xs">
                <Stat
                  label="Workers"
                  value={workerStatus?.worker_count}
                  tone={
                    workerStatus
                      ? workerStatus.worker_count > 0
                        ? 'ok'
                        : 'warn'
                      : 'muted'
                  }
                />
                <Stat
                  label="Broker"
                  value={
                    workerStatus
                      ? workerStatus.broker_ok
                        ? 'OK'
                        : 'DOWN'
                      : undefined
                  }
                  tone={
                    workerStatus
                      ? workerStatus.broker_ok
                        ? 'ok'
                        : 'warn'
                      : 'muted'
                  }
                />
                <Stat
                  label="Failed tasks"
                  value={workerStatus?.failures.total}
                  tone={
                    workerStatus && workerStatus.failures.total > 0
                      ? 'warn'
                      : 'muted'
                  }
                />
              </div>

              {/* Inline error banners for the obvious failure modes */}
              {workerStatus?.broker_error && (
                <ErrorBanner
                  title="Cannot reach Redis broker"
                  detail={workerStatus.broker_error}
                />
              )}
              {workerStatus?.inspect_error && (
                <ErrorBanner
                  title="Celery inspect failed"
                  detail={workerStatus.inspect_error}
                />
              )}
              {workerStatus &&
                workerStatus.broker_ok &&
                workerStatus.worker_count === 0 && (
                  <ErrorBanner
                    title="No workers responding"
                    detail="Broker is reachable but no celery worker pinged back. Check the mulita-worker container logs."
                  />
                )}

              {/* Queue depth */}
              {workerStatus && (
                <div className="mt-3">
                  <div className="mb-1 text-[10px] uppercase tracking-wide text-text-muted">
                    Queue depth
                  </div>
                  <div className="grid grid-cols-3 gap-2 text-xs">
                    {Object.entries(workerStatus.queues).map(([name, depth]) => (
                      <div
                        key={name}
                        className="flex items-center justify-between rounded bg-surface px-2 py-1"
                      >
                        <span className="text-text-muted">{name}</span>
                        <span
                          className={clsx(
                            'font-mono font-semibold',
                            depth > 0 ? 'text-text' : 'text-text-muted'
                          )}
                        >
                          {depth}
                        </span>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {/* Orphaned rows (files gone from disk) */}
              {missingStats &&
                ((missingStats.would_delete ?? 0) > 0 ||
                  (missingStats.would_delete_folders ?? 0) > 0) && (
                  <div className="mt-3 rounded border border-star/40 bg-star/10 p-2">
                    <div className="flex items-center justify-between gap-2">
                      <div className="text-xs">
                        <div className="flex items-center gap-1.5 font-medium text-text">
                          <AlertTriangle className="h-3.5 w-3.5 text-star" />
                          Orphaned rows
                        </div>
                        <div className="mt-0.5 text-[10px] text-text-muted">
                          {missingStats.would_delete ?? 0} photos and{' '}
                          {missingStats.would_delete_folders ?? 0} folders
                          point at paths that no longer exist on disk under a
                          mounted source root. Usually means PHOTO_DIRS was
                          repointed at a different library.
                          {missingStats.skipped_unmounted > 0 && (
                            <>
                              {' '}
                              {missingStats.skipped_unmounted} more rows are
                              under unmounted roots and will not be touched.
                            </>
                          )}
                        </div>
                      </div>
                      <ActionButton
                        loading={busy['prune-missing']}
                        destructive
                        onClick={() => {
                          const photos = missingStats.would_delete ?? 0
                          const folders = missingStats.would_delete_folders ?? 0
                          if (
                            !confirm(
                              `Delete ${photos} photo rows and ${folders} folder rows whose paths are missing? ` +
                                'This cannot be undone.'
                            )
                          )
                            return
                          runAction(
                            'prune-missing',
                            () => library.maintenance.pruneMissing(),
                            'Orphans pruned',
                            (r) =>
                              `${r.deleted ?? 0} photos + ${r.deleted_folders ?? 0} folders deleted`
                          )
                        }}
                      >
                        <AlertTriangle className="h-4 w-4" />
                        Prune
                      </ActionButton>
                    </div>
                  </div>
                )}

              {/* Per-worker breakdown */}
              {workerStatus && workerStatus.workers.length > 0 && (
                <div className="mt-3 space-y-2">
                  <div className="text-[10px] uppercase tracking-wide text-text-muted">
                    Worker fleet
                  </div>
                  {workerStatus.workers.map((w) => (
                    <div
                      key={w.name}
                      className="rounded border border-border bg-surface p-2 text-xs"
                    >
                      <div className="flex items-center justify-between">
                        <div className="flex items-center gap-2">
                          {w.status === 'online' ? (
                            <CheckCircle2 className="h-3.5 w-3.5 text-pick" />
                          ) : (
                            <AlertCircle className="h-3.5 w-3.5 text-reject" />
                          )}
                          <span className="font-mono text-text">{w.name}</span>
                        </div>
                        <span className="text-text-muted">
                          {w.active}/{w.concurrency ?? '?'} active
                        </span>
                      </div>
                      <div className="mt-1 flex flex-wrap gap-x-3 gap-y-0.5 text-[10px] text-text-muted">
                        <span>reserved: {w.reserved}</span>
                        <span>scheduled: {w.scheduled}</span>
                        {w.queues.length > 0 && (
                          <span>queues: {w.queues.join(', ')}</span>
                        )}
                      </div>
                      {w.active_tasks.length > 0 && (
                        <div className="mt-1.5 space-y-0.5 border-t border-border pt-1.5">
                          {w.active_tasks.map((t) => (
                            <div
                              key={t.id}
                              className="truncate font-mono text-[10px] text-text-muted"
                              title={`${t.name} ${JSON.stringify(t.args)}`}
                            >
                              <span className="text-text">{t.name}</span>{' '}
                              {Array.isArray(t.args)
                                ? t.args.map((a) => String(a)).join(', ')
                                : ''}
                            </div>
                          ))}
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              )}

              {/* Recent task failures */}
              {workerStatus && workerStatus.failures.recent.length > 0 && (
                <div className="mt-3">
                  <div className="mb-1 flex items-center justify-between">
                    <div className="text-[10px] uppercase tracking-wide text-text-muted">
                      Recent failures ({workerStatus.failures.total})
                    </div>
                    {workerStatus.failures.recent.length > 5 && (
                      <button
                        onClick={() => setShowAllErrors((v) => !v)}
                        className="text-[10px] text-text-muted hover:text-text"
                      >
                        {showAllErrors ? 'Show less' : 'Show all'}
                      </button>
                    )}
                  </div>
                  <div className="max-h-48 space-y-1 overflow-y-auto rounded border border-border bg-surface p-2">
                    {(showAllErrors
                      ? workerStatus.failures.recent
                      : workerStatus.failures.recent.slice(0, 5)
                    ).map((f) => (
                      <div
                        key={f.photo_id}
                        className="border-b border-border/40 pb-1 last:border-b-0 last:pb-0"
                      >
                        <div className="flex items-center justify-between gap-2 text-[11px]">
                          <span className="truncate font-mono text-text">
                            {f.filename}
                          </span>
                          <span className="shrink-0 text-[10px] text-text-muted">
                            {f.media_type}
                          </span>
                        </div>
                        <div className="break-all font-mono text-[10px] text-reject">
                          {f.error || '(no error message)'}
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {/* Recent scan errors (Redis list) */}
              {workerStatus && workerStatus.scan_errors.length > 0 && (
                <div className="mt-3">
                  <div className="mb-1 text-[10px] uppercase tracking-wide text-text-muted">
                    Scan errors
                  </div>
                  <div className="max-h-32 space-y-0.5 overflow-y-auto rounded border border-border bg-surface p-2 font-mono text-[10px] text-reject">
                    {workerStatus.scan_errors.map((e, i) => (
                      <div key={i} className="break-all">
                        {e}
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {!workerStatus && (
                <div className="mt-3 flex items-center gap-2 text-xs text-text-muted">
                  <Loader2 className="h-3 w-3 animate-spin" />
                  Loading worker status…
                </div>
              )}
            </Section>

            {/* ----------------------------------------------------- */}
            {/* Data integrity                                         */}
            {/* ----------------------------------------------------- */}
            <Section
              icon={<Wrench className="h-4 w-4" />}
              title="Maintenance"
            >
              <p className="text-xs text-text-muted">
                Re-runs the source-roots / folders / photos integrity
                cleanup that normally only runs on backend startup. Safe
                to run any time.
              </p>
              <div className="mt-2">
                <ActionButton
                  loading={busy.cleanup}
                  onClick={() =>
                    runAction(
                      'cleanup',
                      () => library.maintenance.cleanup(),
                      'Data integrity cleanup complete'
                    )
                  }
                >
                  <Wrench className="h-4 w-4" />
                  Run data integrity cleanup
                </ActionButton>
              </div>
            </Section>
          </div>
        </div>
      </div>
    </div>
  )
}

// ---------------------------------------------------------------------------
// Local presentational helpers — kept private to this file.
// ---------------------------------------------------------------------------

function Section({
  icon,
  title,
  right,
  children,
}: {
  icon: React.ReactNode
  title: string
  right?: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <section className="mb-5 last:mb-0">
      <div className="mb-2 flex items-center justify-between">
        <h3 className="flex items-center gap-2 text-sm font-medium text-text">
          {icon}
          {title}
        </h3>
        {right}
      </div>
      <div className="rounded border border-border bg-bg p-3">{children}</div>
    </section>
  )
}

function Stat({
  label,
  value,
  tone = 'muted',
}: {
  label: string
  value: number | string | undefined
  tone?: 'ok' | 'warn' | 'muted'
}) {
  const toneClass =
    tone === 'ok'
      ? 'text-pick'
      : tone === 'warn'
        ? 'text-reject'
        : 'text-text'
  return (
    <div className="rounded bg-surface p-2">
      <div className="text-[10px] uppercase tracking-wide text-text-muted">
        {label}
      </div>
      <div className={clsx('text-base font-semibold', toneClass)}>
        {value ?? '—'}
      </div>
    </div>
  )
}

function ErrorBanner({ title, detail }: { title: string; detail: string }) {
  return (
    <div className="mt-3 rounded border border-reject/40 bg-reject/10 p-2 text-xs">
      <div className="flex items-center gap-1.5 font-medium text-reject">
        <AlertTriangle className="h-3.5 w-3.5" />
        {title}
      </div>
      <div className="mt-0.5 break-all font-mono text-[10px] text-reject/80">
        {detail}
      </div>
    </div>
  )
}

function ActionButton({
  loading,
  disabled,
  destructive,
  onClick,
  children,
}: {
  loading?: boolean
  disabled?: boolean
  destructive?: boolean
  onClick: () => void
  children: React.ReactNode
}) {
  return (
    <button
      onClick={onClick}
      disabled={disabled || loading}
      className={clsx(
        'flex items-center gap-2 rounded border px-3 py-1.5 text-xs font-medium transition-colors',
        destructive
          ? 'border-reject/40 text-reject hover:bg-reject/10'
          : 'border-border text-text hover:bg-surface-2',
        (disabled || loading) && 'cursor-not-allowed opacity-50'
      )}
    >
      {loading && <Loader2 className="h-4 w-4 animate-spin" />}
      {!loading && children}
      {loading && (
        // Re-render only the textual children when loading by stripping
        // the icon (children[0]) — we keep just the label so the spinner
        // takes the icon slot.
        Array.isArray(children) ? children.slice(1) : children
      )}
    </button>
  )
}
