'use client';

import { useCallback, useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import {
  MultiSelect,
  type MultiSelectOption,
} from '@/components/ui/multi-select';
import {
  Search,
  Sparkles,
  CheckCircle2,
  Filter as FilterIcon,
  X,
  AlertTriangle,
  CircleDot,
  Clock,
} from 'lucide-react';
import { toast } from 'sonner';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';

type Period =
  | 'today'
  | 'yesterday'
  | 'this_week'
  | 'last_week'
  | 'last_30d'
  | 'last_60d'
  | 'custom'
  | 'all';

const PERIOD_OPTIONS: { value: Period; label: string }[] = [
  { value: 'today', label: 'Today' },
  { value: 'yesterday', label: 'Yesterday' },
  { value: 'this_week', label: 'This week' },
  { value: 'last_week', label: 'Last week' },
  { value: 'last_30d', label: '30d' },
  { value: 'last_60d', label: '60d' },
  { value: 'custom', label: 'Custom' },
  { value: 'all', label: 'All' },
];

type AnalyzedFilter = 'any' | 'yes' | 'no' | 'stale';

interface TicketRow {
  ticketNumber: string;
  autotaskTicketId: number;
  title: string | null;
  clientName: string | null;
  clientId: number | null;
  status: string | null;
  priority: string | null;
  queue: string | null;
  issueType: string | null;
  subIssueType: string | null;
  assignedResourceName: string | null;
  createdAtAutotask: string | null;
  lastActivityAtAutotask: string | null;
  ageInDays: number | null;
  analyzedState: 'none' | 'current' | 'stale';
  latestAnalysisId: string | null;
  latestAnalysisAt: string | null;
  needsHumanReview: boolean;
  confidenceScore: number | null;
  primaryCategory: string | null;
}

interface FilterOptions {
  companies: { id: string; name: string }[];
  issueTypes: { value: number; label: string }[];
  queues: { value: number; label: string }[];
  statuses: { value: number; label: string }[];
  priorities: { value: number; label: string }[];
  resources: { id: string; name: string }[];
}

const PAGE_SIZE = 50;
const SELECTION_LS_KEY = 'analyzer:ticket-selection:v1';

function formatRelative(iso: string | null, tz: string): string {
  if (!iso) return '—';
  const d = new Date(iso);
  const diffMs = Date.now() - d.getTime();
  const diffMin = Math.round(diffMs / 60000);
  if (diffMin < 1) return 'just now';
  if (diffMin < 60) return `${diffMin}m ago`;
  const diffHr = Math.round(diffMin / 60);
  if (diffHr < 24) return `${diffHr}h ago`;
  const diffDays = Math.round(diffHr / 24);
  if (diffDays < 7) return `${diffDays}d ago`;
  return d.toLocaleDateString(undefined, { timeZone: tz });
}

function formatAge(days: number | null): string {
  if (days === null) return '—';
  if (days < 1) return '<1d';
  if (days < 30) return `${days}d`;
  const months = Math.round(days / 30);
  return `${months}mo`;
}

function readSelection(): string[] {
  if (typeof window === 'undefined') return [];
  try {
    const raw = localStorage.getItem(SELECTION_LS_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed.filter((s) => typeof s === 'string') : [];
  } catch {
    return [];
  }
}

function writeSelection(ids: string[]) {
  if (typeof window === 'undefined') return;
  try {
    localStorage.setItem(SELECTION_LS_KEY, JSON.stringify(ids));
  } catch {
    /* quota / disabled — ignore */
  }
}

export default function AnalyzerBrowseTicketsPage() {
  const tz = useUserTimezone();
  const router = useRouter();

  const [period, setPeriod] = useState<Period>('last_30d');
  const [startDate, setStartDate] = useState('');
  const [endDate, setEndDate] = useState('');
  const [clientIds, setClientIds] = useState<string[]>([]);
  const [issueTypes, setIssueTypes] = useState<string[]>([]);
  const [queues, setQueues] = useState<string[]>([]);
  const [statuses, setStatuses] = useState<string[]>([]);
  const [priorities, setPriorities] = useState<string[]>([]);
  const [assignedTo, setAssignedTo] = useState<string[]>([]);
  const [analyzed, setAnalyzed] = useState<AnalyzedFilter>('any');
  const [needsReview, setNeedsReview] = useState(false);
  const [searchInput, setSearchInput] = useState('');
  const [search, setSearch] = useState('');
  const [page, setPage] = useState(0);
  const [sort, setSort] = useState<
    | 'last_activity_desc'
    | 'last_activity_asc'
    | 'created_desc'
    | 'created_asc'
    | 'priority'
  >('last_activity_desc');

  const [tickets, setTickets] = useState<TicketRow[]>([]);
  const [total, setTotal] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [filterOptions, setFilterOptions] = useState<FilterOptions | null>(null);

  // Bulk selection (session-scoped via localStorage)
  const [selected, setSelected] = useState<string[]>(() => readSelection());
  const selectedSet = useMemo(() => new Set(selected), [selected]);

  // Bulk-action busy state (Analyze N tickets sequential)
  const [bulkRunning, setBulkRunning] = useState(false);

  // Debounce search input
  useEffect(() => {
    const t = setTimeout(() => {
      setSearch(searchInput.trim());
      setPage(0);
    }, 300);
    return () => clearTimeout(t);
  }, [searchInput]);

  // Reset to page 0 on any filter change
  useEffect(() => {
    setPage(0);
  }, [
    period,
    startDate,
    endDate,
    clientIds,
    issueTypes,
    queues,
    statuses,
    priorities,
    assignedTo,
    analyzed,
    needsReview,
    sort,
  ]);

  // Load filter options once
  useEffect(() => {
    let cancelled = false;
    fetch('/api/analyzer/tickets/filter-options')
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((data: FilterOptions) => {
        if (!cancelled) setFilterOptions(data);
      })
      .catch(() => {
        if (!cancelled)
          setFilterOptions({
            companies: [],
            issueTypes: [],
            queues: [],
            statuses: [],
            priorities: [],
            resources: [],
          });
      });
    return () => {
      cancelled = true;
    };
  }, []);

  // Persist selection on change
  useEffect(() => {
    writeSelection(selected);
  }, [selected]);

  const fetchTickets = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const params = new URLSearchParams({
        period,
        sort,
        limit: String(PAGE_SIZE),
        offset: String(page * PAGE_SIZE),
      });
      if (period === 'custom' && startDate && endDate) {
        params.set('startDate', startDate);
        params.set('endDate', endDate);
      }
      if (clientIds.length) params.set('clientId', clientIds.join(','));
      if (issueTypes.length) params.set('issueType', issueTypes.join(','));
      if (queues.length) params.set('queue', queues.join(','));
      if (statuses.length) params.set('status', statuses.join(','));
      if (priorities.length) params.set('priority', priorities.join(','));
      if (assignedTo.length) params.set('assignedTo', assignedTo.join(','));
      if (analyzed !== 'any') params.set('analyzed', analyzed);
      if (needsReview) params.set('needsReview', 'true');
      if (search) params.set('search', search);

      const res = await fetch(`/api/analyzer/tickets?${params.toString()}`);
      if (!res.ok) {
        const data = (await res.json().catch(() => ({}))) as {
          error?: string;
          message?: string;
        };
        throw new Error(data.message ?? data.error ?? `Failed: ${res.status}`);
      }
      const data = (await res.json()) as { tickets: TicketRow[]; total: number };
      setTickets(data.tickets);
      setTotal(data.total);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      setTickets([]);
      setTotal(0);
    } finally {
      setLoading(false);
    }
  }, [
    period,
    startDate,
    endDate,
    clientIds,
    issueTypes,
    queues,
    statuses,
    priorities,
    assignedTo,
    analyzed,
    needsReview,
    search,
    sort,
    page,
  ]);

  useEffect(() => {
    void fetchTickets();
  }, [fetchTickets]);

  const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));

  const activeFilters = useMemo(() => {
    const chips: { key: string; label: string; clear: () => void }[] = [];
    if (period !== 'last_30d')
      chips.push({
        key: 'period',
        label:
          period === 'custom' && startDate && endDate
            ? `${startDate} → ${endDate}`
            : (PERIOD_OPTIONS.find((p) => p.value === period)?.label ?? period),
        clear: () => {
          setPeriod('last_30d');
          setStartDate('');
          setEndDate('');
        },
      });
    const lookup = (
      arr: { value: number | string; label: string }[] | undefined,
      sel: string[]
    ) =>
      sel
        .map(
          (s) =>
            arr?.find((o) => String(o.value ?? (o as { id?: string }).id ?? '') === s)
              ?.label ?? s
        )
        .join(', ');
    if (clientIds.length)
      chips.push({
        key: 'client',
        label: `Client: ${clientIds.length}`,
        clear: () => setClientIds([]),
      });
    if (issueTypes.length)
      chips.push({
        key: 'issue',
        label: `Issue type: ${lookup(filterOptions?.issueTypes, issueTypes)}`,
        clear: () => setIssueTypes([]),
      });
    if (queues.length)
      chips.push({
        key: 'queue',
        label: `Queue: ${lookup(filterOptions?.queues, queues)}`,
        clear: () => setQueues([]),
      });
    if (statuses.length)
      chips.push({
        key: 'status',
        label: `Status: ${lookup(filterOptions?.statuses, statuses)}`,
        clear: () => setStatuses([]),
      });
    if (priorities.length)
      chips.push({
        key: 'priority',
        label: `Priority: ${lookup(filterOptions?.priorities, priorities)}`,
        clear: () => setPriorities([]),
      });
    if (assignedTo.length)
      chips.push({
        key: 'assigned',
        label: `Assigned: ${assignedTo.length}`,
        clear: () => setAssignedTo([]),
      });
    if (analyzed !== 'any')
      chips.push({
        key: 'analyzed',
        label: `Analyzed: ${analyzed}`,
        clear: () => setAnalyzed('any'),
      });
    if (needsReview)
      chips.push({
        key: 'needsReview',
        label: 'Needs review',
        clear: () => setNeedsReview(false),
      });
    if (search)
      chips.push({
        key: 'search',
        label: `"${search}"`,
        clear: () => {
          setSearchInput('');
          setSearch('');
        },
      });
    return chips;
  }, [
    period,
    startDate,
    endDate,
    clientIds,
    issueTypes,
    queues,
    statuses,
    priorities,
    assignedTo,
    analyzed,
    needsReview,
    search,
    filterOptions,
  ]);

  function clearAll() {
    setPeriod('last_30d');
    setStartDate('');
    setEndDate('');
    setClientIds([]);
    setIssueTypes([]);
    setQueues([]);
    setStatuses([]);
    setPriorities([]);
    setAssignedTo([]);
    setAnalyzed('any');
    setNeedsReview(false);
    setSearchInput('');
    setSearch('');
  }

  const visibleSelectableCount = tickets.length;
  const visibleSelectedCount = tickets.filter((t) =>
    selectedSet.has(t.ticketNumber)
  ).length;
  const allOnPageSelected =
    visibleSelectableCount > 0 &&
    visibleSelectedCount === visibleSelectableCount;
  const someOnPageSelected =
    visibleSelectedCount > 0 && !allOnPageSelected;

  function toggleSelectAllOnPage() {
    if (allOnPageSelected) {
      setSelected((prev) =>
        prev.filter((id) => !tickets.some((t) => t.ticketNumber === id))
      );
    } else {
      const merged = new Set(selected);
      tickets.forEach((t) => merged.add(t.ticketNumber));
      setSelected(Array.from(merged));
    }
  }

  function toggleRow(ticketNumber: string) {
    setSelected((prev) =>
      prev.includes(ticketNumber)
        ? prev.filter((id) => id !== ticketNumber)
        : [...prev, ticketNumber]
    );
  }

  function clearSelection() {
    setSelected([]);
  }

  // Lookup of currently-selected tickets in the loaded set, for action gating.
  const selectedRows = useMemo(
    () => tickets.filter((t) => selectedSet.has(t.ticketNumber)),
    [tickets, selectedSet]
  );
  const selectedAllAnalyzedAndCurrent = useMemo(() => {
    if (selected.length === 0) return false;
    if (selectedRows.length !== selected.length) return false; // some selections off-page
    return selectedRows.every((r) => r.analyzedState === 'current');
  }, [selectedRows, selected]);
  const selectedAnyNeedsAnalyze = useMemo(() => {
    if (selectedRows.length === 0) return false;
    return selectedRows.some(
      (r) => r.analyzedState === 'none' || r.analyzedState === 'stale'
    );
  }, [selectedRows]);

  async function bulkAnalyze() {
    if (selectedRows.length === 0) return;
    const targets = selectedRows.filter(
      (r) => r.analyzedState !== 'current'
    );
    if (targets.length === 0) {
      toast.info('All selected tickets already have a current analysis.');
      return;
    }
    if (
      !confirm(
        `Analyze ${targets.length} ticket${targets.length === 1 ? '' : 's'}? This will queue ${targets.length} job${targets.length === 1 ? '' : 's'}.`
      )
    )
      return;
    setBulkRunning(true);
    let queued = 0;
    let failed = 0;
    for (const t of targets) {
      try {
        const res = await fetch(
          `/api/analyzer/tickets/${encodeURIComponent(t.ticketNumber)}/analyze`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ force: t.analyzedState === 'stale' }),
          }
        );
        if (res.ok) queued++;
        else failed++;
      } catch {
        failed++;
      }
    }
    setBulkRunning(false);
    toast.success(
      `Queued ${queued} job${queued === 1 ? '' : 's'}${failed ? ` (${failed} failed)` : ''}. Worker will pick them up shortly.`
    );
    void fetchTickets();
  }

  function bulkAggregateReport() {
    if (!selectedAllAnalyzedAndCurrent) {
      toast.error(
        'All selected tickets must be analyzed and current before generating an aggregate report.'
      );
      return;
    }
    const params = new URLSearchParams({
      ids: selected.join(','),
    });
    router.push(`/analyzer/reports/new?${params.toString()}`);
  }

  return (
    <div className="container mx-auto px-4 py-6 space-y-4 max-w-screen-2xl">
      <div className="flex items-start justify-between gap-4 flex-wrap">
        <div>
          <h1 className="text-2xl font-semibold tracking-tight">
            Browse Tickets to Analyze
          </h1>
          <p className="text-muted-foreground text-sm mt-1">
            Filter, select, and analyze in bulk. Selections persist across
            pagination via localStorage.
          </p>
        </div>
        <div className="flex gap-2">
          <Button variant="outline" asChild>
            <Link href="/analyzer/reports">Reports</Link>
          </Button>
          <Button variant="outline" asChild>
            <Link href="/analyzer/queue">Needs review</Link>
          </Button>
        </div>
      </div>

      {/* Sticky filter bar */}
      <div className="sticky top-0 z-30 -mx-4 px-4 pb-2 pt-2 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-b">
        <div className="space-y-3">
          {/* Period pills */}
          <div className="flex flex-wrap gap-1.5 items-center">
            {PERIOD_OPTIONS.map((p) => (
              <button
                key={p.value}
                type="button"
                onClick={() => setPeriod(p.value)}
                className={`px-3 py-1 rounded-full border text-xs transition ${
                  period === p.value
                    ? 'bg-primary text-primary-foreground border-primary'
                    : 'bg-background hover:bg-accent border-border'
                }`}
              >
                {p.label}
              </button>
            ))}
            {period === 'custom' && (
              <div className="flex items-center gap-1.5 ml-2">
                <Input
                  type="date"
                  value={startDate}
                  onChange={(e) => setStartDate(e.target.value)}
                  className="h-8 w-[140px]"
                />
                <span className="text-muted-foreground text-xs">→</span>
                <Input
                  type="date"
                  value={endDate}
                  onChange={(e) => setEndDate(e.target.value)}
                  className="h-8 w-[140px]"
                />
              </div>
            )}
          </div>

          {/* Multi-selects + analyzed + search */}
          <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-2">
            <MultiSelect
              options={
                (filterOptions?.companies ?? []).map((c) => ({
                  value: c.id,
                  label: c.name,
                })) as MultiSelectOption[]
              }
              value={clientIds}
              onChange={setClientIds}
              placeholder="Client"
              searchPlaceholder="Search clients…"
            />
            <MultiSelect
              options={
                (filterOptions?.issueTypes ?? []).map((it) => ({
                  value: String(it.value),
                  label: it.label,
                })) as MultiSelectOption[]
              }
              value={issueTypes}
              onChange={setIssueTypes}
              placeholder="Issue type"
            />
            <MultiSelect
              options={
                (filterOptions?.queues ?? []).map((q) => ({
                  value: String(q.value),
                  label: q.label,
                })) as MultiSelectOption[]
              }
              value={queues}
              onChange={setQueues}
              placeholder="Queue"
            />
            <MultiSelect
              options={
                (filterOptions?.statuses ?? []).map((s) => ({
                  value: String(s.value),
                  label: s.label,
                })) as MultiSelectOption[]
              }
              value={statuses}
              onChange={setStatuses}
              placeholder="Status"
            />
            <MultiSelect
              options={
                (filterOptions?.priorities ?? []).map((p) => ({
                  value: String(p.value),
                  label: p.label,
                })) as MultiSelectOption[]
              }
              value={priorities}
              onChange={setPriorities}
              placeholder="Priority"
            />
            <MultiSelect
              options={
                (filterOptions?.resources ?? []).map((r) => ({
                  value: r.id,
                  label: r.name,
                })) as MultiSelectOption[]
              }
              value={assignedTo}
              onChange={setAssignedTo}
              placeholder="Assigned to"
              searchPlaceholder="Search resources…"
            />
            <div className="relative">
              <Search className="w-4 h-4 absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
              <Input
                className="pl-8 h-9"
                placeholder="Search…"
                value={searchInput}
                onChange={(e) => setSearchInput(e.target.value)}
              />
            </div>
          </div>

          {/* Analyzed segmented + needs review + sort + clear */}
          <div className="flex flex-wrap items-center gap-3">
            <div className="flex items-center gap-1 rounded-md border bg-background p-0.5">
              {(['any', 'yes', 'no', 'stale'] as const).map((opt) => (
                <button
                  key={opt}
                  type="button"
                  onClick={() => setAnalyzed(opt)}
                  className={`px-2.5 py-1 rounded text-xs capitalize transition ${
                    analyzed === opt
                      ? 'bg-primary text-primary-foreground'
                      : 'hover:bg-accent'
                  }`}
                >
                  {opt === 'yes'
                    ? 'Analyzed'
                    : opt === 'no'
                      ? 'Not analyzed'
                      : opt === 'stale'
                        ? 'Has new activity'
                        : 'Any'}
                </button>
              ))}
            </div>
            <Label className="flex items-center gap-2 text-sm">
              <Checkbox
                checked={needsReview}
                onCheckedChange={(c) => setNeedsReview(c === true)}
              />
              Needs review
            </Label>
            <div className="flex items-center gap-2 ml-auto">
              <Label className="text-xs text-muted-foreground">Sort</Label>
              <Select value={sort} onValueChange={(v) => setSort(v as typeof sort)}>
                <SelectTrigger className="h-9 w-[180px]">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="last_activity_desc">Last activity ↓</SelectItem>
                  <SelectItem value="last_activity_asc">Last activity ↑</SelectItem>
                  <SelectItem value="created_desc">Newest first</SelectItem>
                  <SelectItem value="created_asc">Oldest first</SelectItem>
                  <SelectItem value="priority">Priority</SelectItem>
                </SelectContent>
              </Select>
              {activeFilters.length > 0 && (
                <Button variant="ghost" size="sm" onClick={clearAll}>
                  <X className="w-3 h-3 mr-1" />
                  Clear
                </Button>
              )}
            </div>
          </div>

          {/* Active filter chips */}
          {activeFilters.length > 0 && (
            <div className="flex flex-wrap gap-1.5">
              {activeFilters.map((f) => (
                <Badge
                  key={f.key}
                  variant="secondary"
                  className="gap-1 cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
                  onClick={f.clear}
                >
                  {f.label}
                  <X className="w-3 h-3" />
                </Badge>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Bulk actions + count */}
      <div className="flex flex-wrap items-center justify-between gap-3 pt-2">
        <div className="text-sm text-muted-foreground flex items-center gap-3">
          <FilterIcon className="w-4 h-4" />
          {loading
            ? 'Loading…'
            : `${total.toLocaleString()} ticket${total === 1 ? '' : 's'}`}
          {selected.length > 0 && (
            <Badge variant="secondary">{selected.length} selected</Badge>
          )}
          {selected.length > 0 && (
            <Button
              variant="ghost"
              size="sm"
              className="h-7"
              onClick={clearSelection}
            >
              Clear selection
            </Button>
          )}
        </div>
        <div className="flex gap-2">
          <Button
            disabled={!selectedAnyNeedsAnalyze || bulkRunning}
            onClick={bulkAnalyze}
            title={
              !selectedAnyNeedsAnalyze
                ? 'Select tickets that are not yet analyzed (or are stale)'
                : ''
            }
          >
            <Sparkles className="w-4 h-4 mr-2" />
            {bulkRunning
              ? 'Queueing…'
              : `Analyze ${selectedRows.filter((r) => r.analyzedState !== 'current').length || ''} selected`}
          </Button>
          <Button
            variant="outline"
            disabled={!selectedAllAnalyzedAndCurrent}
            onClick={bulkAggregateReport}
            title={
              !selectedAllAnalyzedAndCurrent
                ? 'All selected tickets must be analyzed and current'
                : ''
            }
          >
            Generate aggregate report
          </Button>
        </div>
      </div>

      {/* Results table */}
      <Card>
        <CardContent className="p-0">
          {error ? (
            <div className="p-6 text-sm text-destructive">{error}</div>
          ) : tickets.length === 0 && !loading ? (
            <div className="p-12 text-center text-muted-foreground">
              <Sparkles className="w-8 h-8 mx-auto mb-3 opacity-40" />
              <p>No tickets match the current filters.</p>
              <p className="text-xs mt-1">
                Try widening the period or clearing some filters.
              </p>
            </div>
          ) : (
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead className="w-[42px]">
                    <Checkbox
                      checked={
                        allOnPageSelected
                          ? true
                          : someOnPageSelected
                            ? 'indeterminate'
                            : false
                      }
                      onCheckedChange={toggleSelectAllOnPage}
                      aria-label="Select all on page"
                    />
                  </TableHead>
                  <TableHead className="w-[42px]">St.</TableHead>
                  <TableHead className="w-[140px]">Ticket</TableHead>
                  <TableHead>Title</TableHead>
                  <TableHead className="w-[160px]">Client</TableHead>
                  <TableHead className="w-[110px]">Status</TableHead>
                  <TableHead className="w-[90px]">Priority</TableHead>
                  <TableHead className="w-[90px]">Age</TableHead>
                  <TableHead className="w-[110px]">Last activity</TableHead>
                  <TableHead className="w-[120px]">Assigned</TableHead>
                  <TableHead className="w-[150px] text-right">Action</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {tickets.map((t) => (
                  <TableRow
                    key={t.ticketNumber}
                    data-selected={selectedSet.has(t.ticketNumber)}
                    className="data-[selected=true]:bg-accent/40"
                  >
                    <TableCell>
                      <Checkbox
                        checked={selectedSet.has(t.ticketNumber)}
                        onCheckedChange={() => toggleRow(t.ticketNumber)}
                        aria-label={`Select ${t.ticketNumber}`}
                      />
                    </TableCell>
                    <TableCell>
                      <AnalyzedDot state={t.analyzedState} />
                    </TableCell>
                    <TableCell className="font-mono text-xs">
                      <Link
                        href={`/analyzer/ticket/${encodeURIComponent(t.ticketNumber)}`}
                        className="hover:underline"
                      >
                        {t.ticketNumber}
                      </Link>
                    </TableCell>
                    <TableCell className="max-w-[480px]">
                      <div className="flex items-start gap-2">
                        <span className="line-clamp-2 text-sm">
                          {t.title ?? (
                            <span className="text-muted-foreground italic">
                              No title
                            </span>
                          )}
                        </span>
                        {t.needsHumanReview && (
                          <Badge
                            variant="outline"
                            className="shrink-0 border-amber-400 text-amber-700 dark:text-amber-400"
                            title="Needs human review"
                          >
                            <AlertTriangle className="w-3 h-3 mr-1" />
                            Review
                          </Badge>
                        )}
                      </div>
                    </TableCell>
                    <TableCell className="text-sm truncate max-w-[160px]">
                      {t.clientName ?? <span className="text-muted-foreground">—</span>}
                    </TableCell>
                    <TableCell className="text-xs">
                      {t.status ? (
                        <Badge variant="secondary" className="font-normal">
                          {t.status}
                        </Badge>
                      ) : (
                        <span className="text-muted-foreground">—</span>
                      )}
                    </TableCell>
                    <TableCell className="text-xs">
                      {t.priority ?? <span className="text-muted-foreground">—</span>}
                    </TableCell>
                    <TableCell className="text-xs text-muted-foreground">
                      {formatAge(t.ageInDays)}
                    </TableCell>
                    <TableCell
                      className="text-xs text-muted-foreground"
                      title={t.lastActivityAtAutotask ?? ''}
                    >
                      {formatRelative(t.lastActivityAtAutotask, tz)}
                    </TableCell>
                    <TableCell className="text-xs truncate max-w-[120px]">
                      {t.assignedResourceName ?? (
                        <span className="text-muted-foreground">—</span>
                      )}
                    </TableCell>
                    <TableCell className="text-right">
                      {t.latestAnalysisId ? (
                        <Button variant="outline" size="sm" asChild>
                          <Link href={`/analyzer/analysis/${t.latestAnalysisId}`}>
                            View analysis
                          </Link>
                        </Button>
                      ) : (
                        <Button variant="default" size="sm" asChild>
                          <Link
                            href={`/analyzer/ticket/${encodeURIComponent(t.ticketNumber)}`}
                          >
                            Analyze
                          </Link>
                        </Button>
                      )}
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          )}

          {total > PAGE_SIZE && (
            <div className="flex items-center justify-between p-3 text-sm border-t">
              <span className="text-muted-foreground">
                Page {page + 1} of {totalPages}
              </span>
              <div className="flex gap-2">
                <Button
                  variant="outline"
                  size="sm"
                  disabled={page === 0 || loading}
                  onClick={() => setPage((p) => Math.max(0, p - 1))}
                >
                  Prev
                </Button>
                <Button
                  variant="outline"
                  size="sm"
                  disabled={page + 1 >= totalPages || loading}
                  onClick={() => setPage((p) => p + 1)}
                >
                  Next
                </Button>
              </div>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
}

function AnalyzedDot({ state }: { state: 'none' | 'current' | 'stale' }) {
  if (state === 'current') {
    return (
      <CheckCircle2
        className="w-4 h-4 text-emerald-500"
        aria-label="Analyzed (current)"
      />
    );
  }
  if (state === 'stale') {
    return (
      <Clock className="w-4 h-4 text-amber-500" aria-label="Analyzed (stale)" />
    );
  }
  return (
    <CircleDot className="w-4 h-4 text-muted-foreground/40" aria-label="Not analyzed" />
  );
}
