import { useEffect, useState, useCallback, useRef } from 'react'
import {
  RefreshCw,
  Wrench,
  Film,
  Image as ImageIcon,
  AlertTriangle,
  Database,
  Loader2,
  Cpu,
  AlertCircle,
  CheckCircle2,
  Copy,
  Sparkles,
  Activity,
  FolderSearch,
  Shield,
  Brain,
  RotateCcw,
} from 'lucide-react'
import clsx from 'clsx'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
  library,
  admin as adminApi,
  type MediaType,
  type PipelineStage,
  type ScanStatus,
  type WorkerStatus,
  type FeatureFlagSnapshot,
} from '../../services/api'
import { toast } from '../ToastContainer'
import { useAuth } from '../../contexts/AuthContext'
import { UserManagement } from '../admin/UserManagement'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'

// 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
const SETTINGS_PIPELINE_STATS_KEY = ['settings', 'pipeline-stats'] as const
const SETTINGS_SCAN_STATUS_KEY = ['settings', 'scan-status'] 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'

type SettingsTab = 'library' | 'ai' | 'users'

const TABS: { id: SettingsTab; label: string; adminOnly?: boolean }[] = [
  { id: 'library', label: 'Library Management' },
  { id: 'ai', label: 'AI Features', adminOnly: true },
  { id: 'users', label: 'Users', adminOnly: true },
]

const SETTINGS_FEATURE_FLAGS_KEY = ['settings', 'feature-flags'] as const

/**
 * Full-page settings view with tabbed navigation. Replaces the old
 * modal dialog — renders as a top-level section in the main content
 * area (like Timeline or MapView).
 */
export function SettingsPage() {
  const { isAdmin } = useAuth()
  const queryClient = useQueryClient()
  const [activeTab, setActiveTab] = useState<SettingsTab>('library')
  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.
  // All queries use scope=global so the admin sees cross-user totals.
  const thumbStatsQuery = useQuery({
    queryKey: SETTINGS_THUMB_STATS_KEY,
    queryFn: () => library.maintenance.thumbnailStats('global'),
    staleTime: 0,
  })
  const libStatsQuery = useQuery({
    queryKey: SETTINGS_LIB_STATS_KEY,
    queryFn: () => library.stats('global'),
    staleTime: 0,
  })
  const workerStatusQuery = useQuery({
    queryKey: SETTINGS_WORKER_STATUS_KEY,
    queryFn: () => library.maintenance.workerStatus('global'),
    refetchInterval: 5000,
    staleTime: 0,
  })
  const missingStatsQuery = useQuery({
    queryKey: SETTINGS_MISSING_STATS_KEY,
    queryFn: library.maintenance.missingStats,
    refetchInterval: 5000,
    staleTime: 0,
  })
  const pipelineStatsQuery = useQuery({
    queryKey: SETTINGS_PIPELINE_STATS_KEY,
    queryFn: () => library.maintenance.pipelineStats('global'),
    refetchInterval: 5000,
    staleTime: 0,
  })
  const scanStatusQuery = useQuery<ScanStatus>({
    queryKey: SETTINGS_SCAN_STATUS_KEY,
    queryFn: library.scanStatus,
    refetchInterval: (q) =>
      (q.state.data as ScanStatus | undefined)?.is_scanning ? 2000 : 15000,
    staleTime: 0,
  })
  const duplicatesQuery = useQuery({
    queryKey: [...DUPLICATE_GROUPS_QUERY_KEY, 'global'],
    queryFn: () => library.duplicates.groups('global'),
    staleTime: 0,
  })

  const thumbStats = thumbStatsQuery.data
  const libStats = libStatsQuery.data
  const workerStatus = workerStatusQuery.data
  const missingStats = missingStatsQuery.data
  const pipelineStats = pipelineStatsQuery.data
  const scanStatus = scanStatusQuery.data

  // Throughput — tasks/minute across the fleet, derived by diffing the
  // `total` counters on `workers[*].processed` between successive polls.
  // We keep the previous sample in a ref so recomputation happens inside
  // the existing 5s polling rhythm without introducing extra state.
  // First sample returns null (we need two points for a rate).
  const throughputSampleRef = useRef<{
    totals: Record<string, number>
    at: number
  } | null>(null)
  const throughput = computeThroughput(workerStatus, throughputSampleRef)
  // "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: SETTINGS_PIPELINE_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.invalidateQueries({ queryKey: SETTINGS_SCAN_STATUS_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 (thumbStatsQuery.error || libStatsQuery.error) {
      console.error('Failed to load settings stats', thumbStatsQuery.error ?? libStatsQuery.error)
      toast.error('Could not load library stats')
    }
  }, [thumbStatsQuery.error, libStatsQuery.error])
  useEffect(() => {
    if (workerStatusQuery.error || missingStatsQuery.error) {
      console.error('Failed to load worker status', workerStatusQuery.error ?? missingStatsQuery.error)
      toast.error('Could not load worker status')
    }
  }, [workerStatusQuery.error, missingStatsQuery.error])

  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, 'global'),
        'Regeneration queued',
        (r) => `${r.queued} photos queued, ${r.cleared_dirs} thumb dirs cleared`
      ),
    [runAction]
  )

  const visibleTabs = TABS.filter((t) => !t.adminOnly || isAdmin)

  return (
    <Tabs
      value={activeTab}
      onValueChange={(v) => setActiveTab(v as SettingsTab)}
      className="flex h-full flex-col"
    >
      {/* Tab bar */}
      <div className="flex items-center border-b border-border bg-surface px-4 py-1.5">
        <TabsList className="h-7 bg-transparent">
          {visibleTabs.map((tab) => (
            <TabsTrigger
              key={tab.id}
              value={tab.id}
              className="h-6 px-3 text-xs data-[state=active]:bg-primary/20 data-[state=active]:text-primary"
            >
              {tab.label}
            </TabsTrigger>
          ))}
        </TabsList>
      </div>

      {/* Tab content */}
      <div className="flex-1 overflow-y-auto p-5">
        <div className="mx-auto max-w-2xl">

        {activeTab === 'library' && (<>
            <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>

              {(libStats?.source_dirs?.length ?? 0) > 0 && (
                <div className="mt-3 text-xs text-text-muted">
                  <span className="font-medium text-text">Source folders</span>
                  <div className="mt-1 space-y-0.5">
                    {libStats!.source_dirs.map((dir: string) => (
                      <div key={dir} className="truncate font-mono" title={dir}>{dir}</div>
                    ))}
                  </div>
                </div>
              )}

              {/* Inline scan progress. Rendered as a full-width sub-card
                  when a scan is active so the user sees the same info
                  they'd get from the floating widget without leaving
                  settings. Hidden when idle to keep the section tight. */}
              {scanStatus?.is_scanning && (
                <div className="mt-3 rounded border border-border bg-surface p-2">
                  <div className="mb-1 flex items-center gap-1.5 text-[10px] uppercase tracking-wide text-text-muted">
                    <FolderSearch className="h-3 w-3 animate-pulse" />
                    Scanning
                  </div>
                  <div className="mb-1 flex items-center justify-between text-xs">
                    <span className="truncate font-mono text-text-muted" title={scanStatus.current_folder ?? ''}>
                      {scanStatus.current_folder ?? '—'}
                    </span>
                    <span className="ml-2 shrink-0 font-mono text-text">
                      {scanStatus.processed_files.toLocaleString()} /{' '}
                      {scanStatus.total_files.toLocaleString()}
                    </span>
                  </div>
                  <ProgressBar
                    done={scanStatus.processed_files}
                    total={scanStatus.total_files || 1}
                  />
                </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>

            <Section
              icon={<Activity className="h-4 w-4" />}
              title="Pipeline progress"
              right={
                <button
                  onClick={() =>
                    queryClient.invalidateQueries({
                      queryKey: SETTINGS_PIPELINE_STATS_KEY,
                    })
                  }
                  disabled={pipelineStatsQuery.isFetching && !pipelineStats}
                  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"
                >
                  {pipelineStatsQuery.isFetching && !pipelineStats ? (
                    <Loader2 className="h-3 w-3 animate-spin" />
                  ) : (
                    <RefreshCw className="h-3 w-3" />
                  )}
                  Refresh
                </button>
              }
            >
              {pipelineStats ? (
                <div className="space-y-2">
                  {pipelineStats.stages.map((stage) => (
                    <PipelineRow key={stage.key} stage={stage} />
                  ))}
                  <p className="pt-1 text-[10px] text-text-muted">
                    Partial stages (marked *) only apply to a subset of
                    photos — e.g. not every photo has GPS, text, or detectable
                    objects. Face clusters and duplicate groups are output
                    counts rather than ratios.
                  </p>
                </div>
              ) : (
                <div className="flex items-center gap-2 text-xs text-text-muted">
                  <Loader2 className="h-3 w-3 animate-spin" />
                  Loading pipeline progress…
                </div>
              )}
            </Section>

            <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>


            <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>

            <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-4 gap-2 text-xs">
                <Stat
                  label="Workers"
                  value={workerStatus?.worker_count}
                  tone={
                    workerStatus
                      ? workerStatus.worker_count > 0
                        ? 'ok'
                        : 'warn'
                      : 'muted'
                  }
                />
                <Stat
                  label="Concurrency"
                  value={
                    workerStatus && workerStatus.workers.length > 0
                      ? workerStatus.workers.reduce(
                          (acc, w) => acc + (w.concurrency ?? 0),
                          0
                        )
                      : undefined
                  }
                />
                <Stat
                  label="Tasks/min"
                  value={
                    throughput === null
                      ? '…'
                      : throughput.toFixed(0)
                  }
                  tone={
                    throughput !== null && throughput > 0 ? 'ok' : 'muted'
                  }
                />
                <Stat
                  label="Failed tasks"
                  value={workerStatus?.failures.total}
                  tone={
                    workerStatus && workerStatus.failures.total > 0
                      ? 'warn'
                      : 'muted'
                  }
                />
              </div>

              {/* How to scale — explicit, because this question comes up
                  every time a large import is running and the user wants
                  it to go faster. The ingestion pipeline runs on two
                  worker services; each has its own concurrency env var. */}
              <p className="mt-3 text-[10px] leading-relaxed text-text-muted">
                Two worker services share the load:{' '}
                <code className="rounded bg-surface px-1">worker-light</code>{' '}
                (scan, thumbnails, EXIF — set by{' '}
                <code className="rounded bg-surface px-1">
                  CELERY_LIGHT_CONCURRENCY
                </code>
                ) and{' '}
                <code className="rounded bg-surface px-1">worker-vision</code>{' '}
                (embed, detect, OCR, faces — set by{' '}
                <code className="rounded bg-surface px-1">
                  CELERY_VISION_CONCURRENCY
                </code>
                ) in{' '}
                <code className="rounded bg-surface px-1">.env</code>. To
                scale, bump those values then run{' '}
                <code className="rounded bg-surface px-1">
                  docker compose up -d worker-light worker-vision
                </code>
                . Keep vision below your physical core count — each fork
                loads ~2 GB of ONNX models. The vision queue is usually the
                bottleneck; watch its depth below.
              </p>

              {/* 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. `vision` is called out with a highlight
                  because it's where the serious backlog lives — every
                  embed / tag / OCR / face task routes here. */}
              {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-4 gap-2 text-xs">
                    {Object.entries(workerStatus.queues).map(([name, depth]) => (
                      <div
                        key={name}
                        className={clsx(
                          'flex items-center justify-between rounded px-2 py-1',
                          name === 'vision' && depth > 0
                            ? 'bg-surface-2'
                            : 'bg-surface'
                        )}
                      >
                        <span className="text-text-muted">{name}</span>
                        <span
                          className={clsx(
                            'font-mono font-semibold',
                            depth > 0 ? 'text-text' : 'text-text-muted'
                          )}
                        >
                          {depth.toLocaleString()}
                        </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>

            <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>
        </>)}

        {activeTab === 'ai' && isAdmin && (
          <AiFeaturesTab
            busy={busy}
            runAction={runAction}
          />
        )}

        {activeTab === 'users' && isAdmin && (
            <Section
              icon={<Shield className="h-4 w-4" />}
              title="User Management"
            >
              <UserManagement />
            </Section>
        )}

        </div>
      </div>
    </Tabs>
  )
}

// ---------------------------------------------------------------------------
// 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>
  )
}

/**
 * Compute the total task-completion rate across the worker fleet by
 * diffing the per-task `processed` counters between successive poll
 * samples. Returns null on the first call (we need two samples for a
 * rate) and 0 when nothing has moved since the last poll.
 *
 * The sample is kept in a ref — not state — because we don't want to
 * re-render on every update; we want the number to settle into the
 * existing render cycle that React Query already drives.
 */
function computeThroughput(
  status: WorkerStatus | undefined,
  ref: React.MutableRefObject<{
    totals: Record<string, number>
    at: number
  } | null>
): number | null {
  if (!status || status.workers.length === 0) {
    return null
  }

  // Flatten processed counts across all workers into one dict keyed by
  // task name so worker restarts (which reset individual counters) are
  // absorbed by the total.
  const totals: Record<string, number> = {}
  for (const w of status.workers) {
    for (const [task, count] of Object.entries(w.processed ?? {})) {
      totals[task] = (totals[task] ?? 0) + (count ?? 0)
    }
  }

  const now = Date.now()
  const prev = ref.current
  // Update the ref BEFORE returning so the next call has a baseline.
  ref.current = { totals, at: now }

  if (!prev) {
    return null
  }

  const elapsedSec = (now - prev.at) / 1000
  if (elapsedSec <= 0) {
    return null
  }

  let delta = 0
  for (const [task, count] of Object.entries(totals)) {
    const before = prev.totals[task] ?? 0
    // Guard against counter resets (worker restart) — negative diffs
    // are clamped to zero rather than dragging the rate down.
    delta += Math.max(0, count - before)
  }

  return (delta / elapsedSec) * 60
}

function PipelineRow({ stage }: { stage: PipelineStage }) {
  const isStandalone = stage.standalone === true
  const isComplete = !isStandalone && stage.total > 0 && stage.done >= stage.total
  const isActive = !isStandalone && stage.done > 0 && stage.done < stage.total

  return (
    <div className="rounded bg-surface px-2 py-1.5" title={stage.hint}>
      <div className="flex items-center justify-between gap-2 text-xs">
        <div className="flex min-w-0 items-center gap-1.5">
          <span className="truncate text-text">
            {stage.label}
            {stage.partial && <span className="text-text-muted"> *</span>}
          </span>
        </div>
        <span
          className={clsx(
            'shrink-0 font-mono text-[11px]',
            isComplete
              ? 'text-pick'
              : isActive
                ? 'text-text'
                : 'text-text-muted'
          )}
        >
          {isStandalone
            ? stage.done.toLocaleString()
            : `${stage.done.toLocaleString()} / ${stage.total.toLocaleString()}`}
        </span>
      </div>
      {!isStandalone && (
        <div className="mt-1">
          <ProgressBar done={stage.done} total={stage.total || 1} />
        </div>
      )}
    </div>
  )
}

function ProgressBar({ done, total }: { done: number; total: number }) {
  const pct = total > 0 ? Math.min(100, (done / total) * 100) : 0
  const complete = total > 0 && done >= total
  return (
    <div className="h-1 w-full overflow-hidden rounded bg-border">
      <div
        className={clsx(
          'h-full rounded transition-[width] duration-500',
          complete ? 'bg-pick' : 'bg-text-muted'
        )}
        style={{ width: `${pct}%` }}
      />
    </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>
  )
}


// ---------------------------------------------------------------------------
// AI Features admin tab
// ---------------------------------------------------------------------------

interface AiFeaturesTabProps {
  busy: Record<string, boolean>
  runAction: <T>(
    key: string,
    fn: () => Promise<T>,
    successTitle: string,
    describe?: (result: T) => string | undefined,
  ) => Promise<void>
}

const FLAG_META: Array<{
  id: string
  label: string
  description: string
  icon: React.ReactNode
}> = [
  {
    id: 'vision.enabled',
    label: 'Vision classifier',
    description:
      'Binary photo-vs-other classifier. Flags screenshots, documents, memes and ' +
      'scans with "needs review" so they can be triaged.',
    icon: <Sparkles className="h-3.5 w-3.5" />,
  },
]

function AiFeaturesTab({ busy, runAction }: AiFeaturesTabProps) {
  const queryClient = useQueryClient()
  const flagsQuery = useQuery<{ flags: FeatureFlagSnapshot }>({
    queryKey: SETTINGS_FEATURE_FLAGS_KEY,
    queryFn: adminApi.listFeatureFlags,
    staleTime: 5_000,
  })

  const flags = flagsQuery.data?.flags ?? {}
  const masterOff = flags['vision.enabled'] && !flags['vision.enabled'].effective

  const applyFlag = async (name: string, value: boolean | null) => {
    await runAction(
      `flag:${name}`,
      () => adminApi.setFeatureFlag(name, value),
      value === null ? 'Override cleared' : `Feature ${value ? 'enabled' : 'disabled'}`,
    )
    queryClient.invalidateQueries({ queryKey: SETTINGS_FEATURE_FLAGS_KEY })
    // Non-admin feature map drives sidebar gating — invalidate so the
    // People / Tags entries appear / disappear immediately without a
    // page reload.
    queryClient.invalidateQueries({ queryKey: ['features'] })
  }

  const runBackfill = () =>
    runAction(
      `ai-backfill:all`,
      () => adminApi.triggerAiBackfill({}),
      'Classifier backfill queued',
      (r) => `Celery task ${r.task_id}`,
    )

  return (
    <>
      <Section icon={<Brain className="h-4 w-4" />} title="AI feature flags">
        <p className="text-xs text-text-muted">
          Toggle each stage at runtime. Changes are observed by Celery
          workers on the next task — no restart needed. "Default" means
          the flag hasn\'t been overridden and is tracking the YAML config;
          an overridden flag is pinned to the value shown until cleared.
        </p>

        {flagsQuery.isLoading && (
          <div className="mt-3 flex items-center gap-2 text-xs text-text-muted">
            <Loader2 className="h-3 w-3 animate-spin" />
            Loading feature flags…
          </div>
        )}
        {flagsQuery.error && (
          <ErrorBanner
            title="Could not load feature flags"
            detail={String((flagsQuery.error as Error).message || flagsQuery.error)}
          />
        )}

        {!flagsQuery.isLoading && !flagsQuery.error && (
          <div className="mt-3 space-y-2">
            {FLAG_META.map((meta) => {
              const state = flags[meta.id]
              if (!state) return null
              const busyKey = `flag:${meta.id}`
              const isBusy = !!busy[busyKey]
              const isMaster = meta.id === 'vision.enabled'
              const dimmed = !isMaster && masterOff
              return (
                <div
                  key={meta.id}
                  className={clsx(
                    'rounded border border-border bg-surface p-3 text-xs',
                    dimmed && 'opacity-60',
                  )}
                >
                  <div className="flex items-start justify-between gap-3">
                    <div className="min-w-0 flex-1">
                      <div className="flex items-center gap-1.5 text-text">
                        {meta.icon}
                        <span className="font-medium">{meta.label}</span>
                        {state.overridden && (
                          <span className="rounded bg-primary/20 px-1 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-primary">
                            overridden
                          </span>
                        )}
                      </div>
                      <p className="mt-1 text-[11px] text-text-muted">{meta.description}</p>
                      <p className="mt-1 text-[10px] text-text-faint">
                        Default: {state.default ? 'on' : 'off'} · Currently:{' '}
                        <span className={state.effective ? 'text-pick' : 'text-reject'}>
                          {state.effective ? 'on' : 'off'}
                        </span>
                      </p>
                    </div>
                    <div className="flex shrink-0 items-center gap-2">
                      <Switch
                        checked={state.effective}
                        onCheckedChange={(v) => applyFlag(meta.id, v)}
                        disabled={isBusy || (dimmed && !isMaster)}
                        title={state.effective ? 'Click to disable' : 'Click to enable'}
                      />
                      {state.overridden && (
                        <Button
                          variant="outline"
                          size="icon"
                          onClick={() => applyFlag(meta.id, null)}
                          disabled={isBusy}
                          className="h-6 w-6"
                          title="Reset to YAML default"
                          aria-label="Reset override"
                        >
                          <RotateCcw className="h-3 w-3" />
                        </Button>
                      )}
                    </div>
                  </div>

                </div>
              )
            })}
          </div>
        )}
      </Section>

      <Section icon={<Cpu className="h-4 w-4" />} title="Manual pipeline triggers">
        <p className="text-xs text-text-muted">
          Run the classifier over any photos that haven't been classified yet,
          or force a fresh filesystem scan. Both are safe to run repeatedly.
        </p>
        <div className="mt-3 flex flex-wrap gap-2">
          <ActionButton
            loading={!!busy['ai-backfill:all']}
            onClick={() => runBackfill()}
            disabled={masterOff}
          >
            <Sparkles className="h-4 w-4" />
            Run classifier backfill
          </ActionButton>
          <ActionButton
            loading={!!busy['rescan-full']}
            onClick={() =>
              runAction(
                'rescan-full',
                () => adminApi.triggerFullRescan(),
                'Rescan queued',
                (r) => `Celery task ${r.task_id}`,
              )
            }
          >
            <RefreshCw className="h-4 w-4" />
            Rescan all source roots
          </ActionButton>
        </div>
      </Section>
    </>
  )
}
