import { useEffect, useMemo, useState } from 'react';
import {
  CheckCircle2,
  ChevronDown,
  ExternalLink,
  Link2,
  Link2Off,
  Loader2,
  Pencil,
  Plus,
  Search,
  X,
} from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api';
import { cn, fmt, errMessage } from '@/lib/utils';
import type { Dollars } from '@/lib/money';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';

interface Descriptor {
  id: number | string;
  descriptor?: string;
}

interface MatchedBill {
  id: number;
  name?: string;
  active?: number;
  monthly_equivalent?: Dollars;
}

interface CatalogEntry {
  id: number | string;
  name: string;
  category?: string | null;
  subscription_type?: string;
  starting_monthly_usd?: number | null;
  website?: string | null;
  matched_bill?: MatchedBill | null;
  user_descriptors?: Descriptor[];
}

const TYPE_LABELS: Record<string, string> = {
  streaming: 'Streaming',
  software: 'Software',
  cloud: 'Cloud',
  music: 'Music',
  news: 'News',
  fitness: 'Fitness',
  gaming: 'Gaming',
  utilities: 'Utilities',
  insurance: 'Insurance',
  food: 'Food',
  education: 'Education',
  shopping: 'Shopping',
  security: 'Security',
  other: 'Other',
};

const CHIPS: { value: string | null; label: string }[] = [
  { value: null, label: 'All' },
  { value: 'streaming', label: 'Streaming' },
  { value: 'music', label: 'Music' },
  { value: 'gaming', label: 'Gaming' },
  { value: 'news', label: 'News' },
  { value: 'fitness', label: 'Fitness' },
  { value: 'software', label: 'Software' },
  { value: 'security', label: 'Security' },
  { value: 'food', label: 'Food' },
  { value: 'cloud', label: 'Cloud' },
  { value: 'shopping', label: 'Shopping' },
  { value: 'education', label: 'Education' },
];

// Returns 'match' | 'above' | 'below' | null
function priceDrift(
  monthly: number | null | undefined,
  catalogStarting: number | null | undefined,
): 'match' | 'above' | 'below' | null {
  if (!catalogStarting || !monthly) return null;
  const pct = (monthly - catalogStarting) / catalogStarting;
  if (Math.abs(pct) <= 0.05) return 'match';
  return pct > 0 ? 'above' : 'below';
}

// ── Descriptor editor inline inside a matched card ─────────────────────────
function DescriptorEditor({
  catalogId,
  descriptors,
  onAdd,
  onDelete,
}: {
  catalogId: number | string;
  descriptors: Descriptor[];
  onAdd: (catalogId: number | string, descriptor: string) => Promise<boolean> | void;
  onDelete: (descriptorId: number | string) => void;
}) {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState('');
  const [busy, setBusy] = useState(false);

  async function handleAdd() {
    const d = value.trim();
    if (!d) return;
    setBusy(true);
    try {
      await onAdd(catalogId, d);
      setValue('');
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="mt-2 border-t border-border/30 pt-2">
      <button
        type="button"
        onClick={() => setOpen((s) => !s)}
        className="flex items-center gap-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground"
      >
        <ChevronDown className={cn('h-3 w-3 transition-transform', !open && '-rotate-90')} />
        Custom bank descriptors
        {descriptors.length > 0 && (
          <span className="ml-1 rounded bg-muted/50 px-1 font-mono text-[10px]">
            {descriptors.length}
          </span>
        )}
      </button>

      {open && (
        <div className="mt-2 space-y-1.5">
          {descriptors.map((d) => (
            <div key={d.id} className="flex items-center gap-2">
              <span className="flex-1 rounded border border-border/40 bg-muted/30 px-2 py-0.5 font-mono text-[11px]">
                {d.descriptor}
              </span>
              <button
                type="button"
                onClick={() => onDelete(d.id)}
                className="text-muted-foreground transition-colors hover:text-destructive"
                title="Remove descriptor"
              >
                <X className="h-3 w-3" />
              </button>
            </div>
          ))}

          <div className="flex gap-1.5">
            <Input
              placeholder="e.g. NETFLIX DIGITAL"
              value={value}
              onChange={(e) => setValue(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' && value.trim()) handleAdd();
              }}
              className="h-7 font-mono text-xs"
            />
            <Button
              type="button"
              size="sm"
              variant="outline"
              disabled={!value.trim() || busy}
              onClick={handleAdd}
              className="h-7 px-2"
            >
              {busy ? <Loader2 className="h-3 w-3 animate-spin" /> : <Plus className="h-3 w-3" />}
            </Button>
          </div>
          <p className="text-[10px] text-muted-foreground/70">
            Add the exact payee text your bank uses to improve auto-matching.
          </p>
        </div>
      )}
    </div>
  );
}

// ── Card for an already-tracked catalog entry ──────────────────────────────
function MatchedCard({
  entry,
  onEdit,
  onRelink,
  onAddDescriptor,
  onDeleteDescriptor,
}: {
  entry: CatalogEntry;
  onEdit: (id: number) => void;
  onRelink: (entry: CatalogEntry) => void;
  onAddDescriptor: (catalogId: number | string, descriptor: string) => Promise<boolean> | void;
  onDeleteDescriptor: (descriptorId: number | string) => void;
}) {
  const { matched_bill: bill, user_descriptors: descs = [] } = entry;
  const drift = priceDrift(bill?.monthly_equivalent, entry.starting_monthly_usd);

  return (
    <div className="rounded-lg border border-primary/20 bg-primary/5 p-3 transition-colors hover:border-primary/30">
      <div className="flex items-start justify-between gap-3">
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-center gap-1.5">
            <span className="text-sm font-semibold text-foreground">{entry.name}</span>
            <Badge
              variant="outline"
              className="border-primary/25 bg-primary/10 text-[10px] text-primary"
            >
              {TYPE_LABELS[entry.subscription_type ?? ''] || 'Other'}
            </Badge>
            {bill && !bill.active && (
              <Badge
                variant="outline"
                className="border-amber-500/25 bg-amber-500/10 text-[10px] text-amber-400"
              >
                Paused
              </Badge>
            )}
          </div>

          <div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs">
            {bill && (
              <span className="font-mono font-semibold text-foreground">
                {fmt(bill.monthly_equivalent)}/mo
              </span>
            )}
            {drift === 'match' && entry.starting_monthly_usd && (
              <span className="flex items-center gap-1 text-emerald-500">
                <CheckCircle2 className="h-3 w-3" />
                matches catalog price
              </span>
            )}
            {drift === 'above' && (
              <span className="text-amber-500">
                catalog from ${entry.starting_monthly_usd?.toFixed(2)}/mo
              </span>
            )}
            {drift === 'below' && entry.starting_monthly_usd && (
              <span className="text-sky-500">
                catalog from ${entry.starting_monthly_usd.toFixed(2)}/mo
              </span>
            )}
            {!entry.starting_monthly_usd && entry.website && (
              <a
                href={entry.website}
                target="_blank"
                rel="noreferrer"
                className="flex items-center gap-0.5 text-muted-foreground hover:text-primary"
              >
                website <ExternalLink className="h-2.5 w-2.5" />
              </a>
            )}
          </div>
        </div>

        <div className="flex shrink-0 items-center gap-1">
          <Button
            type="button"
            size="sm"
            variant="outline"
            className="h-7 gap-1 text-xs"
            onClick={() => bill && onEdit(bill.id)}
          >
            <Pencil className="h-3 w-3" />
            Edit
          </Button>
          <Button
            type="button"
            size="sm"
            variant="ghost"
            className="h-7 gap-1 text-xs text-muted-foreground hover:text-foreground"
            onClick={() => onRelink(entry)}
            title="Change catalog link"
          >
            <Link2 className="h-3 w-3" />
            Re-link
          </Button>
        </div>
      </div>

      <DescriptorEditor
        catalogId={entry.id}
        descriptors={descs}
        onAdd={onAddDescriptor}
        onDelete={onDeleteDescriptor}
      />
    </div>
  );
}

// ── Card for an untracked catalog entry ───────────────────────────────────
function UnmatchedCard({
  entry,
  selected,
  onToggleSelect,
  onTrack,
  trackingBusy,
}: {
  entry: CatalogEntry;
  selected: boolean;
  onToggleSelect: (id: number | string, checked: boolean) => void;
  onTrack: (entry: CatalogEntry) => void;
  trackingBusy: boolean;
}) {
  return (
    <div
      className={cn(
        'flex items-center gap-3 rounded-lg border px-3 py-2.5 transition-colors',
        selected
          ? 'border-primary/30 bg-primary/5'
          : 'border-border/60 bg-background/35 hover:border-border/80',
      )}
    >
      <Checkbox
        checked={selected}
        onCheckedChange={(checked) => onToggleSelect(entry.id, !!checked)}
        aria-label={`Select ${entry.name}`}
      />
      <div className="min-w-0 flex-1">
        <p className="text-sm font-medium text-foreground">{entry.name}</p>
        <p className="text-xs text-muted-foreground">
          {entry.category}
          {entry.starting_monthly_usd && (
            <span className="ml-2 font-mono">from ${entry.starting_monthly_usd.toFixed(2)}/mo</span>
          )}
        </p>
      </div>
      {entry.website && (
        <a
          href={entry.website}
          target="_blank"
          rel="noreferrer"
          className="shrink-0 text-muted-foreground hover:text-primary"
          title="Open website"
        >
          <ExternalLink className="h-3.5 w-3.5" />
        </a>
      )}
      <Button
        type="button"
        size="sm"
        variant="outline"
        className="h-7 shrink-0 gap-1 text-xs"
        disabled={trackingBusy}
        onClick={() => onTrack(entry)}
      >
        {trackingBusy ? (
          <Loader2 className="h-3 w-3 animate-spin" />
        ) : (
          <>
            <Plus className="h-3 w-3" />
            Track
          </>
        )}
      </Button>
    </div>
  );
}

// ── Re-link dialog ─────────────────────────────────────────────────────────
function ReLinkDialog({
  open,
  relinkEntry,
  allEntries,
  onClose,
  onConfirm,
  busy,
}: {
  open: boolean;
  relinkEntry: CatalogEntry | null;
  allEntries: CatalogEntry[];
  onClose: () => void;
  onConfirm: (billId: number | undefined, catalogId: number | string | null) => void;
  busy: boolean;
}) {
  const [search, setSearch] = useState('');
  const [selected, setSelected] = useState<number | string | null>(null);

  useEffect(() => {
    if (open) {
      setSearch('');
      setSelected(null);
    }
  }, [open]);

  const filtered = useMemo(() => {
    const q = search.toLowerCase();
    return allEntries.filter(
      (e) => !q || e.name.toLowerCase().includes(q) || (e.category || '').toLowerCase().includes(q),
    );
  }, [allEntries, search]);

  const bill = relinkEntry?.matched_bill;

  return (
    <Dialog
      open={open}
      onOpenChange={(v) => {
        if (!v) onClose();
      }}
    >
      <DialogContent className="border-border/60 bg-card/95 backdrop-blur-xl sm:max-w-md">
        <DialogHeader>
          <DialogTitle className="text-base font-semibold">Re-link to catalog entry</DialogTitle>
          {bill && (
            <p className="text-sm text-muted-foreground">
              Choose the correct service for{' '}
              <span className="font-medium text-foreground">{bill.name}</span>.
            </p>
          )}
        </DialogHeader>

        <Input
          autoFocus
          placeholder="Search services…"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="text-sm"
        />

        <div className="max-h-60 divide-y divide-border/40 overflow-y-auto rounded-lg border border-border/60">
          {/* Unlink option */}
          <button
            type="button"
            onClick={() => setSelected('unlink')}
            className={cn(
              'flex w-full items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-muted/30',
              selected === 'unlink' && 'bg-primary/5',
            )}
          >
            <Link2Off className="h-4 w-4 shrink-0 text-muted-foreground" />
            <span className="text-sm text-muted-foreground">Unlink from catalog</span>
            {selected === 'unlink' && (
              <CheckCircle2 className="ml-auto h-4 w-4 shrink-0 text-primary" />
            )}
          </button>

          {filtered.length === 0 && search ? (
            <p className="px-3 py-4 text-center text-sm text-muted-foreground">
              No services found.
            </p>
          ) : (
            filtered.map((e) => (
              <button
                key={e.id}
                type="button"
                onClick={() => setSelected(e.id)}
                disabled={e.id === relinkEntry?.id}
                className={cn(
                  'flex w-full items-center justify-between gap-3 px-3 py-2 text-left transition-colors',
                  'hover:bg-muted/30 disabled:pointer-events-none disabled:opacity-40',
                  selected === e.id && 'bg-primary/5',
                )}
              >
                <div className="min-w-0">
                  <p className="truncate text-sm font-medium">{e.name}</p>
                  <p className="text-xs text-muted-foreground">{e.category}</p>
                </div>
                <div className="flex shrink-0 items-center gap-2">
                  {e.starting_monthly_usd && (
                    <span className="font-mono text-xs text-muted-foreground">
                      ${e.starting_monthly_usd.toFixed(2)}/mo
                    </span>
                  )}
                  {selected === e.id && <CheckCircle2 className="h-4 w-4 text-primary" />}
                </div>
              </button>
            ))
          )}
        </div>

        <DialogFooter className="gap-2">
          <Button type="button" variant="ghost" onClick={onClose} className="text-xs">
            Cancel
          </Button>
          <Button
            type="button"
            disabled={!selected || busy}
            onClick={() => onConfirm(bill?.id, selected === 'unlink' ? null : selected)}
            className="text-xs"
          >
            {busy ? 'Saving…' : 'Confirm'}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// ── Main component ─────────────────────────────────────────────────────────
export default function SubscriptionCatalogSection({
  onEditBill,
  onTrackComplete,
}: {
  onEditBill: (id: number) => void;
  onTrackComplete?: () => void;
}) {
  const [catalog, setCatalog] = useState<CatalogEntry[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [showUnmatched, setShowUnmatched] = useState(false);
  const [activeType, setActiveType] = useState<string | null>(null);
  const [search, setSearch] = useState('');
  const [selectedIds, setSelectedIds] = useState<Set<number | string>>(new Set());
  const [relinkEntry, setRelinkEntry] = useState<CatalogEntry | null>(null);
  const [relinkBusy, setRelinkBusy] = useState(false);
  const [trackBusyId, setTrackBusyId] = useState<number | string | null>(null);
  const [bulkBusy, setBulkBusy] = useState(false);

  async function loadCatalog() {
    setLoading(true);
    setError(null);
    try {
      const data = (await api.subscriptionCatalog()) as { catalog?: CatalogEntry[] };
      setCatalog(data.catalog || []);
    } catch (err) {
      const message = errMessage(err, 'Failed to load catalog');
      setError(message);
      toast.error(message);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    loadCatalog();
  }, []);

  // Filter by type chip + search
  const filtered = useMemo(() => {
    const q = search.toLowerCase().trim();
    return catalog.filter((e) => {
      if (activeType && e.subscription_type !== activeType) return false;
      if (q)
        return e.name.toLowerCase().includes(q) || (e.category || '').toLowerCase().includes(q);
      return true;
    });
  }, [catalog, activeType, search]);

  const matched = filtered.filter((e) => e.matched_bill != null);
  const unmatched = filtered.filter((e) => e.matched_bill == null);

  // ── Handlers ─────────────────────────────────────────────────────────────

  async function handleTrack(entry: CatalogEntry) {
    setTrackBusyId(entry.id);
    try {
      await api.createSubscriptionFromRecommendation({
        name: entry.name,
        due_day: 1,
        expected_amount: entry.starting_monthly_usd || 0,
        cycle_type: 'monthly',
        subscription_type: entry.subscription_type || 'other',
        catalog_match: {
          id: entry.id,
          name: entry.name,
          subscription_type: entry.subscription_type,
        },
      });
      toast.success(`${entry.name} added to your subscriptions`, {
        description: 'Open it with Edit to set the correct amount and due date.',
      });
      await loadCatalog();
      onTrackComplete?.();
    } catch (err) {
      toast.error(errMessage(err, `Failed to add ${entry.name}`));
    } finally {
      setTrackBusyId(null);
    }
  }

  async function handleBulkTrack() {
    if (selectedIds.size === 0) return;
    setBulkBusy(true);
    const toTrack = unmatched.filter((e) => selectedIds.has(e.id));
    let succeeded = 0;
    let failed = 0;
    for (const entry of toTrack) {
      try {
        await api.createSubscriptionFromRecommendation({
          name: entry.name,
          due_day: 1,
          expected_amount: entry.starting_monthly_usd || 0,
          cycle_type: 'monthly',
          subscription_type: entry.subscription_type || 'other',
          catalog_match: {
            id: entry.id,
            name: entry.name,
            subscription_type: entry.subscription_type,
          },
        });
        succeeded++;
      } catch {
        failed++;
      }
    }
    if (succeeded > 0) {
      toast.success(`${succeeded} subscription${succeeded !== 1 ? 's' : ''} added`);
      setSelectedIds(new Set());
      await loadCatalog();
      onTrackComplete?.();
    }
    if (failed > 0) {
      toast.error(`${failed} subscription${failed !== 1 ? 's' : ''} could not be added`);
    }
    setBulkBusy(false);
  }

  async function handleRelink(billId: number | undefined, catalogId: number | string | null) {
    if (billId == null) return;
    setRelinkBusy(true);
    try {
      await api.updateSubscriptionCatalogLink(billId, catalogId as number | string);
      toast.success(catalogId ? 'Catalog link updated' : 'Catalog link removed');
      setRelinkEntry(null);
      await loadCatalog();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to update catalog link'));
    } finally {
      setRelinkBusy(false);
    }
  }

  async function handleAddDescriptor(
    catalogId: number | string,
    descriptor: string,
  ): Promise<boolean> {
    try {
      const newDesc = (await api.addCatalogDescriptor(catalogId, descriptor)) as Descriptor;
      setCatalog((prev) =>
        prev.map((e) =>
          e.id === catalogId
            ? { ...e, user_descriptors: [...(e.user_descriptors || []), newDesc] }
            : e,
        ),
      );
      toast.success('Descriptor added');
      return true;
    } catch (err) {
      toast.error(errMessage(err, 'Failed to add descriptor'));
      return false;
    }
  }

  async function handleDeleteDescriptor(catalogId: number | string, descriptorId: number | string) {
    try {
      await api.deleteCatalogDescriptor(descriptorId);
      setCatalog((prev) =>
        prev.map((e) =>
          e.id === catalogId
            ? {
                ...e,
                user_descriptors: (e.user_descriptors || []).filter((d) => d.id !== descriptorId),
              }
            : e,
        ),
      );
      toast.success('Descriptor removed');
    } catch (err) {
      toast.error(errMessage(err, 'Failed to remove descriptor'));
    }
  }

  function toggleSelect(id: number | string, checked: boolean) {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (checked) next.add(id);
      else next.delete(id);
      return next;
    });
  }

  // ── Render ────────────────────────────────────────────────────────────────

  return (
    <Card className="overflow-hidden">
      <CardHeader className="px-4 pb-3 sm:px-6">
        <CardTitle className="text-base">Known Service Catalog</CardTitle>
        <CardDescription>
          Popular services, linked bills, and custom bank descriptors used to improve matching.
        </CardDescription>

        {/* Category filter chips */}
        <div className="mt-3 flex flex-wrap gap-1.5">
          {CHIPS.map((chip) => (
            <button
              key={String(chip.value)}
              type="button"
              onClick={() => setActiveType(chip.value)}
              className={cn(
                'rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors',
                activeType === chip.value
                  ? 'border-primary/40 bg-primary/15 text-primary'
                  : 'border-border/60 bg-muted/20 text-muted-foreground hover:border-border/80 hover:text-foreground',
              )}
            >
              {chip.label}
            </button>
          ))}
        </div>

        {/* Search */}
        <div className="relative mt-2">
          <Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
          <Input
            type="search"
            placeholder="Search services…"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="h-9 pl-8 text-sm"
          />
        </div>
      </CardHeader>

      <CardContent className="p-0">
        {loading ? (
          <div className="flex items-center justify-center gap-2 py-10 text-sm text-muted-foreground">
            <Loader2 className="h-4 w-4 animate-spin" />
            Loading catalog…
          </div>
        ) : error ? (
          <div className="px-4 py-8 text-center">
            <p className="text-sm text-destructive">{error}</p>
            <Button
              type="button"
              variant="outline"
              size="sm"
              className="mt-3 text-xs"
              onClick={loadCatalog}
            >
              Retry
            </Button>
          </div>
        ) : (
          <div className="divide-y divide-border/40">
            {/* ── Matched group ───────────────────────────────────────── */}
            {matched.length > 0 && (
              <div className="px-4 pb-3 pt-3 sm:px-6">
                <p className="mb-2.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
                  Tracking ({matched.length})
                </p>
                <div className="space-y-2">
                  {matched.map((entry) => (
                    <MatchedCard
                      key={entry.id}
                      entry={entry}
                      onEdit={onEditBill}
                      onRelink={setRelinkEntry}
                      onAddDescriptor={handleAddDescriptor}
                      onDeleteDescriptor={(descriptorId) =>
                        handleDeleteDescriptor(entry.id, descriptorId)
                      }
                    />
                  ))}
                </div>
              </div>
            )}

            {/* ── Unmatched toggle + group ─────────────────────────── */}
            <div>
              <button
                type="button"
                onClick={() => setShowUnmatched((s) => !s)}
                className={cn(
                  'flex w-full items-center justify-between px-4 py-3 text-sm font-medium text-muted-foreground',
                  'transition-colors hover:bg-muted/20 hover:text-foreground sm:px-6',
                )}
              >
                <span className="flex items-center gap-2">
                  <ChevronDown
                    className={cn('h-4 w-4 transition-transform', !showUnmatched && '-rotate-90')}
                  />
                  {showUnmatched
                    ? `Hide ${unmatched.length} more service${unmatched.length !== 1 ? 's' : ''}`
                    : `Show ${unmatched.length} more service${unmatched.length !== 1 ? 's' : ''}`}
                </span>
                {selectedIds.size > 0 && (
                  <Badge
                    variant="outline"
                    className="border-primary/30 bg-primary/10 text-primary text-[10px]"
                  >
                    {selectedIds.size} selected
                  </Badge>
                )}
              </button>

              {showUnmatched && (
                <div className="px-4 pb-4 pt-1 sm:px-6">
                  {unmatched.length === 0 ? (
                    <p className="py-4 text-center text-sm text-muted-foreground">
                      {search || activeType
                        ? 'No services match your filter.'
                        : 'All known services are already being tracked.'}
                    </p>
                  ) : (
                    <div className="space-y-1.5">
                      {unmatched.map((entry) => (
                        <UnmatchedCard
                          key={entry.id}
                          entry={entry}
                          selected={selectedIds.has(entry.id)}
                          onToggleSelect={toggleSelect}
                          onTrack={handleTrack}
                          trackingBusy={trackBusyId === entry.id}
                        />
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        )}
      </CardContent>

      {/* ── Re-link dialog ─────────────────────────────────────────────── */}
      <ReLinkDialog
        open={!!relinkEntry}
        relinkEntry={relinkEntry}
        allEntries={catalog}
        onClose={() => setRelinkEntry(null)}
        onConfirm={handleRelink}
        busy={relinkBusy}
      />

      {/* ── Bulk action bar ────────────────────────────────────────────── */}
      {selectedIds.size > 0 && (
        <div className="sticky bottom-0 flex items-center justify-between gap-3 border-t border-primary/20 bg-card/95 px-4 py-3 backdrop-blur-md sm:px-6">
          <div className="flex items-center gap-2">
            <Badge
              variant="outline"
              className="border-primary/30 bg-primary/10 font-mono text-primary"
            >
              {selectedIds.size}
            </Badge>
            <span className="text-sm font-medium">
              {selectedIds.size === 1 ? 'service' : 'services'} selected
            </span>
          </div>
          <div className="flex gap-2">
            <Button
              type="button"
              variant="ghost"
              size="sm"
              className="text-xs"
              onClick={() => setSelectedIds(new Set())}
              disabled={bulkBusy}
            >
              Clear
            </Button>
            <Button
              type="button"
              size="sm"
              className="text-xs"
              disabled={bulkBusy}
              onClick={handleBulkTrack}
            >
              {bulkBusy ? (
                <>
                  <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
                  Adding…
                </>
              ) : (
                `Track ${selectedIds.size} selected`
              )}
            </Button>
          </div>
        </div>
      )}
    </Card>
  );
}
