import { useCallback, useEffect, useMemo, useState } from 'react';
import { SystemSheet, SheetSection } from '@/components/ui/system-sheet';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
  ShieldOff,
  ShieldCheck,
  ExternalLink,
  ChevronLeft,
  ChevronRight,
  RefreshCw,
  Download,
  Loader2,
  Check,
  GitCompare,
} from 'lucide-react';
import { Combobox } from '@/components/ui/combobox';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { ScanComparisonSheet } from './ScanComparisonSheet';
import { apiFetch } from '@/lib/api';
import { toast } from '@/components/ui/toast-store';
import { cn } from '@/lib/utils';
import { cveUrl } from '@/lib/cveUrl';
import { SEVERITY_ROW_TINT } from '@/lib/severityStyles';
import { formatTimeAgo } from '@/lib/relativeTime';
import type {
  VulnerabilityScan,
  VulnerabilityDetail,
  VulnSeverity,
  SecretFinding,
  MisconfigFinding,
} from '@/types/security';

interface VulnerabilityScanSheetProps {
  scanId: number | null;
  onClose: () => void;
  onRescan?: (imageRef: string) => void;
  canGenerateSbom?: boolean;
  canCompare?: boolean;
  canManageSuppressions?: boolean;
}

interface SuppressDialogState {
  cveId: string;
  pkgName: string;
  imagePattern: string;
  reason: string;
  expiresInDays: string;
}

interface AckDialogState {
  ruleId: string;
  stackPattern: string;
  reason: string;
  expiresInDays: string;
}

type SeverityFilter = 'ALL' | VulnSeverity;
type FindingTab = 'vulns' | 'secrets' | 'misconfigs';

const PAGE_SIZE = 25;

const SEVERITY_CLASSES: Record<VulnSeverity, string> = {
  CRITICAL: 'text-destructive border-destructive/40 bg-destructive/10',
  HIGH: 'text-warning border-warning/40 bg-warning/10',
  MEDIUM: 'text-warning border-warning/40 bg-warning/10',
  LOW: 'text-stat-subtitle border-border bg-muted/30',
  UNKNOWN: 'text-stat-subtitle border-border bg-muted/20',
};

function SeverityChip({ severity }: { severity: VulnSeverity }) {
  return (
    <span
      className={cn(
        'inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel',
        SEVERITY_CLASSES[severity],
      )}
    >
      {severity}
    </span>
  );
}

export function VulnerabilityScanSheet({
  scanId,
  onClose,
  onRescan,
  canGenerateSbom = false,
  canCompare = false,
  canManageSuppressions = false,
}: VulnerabilityScanSheetProps) {
  const [scan, setScan] = useState<VulnerabilityScan | null>(null);
  const [details, setDetails] = useState<VulnerabilityDetail[]>([]);
  const [totalDetails, setTotalDetails] = useState(0);
  const [secrets, setSecrets] = useState<SecretFinding[]>([]);
  const [misconfigs, setMisconfigs] = useState<MisconfigFinding[]>([]);
  const [loading, setLoading] = useState(false);
  const [severityFilter, setSeverityFilter] = useState<SeverityFilter>('ALL');
  const [page, setPage] = useState(0);
  const [secretsPage, setSecretsPage] = useState(0);
  const [misconfigsPage, setMisconfigsPage] = useState(0);
  const [tab, setTab] = useState<FindingTab>('vulns');
  const [downloadingSbom, setDownloadingSbom] = useState(false);
  const [compareOpen, setCompareOpen] = useState(false);
  const [compareOptions, setCompareOptions] = useState<VulnerabilityScan[]>([]);
  const [compareLoading, setCompareLoading] = useState(false);
  const [compareBaselineId, setCompareBaselineId] = useState<number | null>(null);
  const [suppressForm, setSuppressForm] = useState<SuppressDialogState | null>(null);
  const [savingSuppression, setSavingSuppression] = useState(false);
  const [ackForm, setAckForm] = useState<AckDialogState | null>(null);
  const [savingAck, setSavingAck] = useState(false);

  const DETAIL_FETCH_LIMIT = 500;

  const load = useCallback(async () => {
    if (scanId == null) return;
    setLoading(true);
    try {
      const [scanRes, detailsRes, secretsRes, misconfigsRes] = await Promise.all([
        apiFetch(`/security/scans/${scanId}`),
        apiFetch(`/security/scans/${scanId}/vulnerabilities?limit=${DETAIL_FETCH_LIMIT}`),
        apiFetch(`/security/scans/${scanId}/secrets?limit=${DETAIL_FETCH_LIMIT}`),
        apiFetch(`/security/scans/${scanId}/misconfigs?limit=${DETAIL_FETCH_LIMIT}`),
      ]);
      if (!scanRes.ok) throw new Error('Failed to fetch scan');
      if (!detailsRes.ok) throw new Error('Failed to fetch vulnerabilities');
      const scanData = (await scanRes.json()) as VulnerabilityScan;
      const detailsData = await detailsRes.json();
      const secretsData = secretsRes.ok ? await secretsRes.json() : { items: [] };
      const misconfigsData = misconfigsRes.ok ? await misconfigsRes.json() : { items: [] };
      setScan(scanData);
      setDetails(Array.isArray(detailsData.items) ? detailsData.items : []);
      setTotalDetails(typeof detailsData.total === 'number' ? detailsData.total : 0);
      setSecrets(Array.isArray(secretsData.items) ? secretsData.items : []);
      setMisconfigs(Array.isArray(misconfigsData.items) ? misconfigsData.items : []);
      setPage(0);
      setSecretsPage(0);
      setMisconfigsPage(0);
      if ((scanData.total_vulnerabilities ?? 0) === 0) {
        if ((scanData.misconfig_count ?? 0) > 0) setTab('misconfigs');
        else if ((scanData.secret_count ?? 0) > 0) setTab('secrets');
        else setTab('vulns');
      } else {
        setTab('vulns');
      }
    } catch (err) {
      toast.error((err as Error)?.message || 'Failed to load scan');
    } finally {
      setLoading(false);
    }
  }, [scanId]);

  useEffect(() => {
    setCompareOpen(false);
    setCompareOptions([]);
    setCompareBaselineId(null);
    if (scanId != null) {
      load();
    } else {
      setScan(null);
      setDetails([]);
      setTotalDetails(0);
      setSecrets([]);
      setMisconfigs([]);
      setSeverityFilter('ALL');
      setPage(0);
      setSecretsPage(0);
      setMisconfigsPage(0);
      setTab('vulns');
    }
  }, [scanId, load]);

  const filtered = useMemo(() => {
    if (severityFilter === 'ALL') return details;
    return details.filter((d) => d.severity === severityFilter);
  }, [details, severityFilter]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const safePage = Math.min(page, totalPages - 1);
  const pageItems = filtered.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
  const needsPagination = filtered.length > PAGE_SIZE;

  const secretsTotalPages = Math.max(1, Math.ceil(secrets.length / PAGE_SIZE));
  const secretsSafePage = Math.min(secretsPage, secretsTotalPages - 1);
  const secretsPageItems = secrets.slice(
    secretsSafePage * PAGE_SIZE,
    (secretsSafePage + 1) * PAGE_SIZE,
  );
  const secretsNeedsPagination = secrets.length > PAGE_SIZE;

  const misconfigsTotalPages = Math.max(1, Math.ceil(misconfigs.length / PAGE_SIZE));
  const misconfigsSafePage = Math.min(misconfigsPage, misconfigsTotalPages - 1);
  const misconfigsPageItems = misconfigs.slice(
    misconfigsSafePage * PAGE_SIZE,
    (misconfigsSafePage + 1) * PAGE_SIZE,
  );
  const misconfigsNeedsPagination = misconfigs.length > PAGE_SIZE;

  const downloadSbom = useCallback(
    async (format: 'spdx-json' | 'cyclonedx') => {
      if (!scan) return;
      setDownloadingSbom(true);
      try {
        const res = await apiFetch('/security/sbom', {
          method: 'POST',
          body: JSON.stringify({ imageRef: scan.image_ref, format }),
        });
        if (!res.ok) {
          const body = await res.json().catch(() => ({}));
          throw new Error(body?.error || 'Failed to generate SBOM');
        }
        const blob = await res.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `${scan.image_ref.replace(/[^a-z0-9]+/gi, '_')}-sbom-${format}.json`;
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(url);
        toast.success('SBOM downloaded');
      } catch (err) {
        toast.error((err as Error)?.message || 'SBOM generation failed');
      } finally {
        setDownloadingSbom(false);
      }
    },
    [scan],
  );

  const openCompareMenu = useCallback(async () => {
    if (!scan) return;
    setCompareOpen((o) => !o);
    if (compareOptions.length > 0 || compareLoading) return;
    setCompareLoading(true);
    try {
      const res = await apiFetch(
        `/security/scans?imageRef=${encodeURIComponent(scan.image_ref)}&limit=25`,
      );
      if (!res.ok) throw new Error('Failed to load scan history');
      const body = await res.json();
      const items: VulnerabilityScan[] = Array.isArray(body?.items) ? body.items : [];
      setCompareOptions(
        items.filter((s) => s.id !== scan.id && s.status === 'completed'),
      );
    } catch (err) {
      toast.error((err as Error)?.message || 'Could not load scan history');
    } finally {
      setCompareLoading(false);
    }
  }, [scan, compareOptions.length, compareLoading]);

  const openSuppressDialog = useCallback((d: VulnerabilityDetail) => {
    setSuppressForm({
      cveId: d.vulnerability_id,
      pkgName: d.pkg_name,
      imagePattern: '',
      reason: '',
      expiresInDays: '',
    });
  }, []);

  const submitSuppression = useCallback(async () => {
    if (!suppressForm) return;
    const reason = suppressForm.reason.trim();
    if (!reason) {
      toast.error('A reason is required.');
      return;
    }
    const days = suppressForm.expiresInDays.trim();
    let expiresAt: number | null = null;
    if (days) {
      const n = Number(days);
      if (!Number.isFinite(n) || n <= 0) {
        toast.error('Expiry must be a positive number of days or blank.');
        return;
      }
      expiresAt = Date.now() + n * 24 * 60 * 60 * 1000;
    }
    setSavingSuppression(true);
    try {
      const res = await apiFetch('/security/suppressions', {
        method: 'POST',
        localOnly: true,
        body: JSON.stringify({
          cve_id: suppressForm.cveId,
          pkg_name: suppressForm.pkgName || null,
          image_pattern: suppressForm.imagePattern.trim() || null,
          reason,
          expires_at: expiresAt,
        }),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body?.error || 'Failed to create suppression');
      }
      toast.success('Suppression created');
      setSuppressForm(null);
      await load();
    } catch (err) {
      toast.error((err as Error)?.message || 'Failed to create suppression');
    } finally {
      setSavingSuppression(false);
    }
  }, [suppressForm, load]);

  const openAckDialog = useCallback((m: MisconfigFinding) => {
    // Prefill stack_pattern with the exact stack name when this scan is a
    // stack-scoped config scan. Operators can broaden in the dialog.
    const stackPattern = scan?.stack_context ?? '';
    setAckForm({
      ruleId: m.rule_id,
      stackPattern,
      reason: '',
      expiresInDays: '',
    });
  }, [scan]);

  const submitAcknowledgement = useCallback(async () => {
    if (!ackForm) return;
    const reason = ackForm.reason.trim();
    if (!reason) {
      toast.error('A reason is required.');
      return;
    }
    const days = ackForm.expiresInDays.trim();
    let expiresAt: number | null = null;
    if (days) {
      const n = Number(days);
      if (!Number.isFinite(n) || n <= 0) {
        toast.error('Expiry must be a positive number of days or blank.');
        return;
      }
      expiresAt = Date.now() + n * 24 * 60 * 60 * 1000;
    }
    setSavingAck(true);
    try {
      const res = await apiFetch('/security/misconfig-acks', {
        method: 'POST',
        localOnly: true,
        body: JSON.stringify({
          rule_id: ackForm.ruleId,
          stack_pattern: ackForm.stackPattern.trim() || null,
          reason,
          expires_at: expiresAt,
        }),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body?.error || 'Failed to create acknowledgement');
      }
      toast.success('Acknowledgement created');
      setAckForm(null);
      await load();
    } catch (err) {
      toast.error((err as Error)?.message || 'Failed to create acknowledgement');
    } finally {
      setSavingAck(false);
    }
  }, [ackForm, load]);

  const exportCsv = useCallback(() => {
    if (!scan || details.length === 0) return;
    const header = 'CVE,Package,Severity,Installed,Fixed,URL\n';
    const escape = (v: string) => `"${v.replace(/"/g, '""')}"`;
    const rows = details
      .map((d) =>
        [
          escape(d.vulnerability_id),
          escape(d.pkg_name),
          escape(d.severity),
          escape(d.installed_version),
          escape(d.fixed_version ?? ''),
          escape(d.primary_url ?? ''),
        ].join(','),
      )
      .join('\n');
    const blob = new Blob([header + rows], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${scan.image_ref.replace(/[^a-z0-9]+/gi, '_')}-vulnerabilities.csv`;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
  }, [scan, details]);

  const exportSarif = useCallback(async () => {
    if (!scan) return;
    try {
      const res = await apiFetch(`/security/scans/${scan.id}/sarif`);
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body?.error || 'Failed to generate SARIF');
      }
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `${scan.image_ref.replace(/[^a-z0-9]+/gi, '_')}.sarif.json`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(url);
      toast.success('SARIF downloaded');
    } catch (err) {
      const error = err as { message?: string; error?: string; data?: { error?: string } };
      toast.error(error?.message || error?.error || error?.data?.error || 'SARIF export failed');
    }
  }, [scan]);

  const meta = scan
    ? `${scan.total_vulnerabilities} vulns · ${scan.fixable_count} fixable · ${scan.triggered_by}`
    : (loading ? 'Loading…' : 'No scan');

  const footerContext = scan
    ? `Scanned ${formatTimeAgo(new Date(scan.scanned_at).getTime())}`
    : undefined;

  const secondaryActions = scan ? [
    ...(canCompare ? [{
      label: 'Compare',
      icon: compareLoading ? Loader2 : GitCompare,
      onClick: openCompareMenu,
      disabled: compareLoading,
    }] : []),
    ...(details.length > 0 ? [{
      label: 'CSV',
      icon: Download,
      onClick: exportCsv,
    }] : []),
    ...(canGenerateSbom && scan.status === 'completed' ? [{
      label: 'SARIF',
      icon: Download,
      onClick: () => { void exportSarif(); },
    }] : []),
  ] : undefined;

  return (
    <>
      <SystemSheet
        open={scanId != null}
        onOpenChange={(open) => !open && onClose()}
        crumb={['Security', 'Scans', scan?.image_ref ?? '…']}
        name={scan?.image_ref ?? 'Loading…'}
        meta={meta}
        primaryAction={onRescan && scan ? {
          label: 'Re-scan',
          icon: RefreshCw,
          onClick: () => onRescan(scan.image_ref),
          disabled: scan.status === 'in_progress',
        } : undefined}
        secondaryActions={secondaryActions}
        tabs={scan ? [
          { id: 'vulns', label: 'Vulnerabilities', count: totalDetails },
          { id: 'secrets', label: 'Secrets', count: scan.secret_count ?? secrets.length },
          { id: 'misconfigs', label: 'Misconfigs', count: scan.misconfig_count ?? misconfigs.length },
        ] : undefined}
        activeTab={tab}
        onTabChange={(id) => setTab(id as FindingTab)}
        footerContext={footerContext}
        size="lg"
      >
        {loading && !scan && (
          <div className="flex items-center justify-center py-12">
            <Loader2 className="w-6 h-6 animate-spin text-muted-foreground" strokeWidth={1.5} />
          </div>
        )}

        {scan && (
          <>
            <SheetSection title="Summary">
              {scan.policy_evaluation?.violated && (
                <div
                  role="alert"
                  className="relative rounded border border-destructive/40 bg-destructive/10 px-3 py-2 pl-4 mb-3 shadow-card-bevel"
                >
                  <span
                    aria-hidden="true"
                    className="absolute left-0 top-0 bottom-0 w-[3px] bg-destructive/80 rounded-l"
                  />
                  <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-destructive">
                    Policy violation
                  </div>
                  <div className="text-sm text-stat-value mt-0.5">
                    <span className="font-mono">{scan.policy_evaluation.policyName}</span> blocks
                    severities at or above{' '}
                    <span className="font-mono tabular-nums">
                      {scan.policy_evaluation.maxSeverity}
                    </span>
                    . This scan's highest severity is{' '}
                    <span className="font-mono tabular-nums">
                      {scan.highest_severity ?? 'UNKNOWN'}
                    </span>
                    .
                  </div>
                </div>
              )}
              <div className="flex flex-wrap gap-2 mb-3">
                {scan.critical_count > 0 && (
                  <span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.CRITICAL)}>
                    {scan.critical_count} CRITICAL
                  </span>
                )}
                {scan.high_count > 0 && (
                  <span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.HIGH)}>
                    {scan.high_count} HIGH
                  </span>
                )}
                {scan.medium_count > 0 && (
                  <span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.MEDIUM)}>
                    {scan.medium_count} MEDIUM
                  </span>
                )}
                {scan.low_count > 0 && (
                  <span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.LOW)}>
                    {scan.low_count} LOW
                  </span>
                )}
                {scan.total_vulnerabilities === 0 && (
                  <span className="rounded border border-success/40 bg-success/10 text-success px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel">
                    No vulnerabilities
                  </span>
                )}
              </div>

              <div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
                <div>
                  <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Total</div>
                  <div className="font-mono tabular-nums text-stat-value">{scan.total_vulnerabilities}</div>
                </div>
                <div>
                  <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Fixable</div>
                  <div className="font-mono tabular-nums text-success">{scan.fixable_count}</div>
                </div>
                <div>
                  <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Triggered</div>
                  <div className="font-mono tabular-nums text-stat-value">{scan.triggered_by}</div>
                </div>
                <div>
                  <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Scanned</div>
                  <div className="font-mono tabular-nums text-stat-value">
                    {new Date(scan.scanned_at).toLocaleString()}
                  </div>
                </div>
              </div>

              {canGenerateSbom && (
                <div className="flex flex-wrap items-center gap-2 mt-3">
                  <DropdownMenu>
                    <DropdownMenuTrigger asChild>
                      <Button variant="outline" size="sm" disabled={downloadingSbom}>
                        {downloadingSbom ? (
                          <Loader2 className="w-3.5 h-3.5 mr-1.5 animate-spin" strokeWidth={1.5} />
                        ) : (
                          <Download className="w-3.5 h-3.5 mr-1.5" strokeWidth={1.5} />
                        )}
                        SBOM
                      </Button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent align="end">
                      <DropdownMenuItem onClick={() => downloadSbom('spdx-json')}>
                        SPDX JSON
                      </DropdownMenuItem>
                      <DropdownMenuItem onClick={() => downloadSbom('cyclonedx')}>
                        CycloneDX
                      </DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </div>
              )}

              {compareOpen && canCompare && (
                <div className="pt-3 space-y-2">
                  {compareOptions.length === 0 && !compareLoading ? (
                    <div className="rounded border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
                      No other completed scans for this image yet. Run a second scan to enable comparison.
                    </div>
                  ) : (
                    <>
                      <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
                        Compare against
                      </div>
                      <Combobox
                        options={compareOptions.map((s) => ({
                          value: String(s.id),
                          label: `${new Date(s.scanned_at).toLocaleString()} - ${s.total_vulnerabilities} findings (${s.triggered_by})`,
                        }))}
                        value={compareBaselineId != null ? String(compareBaselineId) : ''}
                        onValueChange={(v) => setCompareBaselineId(v ? Number(v) : null)}
                        placeholder="Choose a baseline scan..."
                        searchPlaceholder="Search by date..."
                      />
                    </>
                  )}
                </div>
              )}
            </SheetSection>

            {tab === 'vulns' && (
              <SheetSection title={`Vulnerabilities · ${totalDetails}`}>
                <div className="flex items-center gap-1 flex-wrap mb-3">
                  {(['ALL', 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] as SeverityFilter[]).map((s) => (
                    <Button
                      key={s}
                      variant={severityFilter === s ? 'default' : 'ghost'}
                      size="sm"
                      className="h-7 text-xs px-2.5"
                      onClick={() => {
                        setSeverityFilter(s);
                        setPage(0);
                      }}
                    >
                      {s}
                    </Button>
                  ))}
                  {needsPagination && (
                    <div className="flex items-center gap-1 ml-auto">
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() => setPage(Math.max(0, safePage - 1))}
                        disabled={safePage === 0}
                      >
                        <ChevronLeft className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                      <span className="text-xs font-mono tabular-nums text-stat-subtitle min-w-[3rem] text-center">
                        {safePage + 1} / {totalPages}
                      </span>
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() => setPage(Math.min(totalPages - 1, safePage + 1))}
                        disabled={safePage >= totalPages - 1}
                      >
                        <ChevronRight className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                    </div>
                  )}
                </div>

                {totalDetails > details.length && (
                  <div className="text-xs text-stat-subtitle font-mono mb-2">
                    Showing first {details.length} of {totalDetails}. Export CSV for the complete list.
                  </div>
                )}

                <ScrollArea block className="max-h-[60vh]">
                  {pageItems.length === 0 ? (
                    <div className="text-center text-sm text-muted-foreground py-12">
                      {details.length === 0
                        ? 'No vulnerabilities found.'
                        : 'No vulnerabilities match the selected filter.'}
                    </div>
                  ) : (
                    <Table>
                      <TableHeader>
                        <TableRow>
                          <TableHead className="w-[180px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">CVE</TableHead>
                          <TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Package</TableHead>
                          <TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
                          <TableHead className="w-[110px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Installed</TableHead>
                          <TableHead className="w-[110px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Fixed</TableHead>
                          {canManageSuppressions && <TableHead className="w-[40px]" />}
                        </TableRow>
                      </TableHeader>
                      <TableBody>
                        {pageItems.map((d) => {
                          const href = cveUrl(d.vulnerability_id, d.primary_url);
                          return (
                            <TableRow
                              key={d.id}
                              className={cn(SEVERITY_ROW_TINT[d.severity], d.suppressed && 'opacity-60')}
                            >
                              <TableCell className="font-mono text-xs tabular-nums">
                                <span className="inline-flex items-center gap-1.5">
                                  {d.suppressed && (
                                    <ShieldOff
                                      className="w-3 h-3 text-muted-foreground"
                                      strokeWidth={1.5}
                                      aria-label="Suppressed"
                                    />
                                  )}
                                  {href ? (
                                    <a
                                      href={href}
                                      target="_blank"
                                      rel="noreferrer noopener"
                                      className="inline-flex items-center gap-1 hover:underline"
                                    >
                                      {d.vulnerability_id}
                                      <ExternalLink className="w-3 h-3" strokeWidth={1.5} />
                                    </a>
                                  ) : (
                                    d.vulnerability_id
                                  )}
                                </span>
                              </TableCell>
                              <TableCell
                                className="font-mono text-xs truncate max-w-[180px]"
                                title={d.suppression_reason || d.pkg_name}
                              >
                                {d.pkg_name}
                              </TableCell>
                              <TableCell>
                                <SeverityChip severity={d.severity} />
                              </TableCell>
                              <TableCell className="font-mono text-xs">{d.installed_version}</TableCell>
                              <TableCell className="font-mono text-xs">
                                {d.fixed_version ? (
                                  <span className="inline-flex items-center gap-1 text-success">
                                    <Check className="w-3 h-3" strokeWidth={1.5} />
                                    {d.fixed_version}
                                  </span>
                                ) : (
                                  <span className="text-muted-foreground">-</span>
                                )}
                              </TableCell>
                              {canManageSuppressions && (
                                <TableCell>
                                  {!d.suppressed && (
                                    <Button
                                      variant="ghost"
                                      size="icon"
                                      className="h-7 w-7 text-muted-foreground/70 hover:text-foreground"
                                      title="Suppress this CVE"
                                      onClick={() => openSuppressDialog(d)}
                                    >
                                      <ShieldOff className="w-3.5 h-3.5" strokeWidth={1.5} />
                                    </Button>
                                  )}
                                </TableCell>
                              )}
                            </TableRow>
                          );
                        })}
                      </TableBody>
                    </Table>
                  )}
                </ScrollArea>
              </SheetSection>
            )}

            {tab === 'secrets' && (
              <SheetSection title={`Secrets · ${scan.secret_count ?? secrets.length}`}>
                {secretsNeedsPagination && (
                  <div className="flex items-center gap-1 mb-3">
                    <div className="flex items-center gap-1 ml-auto">
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() => setSecretsPage(Math.max(0, secretsSafePage - 1))}
                        disabled={secretsSafePage === 0}
                      >
                        <ChevronLeft className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                      <span className="text-xs font-mono tabular-nums text-stat-subtitle min-w-[3rem] text-center">
                        {secretsSafePage + 1} / {secretsTotalPages}
                      </span>
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() =>
                          setSecretsPage(Math.min(secretsTotalPages - 1, secretsSafePage + 1))
                        }
                        disabled={secretsSafePage >= secretsTotalPages - 1}
                      >
                        <ChevronRight className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                    </div>
                  </div>
                )}
                <ScrollArea block className="max-h-[60vh]">
                  {secrets.length === 0 ? (
                    <div className="text-center text-sm text-muted-foreground py-12">
                      No secrets detected.
                    </div>
                  ) : (
                    <Table>
                      <TableHeader>
                        <TableRow>
                          <TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
                          <TableHead className="w-[160px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Rule</TableHead>
                          <TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Title</TableHead>
                          <TableHead className="w-[260px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Target</TableHead>
                        </TableRow>
                      </TableHeader>
                      <TableBody>
                        {secretsPageItems.map((s) => (
                          <TableRow key={s.id} className={SEVERITY_ROW_TINT[s.severity]}>
                            <TableCell>
                              <SeverityChip severity={s.severity} />
                            </TableCell>
                            <TableCell className="font-mono text-xs truncate max-w-[160px]" title={s.rule_id}>
                              {s.rule_id}
                            </TableCell>
                            <TableCell className="text-xs">
                              <div className="truncate max-w-[320px]" title={s.title ?? undefined}>
                                {s.title || <span className="text-muted-foreground">-</span>}
                              </div>
                              {s.match_excerpt && (
                                <div
                                  className="font-mono text-[11px] text-muted-foreground truncate max-w-[320px]"
                                  title={s.match_excerpt}
                                >
                                  {s.match_excerpt}
                                </div>
                              )}
                            </TableCell>
                            <TableCell className="font-mono text-xs truncate max-w-[260px]" title={s.target}>
                              {s.target}
                              {s.start_line != null && (
                                <span className="text-muted-foreground">
                                  :{s.start_line}
                                  {s.end_line != null && s.end_line !== s.start_line
                                    ? `-${s.end_line}`
                                    : ''}
                                </span>
                              )}
                            </TableCell>
                          </TableRow>
                        ))}
                      </TableBody>
                    </Table>
                  )}
                </ScrollArea>
              </SheetSection>
            )}

            {tab === 'misconfigs' && (
              <SheetSection title={`Misconfigs · ${scan.misconfig_count ?? misconfigs.length}`}>
                {misconfigsNeedsPagination && (
                  <div className="flex items-center gap-1 mb-3">
                    <div className="flex items-center gap-1 ml-auto">
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() => setMisconfigsPage(Math.max(0, misconfigsSafePage - 1))}
                        disabled={misconfigsSafePage === 0}
                      >
                        <ChevronLeft className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                      <span className="text-xs font-mono tabular-nums text-stat-subtitle min-w-[3rem] text-center">
                        {misconfigsSafePage + 1} / {misconfigsTotalPages}
                      </span>
                      <Button
                        variant="ghost"
                        size="icon"
                        className="h-6 w-6"
                        onClick={() =>
                          setMisconfigsPage(
                            Math.min(misconfigsTotalPages - 1, misconfigsSafePage + 1),
                          )
                        }
                        disabled={misconfigsSafePage >= misconfigsTotalPages - 1}
                      >
                        <ChevronRight className="w-4 h-4" strokeWidth={1.5} />
                      </Button>
                    </div>
                  </div>
                )}
                <ScrollArea block className="max-h-[60vh]">
                  {misconfigs.length === 0 ? (
                    <div className="text-center text-sm text-muted-foreground py-12">
                      No misconfigurations detected.
                    </div>
                  ) : (
                    <Table>
                      <TableHeader>
                        <TableRow>
                          <TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
                          <TableHead className="w-[140px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Check</TableHead>
                          <TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Title</TableHead>
                          <TableHead className="w-[200px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Target</TableHead>
                          <TableHead className="w-[220px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Fix</TableHead>
                          {canManageSuppressions && <TableHead className="w-[40px]" />}
                        </TableRow>
                      </TableHeader>
                      <TableBody>
                        {misconfigsPageItems.map((m) => (
                          <TableRow
                            key={m.id}
                            className={cn(
                              SEVERITY_ROW_TINT[m.severity],
                              m.acknowledged && 'opacity-60',
                            )}
                            title={
                              m.acknowledged
                                ? `Acknowledged: ${m.acknowledgement_reason ?? '(no reason)'}`
                                : undefined
                            }
                          >
                            <TableCell>
                              <SeverityChip severity={m.severity} />
                            </TableCell>
                            <TableCell
                              className="font-mono text-xs truncate max-w-[140px]"
                              title={m.check_id ?? m.rule_id}
                            >
                              {m.check_id || m.rule_id}
                            </TableCell>
                            <TableCell className={cn('text-xs', m.acknowledged && 'line-through')}>
                              <div className="truncate max-w-[320px]" title={m.title ?? undefined}>
                                {m.primary_url ? (
                                  <a
                                    href={m.primary_url}
                                    target="_blank"
                                    rel="noreferrer noopener"
                                    className="inline-flex items-center gap-1 hover:underline"
                                  >
                                    {m.title || m.rule_id}
                                    <ExternalLink className="w-3 h-3" strokeWidth={1.5} />
                                  </a>
                                ) : (
                                  m.title || m.rule_id
                                )}
                              </div>
                              {m.message && (
                                <div
                                  className="text-[11px] text-muted-foreground truncate max-w-[320px]"
                                  title={m.message}
                                >
                                  {m.message}
                                </div>
                              )}
                            </TableCell>
                            <TableCell className="font-mono text-xs truncate max-w-[200px]" title={m.target}>
                              {m.target}
                            </TableCell>
                            <TableCell className="text-xs truncate max-w-[220px]" title={m.resolution ?? undefined}>
                              {m.resolution || <span className="text-muted-foreground">-</span>}
                            </TableCell>
                            {canManageSuppressions && (
                              <TableCell>
                                {!m.acknowledged && (
                                  <Button
                                    variant="ghost"
                                    size="icon"
                                    className="h-7 w-7"
                                    onClick={() => openAckDialog(m)}
                                    title="Acknowledge this misconfiguration"
                                  >
                                    <ShieldCheck className="w-3.5 h-3.5" strokeWidth={1.5} />
                                  </Button>
                                )}
                              </TableCell>
                            )}
                          </TableRow>
                        ))}
                      </TableBody>
                    </Table>
                  )}
                </ScrollArea>
              </SheetSection>
            )}
          </>
        )}
      </SystemSheet>

      <ScanComparisonSheet
        baselineScanId={compareBaselineId}
        currentScanId={compareBaselineId != null ? scanId : null}
        onClose={() => setCompareBaselineId(null)}
      />

      <Dialog
        open={suppressForm !== null}
        onOpenChange={(open) => !open && setSuppressForm(null)}
      >
        <DialogContent className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle>Suppress CVE</DialogTitle>
            <DialogDescription className="sr-only">
              Accept this CVE as known-benign so it stops triggering alerts across the fleet.
            </DialogDescription>
          </DialogHeader>
          {suppressForm && (
            <div className="space-y-4 py-2">
              <div className="grid grid-cols-2 gap-3">
                <div className="space-y-1">
                  <Label className="text-xs uppercase tracking-wide text-stat-subtitle">CVE</Label>
                  <div className="font-mono text-sm">{suppressForm.cveId}</div>
                </div>
                <div className="space-y-1">
                  <Label className="text-xs uppercase tracking-wide text-stat-subtitle">Package</Label>
                  <div className="font-mono text-sm truncate" title={suppressForm.pkgName}>
                    {suppressForm.pkgName || '-'}
                  </div>
                </div>
              </div>
              <div className="space-y-2">
                <Label htmlFor="suppress-pattern">Image pattern (optional)</Label>
                <Input
                  id="suppress-pattern"
                  placeholder="e.g. registry.internal/* (leave blank for all images)"
                  value={suppressForm.imagePattern}
                  onChange={(e) =>
                    setSuppressForm((f) => (f ? { ...f, imagePattern: e.target.value } : f))
                  }
                />
                <p className="text-xs text-muted-foreground">
                  Glob pattern matched against the image reference. Leave blank to suppress this CVE on any image.
                </p>
              </div>
              <div className="space-y-2">
                <Label htmlFor="suppress-reason">Reason</Label>
                <textarea
                  id="suppress-reason"
                  className="flex min-h-[72px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
                  placeholder="Why is this CVE safe to accept?"
                  value={suppressForm.reason}
                  onChange={(e) =>
                    setSuppressForm((f) => (f ? { ...f, reason: e.target.value } : f))
                  }
                />
              </div>
              <div className="space-y-2">
                <Label htmlFor="suppress-expiry">Expires in (days, optional)</Label>
                <Input
                  id="suppress-expiry"
                  type="number"
                  min="1"
                  placeholder="Leave blank for no expiry"
                  value={suppressForm.expiresInDays}
                  onChange={(e) =>
                    setSuppressForm((f) => (f ? { ...f, expiresInDays: e.target.value } : f))
                  }
                />
              </div>
            </div>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setSuppressForm(null)} disabled={savingSuppression}>
              Cancel
            </Button>
            <Button onClick={submitSuppression} disabled={savingSuppression}>
              {savingSuppression ? 'Saving...' : 'Suppress'}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog
        open={ackForm !== null}
        onOpenChange={(open) => !open && setAckForm(null)}
      >
        <DialogContent className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle>Acknowledge misconfiguration</DialogTitle>
            <DialogDescription className="sr-only">
              Accept this misconfiguration as known-benign so it stops triggering alerts across the fleet.
            </DialogDescription>
          </DialogHeader>
          {ackForm && (
            <div className="space-y-4 py-2">
              <div className="space-y-1">
                <Label className="text-xs uppercase tracking-wide text-stat-subtitle">Rule</Label>
                <div className="font-mono text-sm">{ackForm.ruleId}</div>
              </div>
              <div className="space-y-2">
                <Label htmlFor="ack-pattern">Stack pattern (optional)</Label>
                <Input
                  id="ack-pattern"
                  placeholder="e.g. traefik or web-* (leave blank for all stacks)"
                  value={ackForm.stackPattern}
                  onChange={(e) =>
                    setAckForm((f) => (f ? { ...f, stackPattern: e.target.value } : f))
                  }
                />
                <p className="text-xs text-muted-foreground">
                  Glob pattern matched against the stack name. Prefilled with the current stack so this ack stays narrowest by default.
                </p>
              </div>
              <div className="space-y-2">
                <Label htmlFor="ack-reason">Reason</Label>
                <textarea
                  id="ack-reason"
                  className="flex min-h-[72px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
                  placeholder="Why is this misconfiguration safe to accept?"
                  value={ackForm.reason}
                  onChange={(e) =>
                    setAckForm((f) => (f ? { ...f, reason: e.target.value } : f))
                  }
                />
              </div>
              <div className="space-y-2">
                <Label htmlFor="ack-expiry">Expires in (days, optional)</Label>
                <Input
                  id="ack-expiry"
                  type="number"
                  min="1"
                  placeholder="Leave blank for no expiry"
                  value={ackForm.expiresInDays}
                  onChange={(e) =>
                    setAckForm((f) => (f ? { ...f, expiresInDays: e.target.value } : f))
                  }
                />
              </div>
            </div>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setAckForm(null)} disabled={savingAck}>
              Cancel
            </Button>
            <Button onClick={submitAcknowledgement} disabled={savingAck}>
              {savingAck ? 'Saving...' : 'Acknowledge'}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}

export { SeverityChip };
export type { VulnerabilityScanSheetProps };
