import { useCallback, useState } from "react";
import { CheckCircle2, Download, Mail, ShieldCheck } from "lucide-react";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Skeleton } from "@/components/ui/skeleton";
import { EmptyState } from "@/components/ui/empty-state";
import { ErrorState } from "@/components/ui/error-state";
import { Card, CardContent } from "@/components/ui/card";
import { KpiCard } from "@/components/KpiCard";
import { PageHeader } from "@/components/PageHeader";
import { KeyboardCheatsheet } from "@/components/KeyboardCheatsheet";
import { AckDrawer } from "@/components/AckDrawer";
import { Pagination } from "@/components/ui/pagination";
import { useAckDrawerUrlState } from "@/hooks/useAckDrawerUrlState";
import { useAcks } from "@/hooks/useAcks";
import { useTa1Acks } from "@/hooks/useTa1Acks";
import { useMergedTail } from "@/hooks/useMergedTail";
import { useTailStream } from "@/hooks/useTailStream";
import { useRowKeyboard } from "@/hooks/useRowKeyboard";
import { TailStatusPill } from "@/components/TailStatusPill";
import { api } from "@/lib/api";
import { fmt } from "@/lib/format";
import { cn } from "@/lib/utils";
import type { Ack, Ta1Ack } from "@/types";

const PAGE_SIZE = 50;

/**
 * 999 ACK register. The page reads the persisted 999 Implementation
 * Acknowledgments produced in response to 837P ingests (or parsed
 * from inbound 999 uploads) and exposes a single j/k-navigable
 * table. A click drills into the AckDrawer via `?ack=ID` URL state.
 *
 * Chrome language matches the rest of Cyclone: dark instrument
 * surface, PageHeader hero with a ghost editorial watermark,
 * KpiCard strip, single Card-wrapped register, hairline-separated
 * keyboard cheatsheet affordance. The page is *not* a paper
 * register — it sits next to Upload / Reconciliation as another
 * instrument view of the same data.
 */
export function Acks() {
  const [page, setPage] = useState(1);
  const { data, isLoading, isError, error, refetch } = useAcks({
    limit: PAGE_SIZE,
    offset: (page - 1) * PAGE_SIZE,
  });
  // SP25: subscribe to /api/acks/stream so new 999 acks (whether from
  // the SFTP poller or a manual upload) land in the table the moment
  // they hit the database. The status surfaces in the hero pill so the
  // operator can see whether the connection is healthy.
  const {
    status: tailStatus,
    lastEventAt: tailLastEventAt,
    forceReconnect,
  } = useTailStream("acks");
  // Merge the page's base list with the live-tail delta. The hook
  // dedupes by id (first write wins), so a snapshot replay on
  // reconnect can't double-count an existing row.
  const mergedItems = useMergedTail("acks", data?.items ?? []);
  const items = mergedItems;
  const totalCount = data?.total ?? 0;
  // Server-side aggregates — sum over the *full* ACK set, not just the
  // visible page. Without this, the KPI strip silently under-reports
  // once the row count exceeds PAGE_SIZE (the "silent failure" the
  // operator flagged on 2026-06-29 when 1056 acks showed as 100).
  const aggregates = data?.aggregates;
  // SP21 Phase 5 Task 5.3: drill-down from an acks row into
  // AckDrawer. The hook reads `?ack=` off `window.location.search`
  // so deep links restore the open ack on reload.
  const { ackId, open, close } = useAckDrawerUrlState();

  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
  const [helpOpen, setHelpOpen] = useState(false);

  const moveNext = useCallback(() => {
    setSelectedIndex((i) => {
      if (items.length === 0) return null;
      if (i === null) return 0;
      return (i + 1) % items.length;
    });
  }, [items.length]);

  const movePrev = useCallback(() => {
    setSelectedIndex((i) => {
      if (items.length === 0) return null;
      if (i === null) return items.length - 1;
      return (i - 1 + items.length) % items.length;
    });
  }, [items.length]);

  useRowKeyboard({
    enabled: !helpOpen && items.length > 0,
    onNext: moveNext,
    onPrev: movePrev,
    onClose: () => setHelpOpen(false),
    onToggleHelp: () => setHelpOpen((v) => !v),
  });

  // -----------------------------------------------------------------
  // Aggregate metrics for the KPI strip + hero copy.
  // Sourced from the server-side `aggregates` field so the KPI strip
  // reflects every persisted 999, not just the visible page. If
  // `aggregates` hasn't loaded yet (first page), fall back to summing
  // the visible items so the cards still display a meaningful value.
  // -----------------------------------------------------------------
  const totals = aggregates ?? {
    accepted: items.reduce((s, a) => s + a.acceptedCount, 0),
    rejected: items.reduce((s, a) => s + a.rejectedCount, 0),
    received: items.reduce((s, a) => s + a.receivedCount, 0),
  };
  const acceptRate =
    totals.received > 0
      ? Math.round((totals.accepted / totals.received) * 1000) / 10
      : 0;

  // The ghost watermark carries the on-file count, scaled like the
  // Dashboard/Upload/Reconciliation watermarks (clamp 72–140px, 4.5%
  // opacity, right-anchored). Use the server-reported total (not the
  // page length) so the watermark tells the truth when there are more
  // than PAGE_SIZE rows on file.
  const watermark = totalCount > 0 ? totalCount.toLocaleString() : "999";

  // Tone for the status pill: green when nothing rejected, amber when
  // the register holds a partial/rejected payload.
  // `anyRejected` is a "page" property, not a "totals" property — the
  // status pill summarizes the visible register, so it's OK to scope
  // it to the current page.
  const anyRejected = items.some(
    (a) => a.ackCode === "R" || a.ackCode === "E",
  );

  // -----------------------------------------------------------------
  // Stagger choreography — hero lands first, KPI strip at 140ms,
  // register at 240ms, footer at 320ms. Total < 500ms.
  // -----------------------------------------------------------------
  const heroDelay = 0;
  const kpiDelay = 140;
  const tableDelay = 240;
  const footerDelay = 320;

  return (
    <>
      <KeyboardCheatsheet
        open={helpOpen}
        onClose={() => setHelpOpen(false)}
      />
      {/* SP21 Phase 5 Task 5.3: AckDrawer mount. Row click drills
          into the matching ack; the drawer portals into document.body
          (Radix Dialog), so the surrounding dark surface stays put
          while the drawer is open. Deep links via /acks?ack=ID
          restore the open ack on reload. */}
      <AckDrawer ackId={ackId} onClose={close} />

      <div className="space-y-6 lg:space-y-10">
        {/* =================================================================
            HERO — dark editorial header
            ================================================================= */}
        <section
          className="relative animate-fade-in overflow-hidden"
          style={{ animationDelay: `${heroDelay}ms` }}
        >
          {/* Ghost editorial watermark — anchors the page's scale. */}
          <div
            aria-hidden="true"
            className="pointer-events-none select-none absolute -right-4 top-1/2 -translate-y-1/2 whitespace-nowrap display text-foreground"
            style={{
              fontSize: "clamp(72px, 12vw, 140px)",
              letterSpacing: "-0.04em",
              opacity: 0.045,
              lineHeight: 1,
            }}
          >
            {watermark}
          </div>

          <div className="relative z-10 flex items-end justify-between gap-4 sm:gap-6 flex-wrap">
            <div className="min-w-0">
              <PageHeader
                eyebrow={`999 ACKs · EDI reference · ${totalCount.toLocaleString()} on file`}
                title={
                  <>
                    Acknowledgments,{" "}
                    <span className="italic text-muted-foreground/85">
                      received.
                    </span>
                  </>
                }
                subtitle={
                  <>
                    999 Implementation Acknowledgments — generated in response
                    to 837P ingests, or parsed from inbound 999 uploads. Each
                    row below is a single transaction set with its accept /
                    reject counts and the original X12 to download.
                  </>
                }
              />
            </div>
            <div
              className={cn(
                "inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-[11px] mono uppercase tracking-[0.12em] backdrop-blur",
                anyRejected
                  ? "border-[hsl(var(--warning)/0.4)] bg-[hsl(var(--warning)/0.08)] text-[hsl(var(--warning))]"
                  : "border-[hsl(var(--success)/0.4)] bg-[hsl(var(--success)/0.08)] text-[hsl(var(--success))]"
              )}
            >
              <span
                className={cn(
                  "relative inline-flex h-1.5 w-1.5 rounded-full",
                  anyRejected
                    ? "bg-[hsl(var(--warning))]"
                    : "bg-[hsl(var(--success))]"
                )}
              >
                <span
                  className={cn(
                    "absolute inline-flex h-full w-full rounded-full opacity-60 animate-ping",
                    anyRejected
                      ? "bg-[hsl(var(--warning))]"
                      : "bg-[hsl(var(--success))]"
                  )}
                />
              </span>
              <ShieldCheck className="h-3.5 w-3.5" strokeWidth={1.75} />
              {anyRejected ? "Mixed envelope" : "Envelope accepted"}
            </div>
            {/* SP25: live-tail connection status. Sits next to the
                envelope-accepted pill so the operator can see at a
                glance whether the stream is healthy without having to
                open the drawer. Reconnect button only renders when
                the stream is in a visibly-failed state. */}
            <TailStatusPill
              status={tailStatus}
              lastEventAt={tailLastEventAt}
              onReconnect={forceReconnect}
            />
          </div>

          {/* Keyboard hint — same rhythm as Reconciliation, sits under
              the header so the j/k affordance reads on first scan. */}
          <div className="relative z-10 mt-5 flex items-center gap-3 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60">
            <span>Navigate</span>
            <kbd className="rounded-sm border border-border/60 bg-card/40 px-1.5 py-0.5 not-italic text-foreground/80">
              j
            </kbd>
            <span className="text-muted-foreground/40">/</span>
            <kbd className="rounded-sm border border-border/60 bg-card/40 px-1.5 py-0.5 not-italic text-foreground/80">
              k
            </kbd>
            <span className="text-muted-foreground/40">·</span>
            <span>Press</span>
            <kbd className="rounded-sm border border-border/60 bg-card/40 px-1.5 py-0.5 not-italic text-foreground/80">
              ?
            </kbd>
            <span>for shortcuts</span>
          </div>
        </section>

        {/* =================================================================
            KPI STRIP — four standard KpiCards, accent tokens instead of
            paper-tinted custom tiles. Each card carries one hairline-
            width left accent in its signal color.
            ================================================================= */}
        <section
          aria-label="Aggregate ACK metrics"
          className="grid gap-3.5 grid-cols-2 md:grid-cols-4 animate-fade-in-up"
          style={{ animationDelay: `${kpiDelay}ms` }}
        >
          <KpiCard
            label="Accepted"
            value={fmt.num(totals.accepted)}
            accent="success"
            hint="from the 999 envelope"
          />
          <KpiCard
            label="Rejected"
            value={fmt.num(totals.rejected)}
            accent="destructive"
            hint="reject segments in 999"
          />
          <KpiCard
            label="Received"
            value={fmt.num(totals.received)}
            accent="default"
            hint="total segments seen"
          />
          <KpiCard
            label="Accept rate"
            value={
              totals.received > 0 ? `${acceptRate.toFixed(1)}%` : "—"
            }
            accent={acceptRate >= 95 ? "success" : "warning"}
            hint="accepted / received"
          />
        </section>

        {/* =================================================================
            REGISTER — single Card wrapping the table. Same chrome
            language as the rest of the app: hairline header, eyebrow
            + display title + sidecar kbd hint, dark-tone table.
            ================================================================= */}
        <section
          aria-label="ACK register"
          className="animate-fade-in-up"
          style={{ animationDelay: `${tableDelay}ms` }}
        >
          <Card>
            <CardContent className="p-6 lg:p-7 space-y-5">
              {/* Section header */}
              <div className="flex items-end justify-between gap-6 flex-wrap">
                <div className="min-w-0">
                  <div className="eyebrow flex items-center gap-2 mb-2">
                    <span className="inline-block h-px w-6 bg-foreground/20" />
                    The register
                  </div>
                  <h2 className="display text-[26px] leading-[1.05] tracking-[-0.02em]">
                    All <span className="italic">999s</span>, newest first.
                  </h2>
                </div>
                <p className="text-[12.5px] text-muted-foreground/80 max-w-sm">
                  Each row is a single 999. The accept / reject / partial
                  code, the source batch it answers, and the original
                  transaction set to download.
                </p>
              </div>

              <div className="pt-5 border-t border-border/40">
                {isError ? (
                  <ErrorState
                    message="Couldn't load ACKs from the backend."
                    detail={error instanceof Error ? error.message : String(error)}
                    onRetry={() => refetch()}
                  />
                ) : isLoading ? (
                  <div className="rounded-md border border-border/60 bg-card/40 p-4 space-y-2">
                    {Array.from({ length: 5 }).map((_, i) => (
                      <Skeleton key={i} variant="row" />
                    ))}
                  </div>
                ) : items.length === 0 ? (
                  <div className="rounded-md border border-dashed border-border/60 bg-card/40">
                    <EmptyState
                      eyebrow="999 ACKs · awaiting first 837 with ack=true"
                      message="Upload an 837P with ?ack=true, or parse a 999 file directly to populate this list."
                    />
                  </div>
                ) : (
                  <div className="rounded-md border border-border/60 overflow-hidden bg-card/40">
                    <Table>
                      <TableHeader>
                        <TableRow>
                          <TableHead
                            className="w-12"
                            aria-label="Status"
                          />
                          <TableHead>ID</TableHead>
                          <TableHead>Source Batch</TableHead>
                          <TableHead className="text-right">
                            Accepted
                          </TableHead>
                          <TableHead className="text-right">
                            Rejected
                          </TableHead>
                          <TableHead className="text-right">
                            Received
                          </TableHead>
                          <TableHead>ACK Code</TableHead>
                          <TableHead>Parsed</TableHead>
                          <TableHead
                            className="w-20"
                            aria-label="Download"
                          />
                        </TableRow>
                      </TableHeader>
                      <TableBody>
                        {items.map((a, idx) => {
                          const isSelected = selectedIndex === idx;
                          return (
                            <TableRow
                              key={a.id}
                              data-row-index={idx}
                              data-state={isSelected ? "selected" : undefined}
                              aria-selected={isSelected}
                              onClick={() => open(String(a.id))}
                              className={cn(
                                "cursor-pointer",
                                isSelected &&
                                  "bg-accent/[0.06] shadow-[inset_2px_0_0_0_hsl(var(--accent))] hover:bg-accent/[0.06]"
                              )}
                            >
                              <TableCell>
                                <CheckCircle2
                                  className={cn(
                                    "h-3.5 w-3.5",
                                    a.ackCode === "A"
                                      ? "text-[hsl(var(--success))]"
                                      : a.ackCode === "R"
                                        ? "text-destructive"
                                        : "text-[hsl(var(--warning))]"
                                  )}
                                  strokeWidth={1.75}
                                  aria-hidden
                                />
                              </TableCell>
                              <TableCell className="display mono text-[13px] text-foreground">
                                {a.id}
                              </TableCell>
                              <TableCell className="font-mono text-[12px] text-muted-foreground truncate max-w-[180px]">
                                {a.patientControlNumber ? (
                                  <>
                                    <span className="text-foreground">
                                      {a.patientControlNumber}
                                    </span>
                                    <span className="text-muted-foreground/60">
                                      {" "}· {a.sourceBatchId}
                                    </span>
                                  </>
                                ) : (
                                  a.sourceBatchId
                                )}
                              </TableCell>
                              <TableCell className="text-right display mono text-[hsl(var(--success))]">
                                {a.acceptedCount}
                              </TableCell>
                              <TableCell className="text-right display mono text-destructive">
                                {a.rejectedCount}
                              </TableCell>
                              <TableCell className="text-right display mono text-muted-foreground">
                                {a.receivedCount}
                              </TableCell>
                              <TableCell>
                                <AckCodeBadge code={a.ackCode} />
                              </TableCell>
                              <TableCell className="mono text-[12.5px] text-muted-foreground/70">
                                {a.parsedAt ? fmt.dateShort(a.parsedAt) : "—"}
                              </TableCell>
                              <TableCell>
                                <DownloadButton
                                  id={a.id}
                                  sourceBatchId={a.sourceBatchId}
                                />
                              </TableCell>
                            </TableRow>
                          );
                        })}
                      </TableBody>
                    </Table>
                  </div>
                )}
                {totalCount > PAGE_SIZE ? (
                  <Pagination
                    page={page}
                    pageSize={PAGE_SIZE}
                    total={totalCount}
                    onPageChange={setPage}
                  />
                ) : null}
              </div>
            </CardContent>
          </Card>
        </section>

        {/* =================================================================
            TA1 ENVELOPE ACKS — per-interchange acknowledgments.
            A TA1 (X12 Interchange Acknowledgment) is one row per
            inbound ISA/IEA envelope — the lower-level sibling of the
            999 (per-batch). Colorado Medicaid's Gainwell MFT currently
            only ships 999s, but the section stays in the surface so
            the operator can see TA1s as soon as one shows up. Kept as
            a quieter, narrower section than the 999 register so the
            999s remain the page's primary instrument.
            ================================================================= */}
        <Ta1AcksSection />

        {/* =================================================================
            FOOTER — single hairline-separated status row. Replaces the
            warm-paper "End of register" treatment with a quiet
            instrument-style status line in the same rhythm as the
            other pages.
            ================================================================= */}
        <section
          aria-label="Register status"
          className="animate-fade-in-up flex items-center justify-between gap-4 flex-wrap pt-5 border-t border-border/40"
          style={{ animationDelay: `${footerDelay}ms` }}
        >
          <div className="flex items-center gap-2 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60">
            <span className="h-1.5 w-1.5 rounded-full bg-[hsl(var(--accent))]" />
            Cyclone · 999 ACKs
          </div>
          <div className="flex items-center gap-3 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60">
            <span>
              {totalCount.toLocaleString()}{" "}
              {totalCount === 1 ? "row" : "rows"} on file
            </span>
            <span className="text-muted-foreground/30" aria-hidden>
              ·
            </span>
            <span>
              accepted {fmt.num(totals.accepted)} · rejected{" "}
              {fmt.num(totals.rejected)}
            </span>
          </div>
          <button
            type="button"
            onClick={() => setHelpOpen(true)}
            className="inline-flex items-center gap-1.5 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60 hover:text-foreground transition-colors"
          >
            Shortcuts
            <kbd className="rounded-sm border border-border/60 bg-card/40 px-1.5 py-0.5 not-italic text-foreground/80">
              ?
            </kbd>
          </button>
        </section>
      </div>
    </>
  );
}

// ---------------------------------------------------------------------------
// AckCodeBadge — dark-theme pill for the 999 accept / reject / partial code.
// A → success, R → destructive, anything else (E / P) → warning.
// ---------------------------------------------------------------------------
function AckCodeBadge({ code }: { code: Ack["ackCode"] }) {
  const color =
    code === "A"
      ? {
          text: "hsl(var(--success))",
          bg: "hsl(var(--success) / 0.10)",
          border: "hsl(var(--success) / 0.30)",
        }
      : code === "R"
        ? {
            text: "hsl(var(--destructive))",
            bg: "hsl(var(--destructive) / 0.10)",
            border: "hsl(var(--destructive) / 0.30)",
          }
        : {
            text: "hsl(var(--warning))",
            bg: "hsl(var(--warning) / 0.10)",
            border: "hsl(var(--warning) / 0.30)",
          };
  return (
    <span
      className="inline-flex items-center gap-1 rounded-sm border px-2 py-0.5 mono text-[10.5px] font-semibold uppercase tracking-[0.14em]"
      style={{
        color: color.text,
        backgroundColor: color.bg,
        borderColor: color.border,
      }}
    >
      {code}
    </span>
  );
}

// ---------------------------------------------------------------------------
// DownloadButton — fetches the raw 999 via api.getAck and streams it to
// disk as a text blob. Styled to match the rest of the dark surface.
// ---------------------------------------------------------------------------
function DownloadButton({
  id,
  sourceBatchId,
}: {
  id: number;
  sourceBatchId: string;
}) {
  const [busy, setBusy] = useState(false);
  const onClick = async () => {
    if (busy) return;
    setBusy(true);
    try {
      const detail = await api.getAck(id);
      const raw =
        (detail as unknown as { raw_999_text?: string }).raw_999_text ?? "";
      if (raw) {
        downloadBlob(`ack-${sourceBatchId}.999`, raw);
      }
    } catch (err) {
      console.error("download 999 failed", err);
    } finally {
      setBusy(false);
    }
  };
  return (
    <button
      type="button"
      onClick={(e) => {
        // The row's onClick drills into AckDrawer; this button is a
        // nested control and we want the click to NOT bubble into the
        // row's onClick handler (matches the precedent set by
        // DrillableCell onClick in `src/components/drill/DrillableCell.tsx:39`).
        e.stopPropagation();
        onClick();
      }}
      disabled={busy}
      aria-label="Download 999"
      className={cn(
        "inline-flex items-center gap-1.5 rounded-sm border border-border/60 bg-card/40 px-2 py-1 mono text-[10.5px] uppercase tracking-[0.14em] font-semibold text-muted-foreground hover:text-foreground hover:bg-card/80 transition-colors",
        busy && "opacity-50 cursor-not-allowed"
      )}
    >
      <Download className="h-3 w-3" strokeWidth={1.75} />
      999
    </button>
  );
}

function downloadBlob(filename: string, content: string) {
  const blob = new Blob([content], { type: "text/plain" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

// ---------------------------------------------------------------------------
// Ta1AcksSection — per-interchange TA1 register.
//
// Deliberately quieter than the 999 register above: smaller KPI strip,
// single-card table, and an empty state that explains the
// Colorado-specific context (TA1s aren't shipped today, so the
// section is empty unless Gainwell starts sending them).
// ---------------------------------------------------------------------------
function Ta1AcksSection() {
  const { data, isLoading, isError, error, refetch } = useTa1Acks({ limit: 50 });
  // SP25: same live-tail triplet as the 999 register above, just
  // bound to /api/ta1-acks/stream and the ta1Acks store slice.
  const {
    status: tailStatus,
    lastEventAt: tailLastEventAt,
    forceReconnect,
  } = useTailStream("ta1_acks");
  const items = useMergedTail("ta1_acks", data?.items ?? []);

  const totals = items.reduce(
    (acc, t) => {
      acc[t.ackCode] = (acc[t.ackCode] ?? 0) + 1;
      return acc;
    },
    {} as Record<string, number>,
  );

  return (
    <section
      aria-label="TA1 envelope acknowledgments"
      className="animate-fade-in-up"
    >
      <Card>
        <CardContent className="p-6 lg:p-7 space-y-5">
          <div className="flex items-end justify-between gap-6 flex-wrap">
            <div className="min-w-0">
              <div className="eyebrow flex items-center gap-2 mb-2">
                <span className="inline-block h-px w-6 bg-foreground/20" />
                Envelope acks
              </div>
              <h2 className="display text-[22px] leading-[1.05] tracking-[-0.02em]">
                TA1 <span className="italic">envelopes</span>, newest first.
              </h2>
            </div>
            <div className="flex items-center gap-3 flex-wrap">
              <p className="text-[12.5px] text-muted-foreground/80 max-w-sm">
                One row per inbound ISA/IEA interchange — the
                envelope-level sibling of the 999. Gainwell's Colorado
                MFT does not ship TA1s today; this section surfaces them
                when they appear.
              </p>
              {/* SP25: live-tail status for the TA1 stream. Quietly
                  placed after the description so the eyebrow / display
                  title remains the dominant scan target. */}
              <TailStatusPill
                status={tailStatus}
                lastEventAt={tailLastEventAt}
                onReconnect={forceReconnect}
              />
            </div>
          </div>

          <div className="grid gap-3 grid-cols-2 md:grid-cols-4 pt-2">
            <KpiCard
              label="On file"
              value={fmt.num(items.length)}
              accent="default"
              hint="persisted TA1s"
            />
            <KpiCard
              label="Accepted"
              value={fmt.num(totals.A ?? 0)}
              accent="success"
              hint="ack_code = A"
            />
            <KpiCard
              label="Envelope errors"
              value={fmt.num(totals.E ?? 0)}
              accent="warning"
              hint="ack_code = E"
            />
            <KpiCard
              label="Rejected"
              value={fmt.num(totals.R ?? 0)}
              accent="destructive"
              hint="ack_code = R"
            />
          </div>

          <div className="pt-5 border-t border-border/40">
            {isError ? (
              <ErrorState
                message="Couldn't load TA1 acks from the backend."
                detail={error instanceof Error ? error.message : String(error)}
                onRetry={() => refetch()}
              />
            ) : isLoading ? (
              <div className="rounded-md border border-border/60 bg-card/40 p-4 space-y-2">
                {Array.from({ length: 3 }).map((_, i) => (
                  <Skeleton key={i} variant="row" />
                ))}
              </div>
            ) : items.length === 0 ? (
              <div className="rounded-md border border-dashed border-border/60 bg-card/40">
                <EmptyState
                  eyebrow="TA1 envelopes · none on file"
                  message="Gainwell's Colorado MFT does not ship TA1 files today. When one does arrive (or you upload one), it will land here."
                />
              </div>
            ) : (
              <div className="rounded-md border border-border/60 overflow-hidden bg-card/40">
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead className="w-10" aria-label="Status" />
                      <TableHead>Control #</TableHead>
                      <TableHead>Ack</TableHead>
                      <TableHead>Note</TableHead>
                      <TableHead>Interchange date</TableHead>
                      <TableHead>Sender → Receiver</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {items.map((t) => (
                      <TableRow key={t.id}>
                        <TableCell>
                          <Mail
                            className={cn(
                              "h-3.5 w-3.5",
                              t.ackCode === "A"
                                ? "text-[hsl(var(--success))]"
                                : t.ackCode === "R"
                                  ? "text-destructive"
                                  : "text-[hsl(var(--warning))]",
                            )}
                            strokeWidth={1.75}
                            aria-hidden
                          />
                        </TableCell>
                        <TableCell className="display mono text-[13px] text-foreground">
                          {t.controlNumber || "—"}
                        </TableCell>
                        <TableCell>
                          <Ta1CodeBadge code={t.ackCode} />
                        </TableCell>
                        <TableCell className="mono text-[12px] text-muted-foreground">
                          {t.noteCode ?? "—"}
                        </TableCell>
                        <TableCell className="mono text-[12.5px] text-muted-foreground/70">
                          {t.interchangeDate ? fmt.dateShort(t.interchangeDate) : "—"}
                          {t.interchangeTime ? (
                            <span className="text-muted-foreground/50"> · {t.interchangeTime}</span>
                          ) : null}
                        </TableCell>
                        <TableCell className="mono text-[12px] text-muted-foreground truncate max-w-[260px]">
                          {t.senderId ?? "?"} → {t.receiverId ?? "?"}
                        </TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </div>
            )}
          </div>
        </CardContent>
      </Card>
    </section>
  );
}

// ---------------------------------------------------------------------------
// Ta1CodeBadge — same A/E/R → success/warning/destructive mapping as the
// 999 AckCodeBadge above. Kept as a separate component so the TA1
// surface can evolve independently (e.g. add a tooltip explaining the
// X12 005010X231A1 note code table) without churning the 999 surface.
// ---------------------------------------------------------------------------
function Ta1CodeBadge({ code }: { code: Ta1Ack["ackCode"] }) {
  const color =
    code === "A"
      ? {
          text: "hsl(var(--success))",
          bg: "hsl(var(--success) / 0.10)",
          border: "hsl(var(--success) / 0.30)",
        }
      : code === "R"
        ? {
            text: "hsl(var(--destructive))",
            bg: "hsl(var(--destructive) / 0.10)",
            border: "hsl(var(--destructive) / 0.30)",
          }
        : {
            text: "hsl(var(--warning))",
            bg: "hsl(var(--warning) / 0.10)",
            border: "hsl(var(--warning) / 0.30)",
          };
  return (
    <span
      className="inline-flex items-center gap-1 rounded-sm border px-2 py-0.5 mono text-[10.5px] font-semibold uppercase tracking-[0.14em]"
      style={{
        color: color.text,
        backgroundColor: color.bg,
        borderColor: color.border,
      }}
    >
      {code}
    </span>
  );
}
