import { useMemo, useRef, useState } from "react";
import {
  AlertTriangle,
  CheckCircle2,
  ChevronRight,
  FileText,
  Loader2,
  Upload as UploadIcon,
  XCircle,
} from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { api, type ParseProgress } from "@/lib/api";
import { fmt, toNum } from "@/lib/format";
import { useAppStore } from "@/store";
import { useParse } from "@/hooks/useParse";
import type {
  ClaimOutput,
  ClaimPayment,
  ParsedBatch,
  ParsedBatchKind,
  ServiceLine,
  ServicePayment,
} from "@/types";
import { cn } from "@/lib/utils";

// ---------------------------------------------------------------------------
// Streaming state — every claim that arrives from the backend accumulates
// in this list. The 837P and 835 cases are kept as separate slots so the UI
// can render the right "card" type for each.
// ---------------------------------------------------------------------------

type StreamedClaim837 = { kind: "837p"; data: ClaimOutput };
type StreamedClaim835 = { kind: "835"; data: ClaimPayment };
type StreamedClaim = StreamedClaim837 | StreamedClaim835;

interface StreamState {
  items: StreamedClaim[];
  expectedTotal: number | null;
  passed: number;
  failed: number;
}

const PAYERS_837 = [
  { value: "co_medicaid", label: "CO Medicaid" },
  { value: "generic_837p", label: "Generic 837P" },
];

const PAYERS_835 = [
  { value: "co_medicaid_835", label: "CO Medicaid" },
  { value: "generic_835", label: "Generic 835" },
];

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}

// ---------------------------------------------------------------------------
// Small reusable card bits
// ---------------------------------------------------------------------------

function ValidationDot({
  passed,
  hasWarnings,
}: {
  passed: boolean;
  hasWarnings?: boolean;
}) {
  if (passed) {
    return (
      <span
        className="inline-flex items-center gap-1.5 text-[11px] text-[hsl(var(--success))] font-medium"
        title="Validation passed"
      >
        <CheckCircle2 className="h-3.5 w-3.5" strokeWidth={1.75} />
        Passed
      </span>
    );
  }
  if (hasWarnings) {
    return (
      <span
        className="inline-flex items-center gap-1.5 text-[11px] text-[hsl(var(--warning))] font-medium"
        title="Validation passed with warnings"
      >
        <AlertTriangle className="h-3.5 w-3.5" strokeWidth={1.75} />
        Warnings
      </span>
    );
  }
  return (
    <span
      className="inline-flex items-center gap-1.5 text-[11px] text-destructive font-medium"
      title="Validation failed"
    >
      <XCircle className="h-3.5 w-3.5" strokeWidth={1.75} />
      Failed
    </span>
  );
}

function StatPill({
  label,
  value,
  mono = true,
}: {
  label: string;
  value: React.ReactNode;
  mono?: boolean;
}) {
  return (
    <div className="flex flex-col gap-0.5">
      <div className="text-[10.5px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
        {label}
      </div>
      <div
        className={cn(
          "text-[13px] text-foreground",
          mono && "display num"
        )}
      >
        {value}
      </div>
    </div>
  );
}

function ClaimCard837({ claim }: { claim: ClaimOutput }) {
  const [open, setOpen] = useState(false);
  const passed = claim.validation.passed;
  const hasWarnings = claim.validation.warnings.length > 0;
  return (
    <div className="surface rounded-lg overflow-hidden">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="w-full text-left px-4 py-3 hover:bg-muted/30 transition-colors"
        aria-expanded={open}
      >
        <div className="flex items-center gap-3">
          <ChevronRight
            className={cn(
              "h-3.5 w-3.5 text-muted-foreground transition-transform shrink-0",
              open && "rotate-90"
            )}
            strokeWidth={1.75}
          />
          <div className="flex-1 min-w-0">
            <div className="flex items-center gap-2.5 flex-wrap">
              <span className="display num text-[13px]">{claim.claim_id}</span>
              <span className="text-[12px] text-muted-foreground truncate">
                {claim.subscriber.first_name} {claim.subscriber.last_name}
              </span>
              <span className="text-[12px] text-muted-foreground">
                · {claim.payer.name}
              </span>
            </div>
            <div className="flex items-center gap-3 mt-1 text-[11px] text-muted-foreground num">
              <span>
                NPI {claim.billing_provider.npi}
              </span>
              <span>·</span>
              <span>
                {claim.service_lines.length} line
                {claim.service_lines.length === 1 ? "" : "s"}
              </span>
              <span>·</span>
              <span>
                {claim.diagnoses.length} dx
              </span>
            </div>
          </div>
          <div className="text-right shrink-0">
            <div className="display num text-[14px]">
              {fmt.usdDecimal(claim.claim.total_charge)}
            </div>
            <div className="mt-0.5">
              <ValidationDot passed={passed} hasWarnings={hasWarnings} />
            </div>
          </div>
        </div>
      </button>
      {open ? (
        <div className="border-t border-border/40 px-4 py-3 grid gap-4 bg-muted/20 animate-fade-in">
          <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
            <StatPill label="Member" value={claim.subscriber.member_id} />
            <StatPill
              label="Place of service"
              value={claim.claim.place_of_service ?? "—"}
            />
            <StatPill label="Frequency" value={claim.claim.frequency_code ?? "—"} />
            <StatPill
              label="Prior auth"
              value={claim.claim.prior_auth ?? "—"}
            />
          </div>

          {claim.diagnoses.length > 0 ? (
            <div>
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-1.5">
                Diagnoses
              </div>
              <div className="flex flex-wrap gap-1.5">
                {claim.diagnoses.map((d, i) => (
                  <Badge key={`${d.code}-${i}`} variant="muted">
                    {d.qualifier ? `${d.qualifier}·` : ""}
                    {d.code}
                  </Badge>
                ))}
              </div>
            </div>
          ) : null}

          {claim.service_lines.length > 0 ? (
            <div>
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-1.5">
                Service lines
              </div>
              <div className="rounded-md border border-border/40 overflow-x-auto">
                <table className="w-full text-[12px]">
                  <thead className="bg-muted/30">
                    <tr className="text-left text-muted-foreground">
                      <th className="px-2.5 py-1.5 font-medium">#</th>
                      <th className="px-2.5 py-1.5 font-medium">Code</th>
                      <th className="px-2.5 py-1.5 font-medium">Mods</th>
                      <th className="px-2.5 py-1.5 font-medium">Date</th>
                      <th className="px-2.5 py-1.5 font-medium">Units</th>
                      <th className="px-2.5 py-1.5 font-medium text-right">Charge</th>
                    </tr>
                  </thead>
                  <tbody>
                    {claim.service_lines.map((line) => (
                      <ServiceLine837Row key={line.line_number} line={line} />
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          ) : null}

          {(claim.validation.errors.length > 0 ||
            claim.validation.warnings.length > 0) ? (
            <div>
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-1.5">
                Validation
              </div>
              <ul className="space-y-1 text-[12px]">
                {claim.validation.errors.map((issue, i) => (
                  <li
                    key={`e-${i}`}
                    className="flex items-start gap-2 text-destructive"
                  >
                    <XCircle
                      className="h-3.5 w-3.5 mt-0.5 shrink-0"
                      strokeWidth={1.75}
                    />
                    <span>
                      <span className="num">{issue.rule}</span> — {issue.message}
                    </span>
                  </li>
                ))}
                {claim.validation.warnings.map((issue, i) => (
                  <li
                    key={`w-${i}`}
                    className="flex items-start gap-2 text-[hsl(var(--warning))]"
                  >
                    <AlertTriangle
                      className="h-3.5 w-3.5 mt-0.5 shrink-0"
                      strokeWidth={1.75}
                    />
                    <span>
                      <span className="num">{issue.rule}</span> — {issue.message}
                    </span>
                  </li>
                ))}
              </ul>
            </div>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}

function ServiceLine837Row({ line }: { line: ServiceLine }) {
  return (
    <tr className="border-t border-border/30">
      <td className="px-2.5 py-1.5 num">{line.line_number}</td>
      <td className="px-2.5 py-1.5 num">
        {line.procedure.qualifier}·{line.procedure.code}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {line.procedure.modifiers.length > 0
          ? line.procedure.modifiers.join(", ")
          : "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {line.service_date ?? "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {line.units ? `${toNum(line.units)} ${line.unit_type ?? ""}`.trim() : "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-right display">
        {fmt.usdDecimal(line.charge)}
      </td>
    </tr>
  );
}

function ClaimCard835({ claim }: { claim: ClaimPayment }) {
  const [open, setOpen] = useState(false);
  const passed = claim.service_payments.length > 0; // placeholder; the
  // 835 batch-level validation is on summary. We treat the per-card view as
  // "ok" if the claim parsed at all. The card-level dot still gives a visual
  // anchor; the summary toast shows the real pass/fail.
  return (
    <div className="surface rounded-lg overflow-hidden">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="w-full text-left px-4 py-3 hover:bg-muted/30 transition-colors"
        aria-expanded={open}
      >
        <div className="flex items-center gap-3">
          <ChevronRight
            className={cn(
              "h-3.5 w-3.5 text-muted-foreground transition-transform shrink-0",
              open && "rotate-90"
            )}
            strokeWidth={1.75}
          />
          <div className="flex-1 min-w-0">
            <div className="flex items-center gap-2.5 flex-wrap">
              <span className="display num text-[13px]">
                {claim.payer_claim_control_number}
              </span>
              <span className="text-[12px] text-muted-foreground">
                {claim.status_label ?? `Status ${claim.status_code}`}
              </span>
            </div>
            <div className="flex items-center gap-3 mt-1 text-[11px] text-muted-foreground num">
              <span>CLP {claim.status_code}</span>
              {claim.original_claim_id ? (
                <>
                  <span>·</span>
                  <span>Orig {claim.original_claim_id}</span>
                </>
              ) : null}
              <span>·</span>
              <span>
                {claim.service_payments.length} svc
              </span>
            </div>
          </div>
          <div className="text-right shrink-0">
            <div className="display num text-[14px] text-[hsl(var(--success))]">
              {fmt.usdDecimal(claim.total_paid)}
            </div>
            <div className="text-[11px] text-muted-foreground num">
              of {fmt.usdDecimal(claim.total_charge)}
            </div>
            <div className="mt-0.5">
              <ValidationDot passed={passed} />
            </div>
          </div>
        </div>
      </button>
      {open ? (
        <div className="border-t border-border/40 px-4 py-3 grid gap-4 bg-muted/20 animate-fade-in">
          <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
            <StatPill label="Filing" value={claim.claim_filing_indicator ?? "—"} />
            <StatPill label="Facility" value={claim.facility_type ?? "—"} />
            <StatPill label="Frequency" value={claim.frequency_code ?? "—"} />
            <StatPill
              label="Patient resp"
              value={
                claim.patient_responsibility
                  ? fmt.usdDecimal(claim.patient_responsibility)
                  : "—"
              }
            />
          </div>

          {claim.service_payments.length > 0 ? (
            <div>
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-1.5">
                Service payments
              </div>
              <div className="rounded-md border border-border/40 overflow-x-auto">
                <table className="w-full text-[12px]">
                  <thead className="bg-muted/30">
                    <tr className="text-left text-muted-foreground">
                      <th className="px-2.5 py-1.5 font-medium">#</th>
                      <th className="px-2.5 py-1.5 font-medium">Code</th>
                      <th className="px-2.5 py-1.5 font-medium">Mods</th>
                      <th className="px-2.5 py-1.5 font-medium">Date</th>
                      <th className="px-2.5 py-1.5 font-medium">Units</th>
                      <th className="px-2.5 py-1.5 font-medium text-right">Charge</th>
                      <th className="px-2.5 py-1.5 font-medium text-right">Paid</th>
                    </tr>
                  </thead>
                  <tbody>
                    {claim.service_payments.map((svc) => (
                      <ServicePaymentRow key={svc.line_number} svc={svc} />
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}

function ServicePaymentRow({ svc }: { svc: ServicePayment }) {
  return (
    <tr className="border-t border-border/30">
      <td className="px-2.5 py-1.5 num">{svc.line_number}</td>
      <td className="px-2.5 py-1.5 num">
        {svc.procedure_qualifier}·{svc.procedure_code}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {svc.modifiers.length > 0 ? svc.modifiers.join(", ") : "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {svc.service_date ?? "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-muted-foreground">
        {svc.units ? `${toNum(svc.units)} ${svc.unit_type ?? ""}`.trim() : "—"}
      </td>
      <td className="px-2.5 py-1.5 num text-right display">
        {fmt.usdDecimal(svc.charge)}
      </td>
      <td className="px-2.5 py-1.5 num text-right display text-[hsl(var(--success))]">
        {fmt.usdDecimal(svc.payment)}
      </td>
    </tr>
  );
}

// ---------------------------------------------------------------------------
// Page
// ---------------------------------------------------------------------------

export function Upload() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [file, setFile] = useState<File | null>(null);
  const [kind, setKind] = useState<ParsedBatchKind>("837p");
  const [payer, setPayer] = useState<string>(PAYERS_837[0]!.value);
  const [stream, setStream] = useState<StreamState>({
    items: [],
    expectedTotal: null,
    passed: 0,
    failed: 0,
  });
  const addParsedBatch = useAppStore((s) => s.addParsedBatch);
  const parsedBatches = useAppStore((s) => s.parsedBatches);
  // useParse wires the mutation; on success it invalidates every list query
  // (batches, claims, remittances, providers, activity) so the other pages
  // see the new data without manual refetching.
  const parseMutation = useParse(kind);

  const payerOptions = kind === "837p" ? PAYERS_837 : PAYERS_835;

  const expectedTotal = stream.expectedTotal;
  const totalSoFar = stream.items.length;
  const running = parseMutation.isPending;
  const progressPct = useMemo(() => {
    if (!expectedTotal || expectedTotal <= 0) return 0;
    return Math.min(100, Math.round((totalSoFar / expectedTotal) * 100));
  }, [expectedTotal, totalSoFar]);

  function pickFile(f: File | null) {
    setFile(f);
    setStream({ items: [], expectedTotal: null, passed: 0, failed: 0 });
  }

  async function onParse() {
    if (!file) return;
    if (!api.isConfigured) {
      toast.error(
        "Backend not configured. Set VITE_API_BASE_URL in .env.local to use the Upload page."
      );
      return;
    }
    setStream({ items: [], expectedTotal: null, passed: 0, failed: 0 });
    const items: StreamedClaim[] = [];
    const onProgress: ParseProgress = (event) => {
      if (event.type === "claim") {
        items.push({ kind: "837p", data: event.data });
        setStream((s) => ({
          ...s,
          items: [...items],
        }));
      } else if (event.type === "claim_payment") {
        items.push({ kind: "835", data: event.data });
        setStream((s) => ({
          ...s,
          items: [...items],
        }));
      } else if (event.type === "summary") {
        setStream((s) => ({
          ...s,
          expectedTotal: event.data.total_claims,
          passed: event.data.passed,
          failed: event.data.failed,
        }));
      }
      // financial_info / trace / payer / payee are accepted silently;
      // the page summary at the end reports the real pass/fail counts.
    };

    try {
      // mutateAsync triggers the mutation; the streaming callbacks above
      // populate `stream` as claims arrive, and the resolver returns the
      // final BatchSummary from the "summary" NDJSON line.
      const summary = await parseMutation.mutateAsync({
        file,
        options: { payer, onProgress },
      });

      // The final return value is a BatchSummary in the streaming case.
      const finalSummary =
        summary && typeof summary === "object" && "total_claims" in summary
          ? summary
          : null;

      if (finalSummary) {
        const id = `BATCH-${Date.now()}`;
        const claimIds = items
          .map((c) =>
            c.kind === "837p" ? c.data.claim_id : c.data.payer_claim_control_number
          )
          .filter(Boolean);
        const batch: ParsedBatch = {
          id,
          kind,
          inputFilename: file.name,
          parsedAt: new Date().toISOString(),
          claimCount: finalSummary.total_claims,
          passed: finalSummary.passed,
          failed: finalSummary.failed,
          claimIds,
          summary: finalSummary,
        };
        addParsedBatch(batch);
        toast.success(
          `Parsed ${finalSummary.total_claims} ${kind === "837p" ? "claims" : "payments"} · ${
            finalSummary.passed
          } passed · ${finalSummary.failed} failed`,
          { description: file.name }
        );
      }
    } catch (err) {
      toast.error(
        err instanceof Error ? err.message : "Failed to parse file"
      );
    }
  }

  function onDrop(e: React.DragEvent<HTMLDivElement>) {
    e.preventDefault();
    const f = e.dataTransfer.files?.[0];
    if (f) pickFile(f);
  }

  return (
    <div className="space-y-6 md:space-y-8 animate-fade-in">
      <header>
        <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-2 flex items-center gap-2">
          <span className="inline-block h-px w-6 bg-border" />
          Upload · EDI parser
        </div>
        <h1 className="text-[22px] sm:text-[26px] font-semibold tracking-tight">
          Parse an X12 file
        </h1>
        <p className="text-muted-foreground mt-1.5 text-[14px] max-w-2xl">
          Upload an 837P professional claim or 835 ERA remittance file. The
          parser streams claims back as they're produced so the UI updates
          in real time.
        </p>
      </header>

      <Card>
        <CardHeader className="pb-3">
          <CardTitle className="text-[14px] flex items-center gap-2">
            <UploadIcon className="h-3.5 w-3.5 text-muted-foreground" strokeWidth={1.75} />
            File
          </CardTitle>
          <CardDescription>
            .txt (X12 837P or 835). The file is sent to the FastAPI backend
            configured by <code className="text-[12px]">VITE_API_BASE_URL</code>.
          </CardDescription>
        </CardHeader>
        <CardContent className="pt-0 space-y-4">
          <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
            <div className="grid gap-1.5">
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
                Kind
              </div>
              <Select
                value={kind}
                onValueChange={(v) => {
                  const next = v as ParsedBatchKind;
                  setKind(next);
                  setPayer(
                    next === "837p" ? PAYERS_837[0]!.value : PAYERS_835[0]!.value
                  );
                }}
              >
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="837p">837P (Professional claim)</SelectItem>
                  <SelectItem value="835">835 (ERA remittance)</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="grid gap-1.5">
              <div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
                Payer config
              </div>
              <Select value={payer} onValueChange={setPayer}>
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {payerOptions.map((p) => (
                    <SelectItem key={p.value} value={p.value}>
                      {p.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          </div>

          <div
            onDragOver={(e) => e.preventDefault()}
            onDrop={onDrop}
            onClick={() => inputRef.current?.click()}
            role="button"
            tabIndex={0}
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") inputRef.current?.click();
            }}
            className={cn(
              "relative flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border/60 bg-muted/20 px-6 py-10 sm:py-8 min-h-[140px] sm:min-h-[120px] cursor-pointer transition-colors",
              "hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
            )}
          >
            <UploadIcon
              className="h-5 w-5 text-muted-foreground"
              strokeWidth={1.5}
            />
            {file ? (
              <div className="flex items-center gap-2 text-sm">
                <FileText
                  className="h-3.5 w-3.5 text-muted-foreground"
                  strokeWidth={1.75}
                />
                <span className="font-medium">{file.name}</span>
                <span className="text-muted-foreground num">
                  · {formatBytes(file.size)}
                </span>
              </div>
            ) : (
              <>
                <div className="text-sm font-medium">
                  Drop a file here, or click to choose
                </div>
                <div className="text-[12px] text-muted-foreground">
                  .txt — the X12 837/835 file as exported from your
                  clearinghouse
                </div>
              </>
            )}
            <input
              ref={inputRef}
              type="file"
              accept=".txt"
              className="sr-only"
              onChange={(e) => pickFile(e.target.files?.[0] ?? null)}
            />
          </div>

          <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
            <div className="text-[12px] text-muted-foreground">
              {api.isConfigured ? (
                <span className="inline-flex items-center gap-1.5">
                  <span className="h-1.5 w-1.5 rounded-full bg-[hsl(var(--success))]" />
                  Backend ready · {api.baseUrl}
                </span>
              ) : (
                <span className="inline-flex items-center gap-1.5 text-[hsl(var(--warning))]">
                  <AlertTriangle
                    className="h-3 w-3"
                    strokeWidth={1.75}
                  />
                  No backend configured — set VITE_API_BASE_URL to enable parsing
                </span>
              )}
            </div>
            <div className="flex items-center gap-2 flex-wrap">
              {file ? (
                <Button
                  variant="ghost"
                  onClick={() => pickFile(null)}
                  disabled={running}
                  className="min-h-[44px] sm:min-h-0"
                >
                  Clear
                </Button>
              ) : null}
              <Button
                onClick={onParse}
                disabled={!file || running}
                className="min-h-[44px] sm:min-h-0"
              >
                {running ? (
                  <>
                    <Loader2 className="h-3.5 w-3.5 animate-spin" />
                    Parsing…
                  </>
                ) : (
                  <>
                    <UploadIcon className="h-3.5 w-3.5" />
                    Parse
                  </>
                )}
              </Button>
            </div>
          </div>
        </CardContent>
      </Card>

      {(running || stream.items.length > 0) ? (
        <Card>
          <CardHeader className="pb-3">
            <div className="flex items-center justify-between gap-3">
              <div>
                <CardTitle className="text-[14px] flex items-center gap-2">
                  {running ? (
                    <Loader2
                      className="h-3.5 w-3.5 animate-spin text-muted-foreground"
                      strokeWidth={1.75}
                    />
                  ) : (
                    <CheckCircle2
                      className="h-3.5 w-3.5 text-[hsl(var(--success))]"
                      strokeWidth={1.75}
                    />
                  )}
                  {kind === "837p" ? "Claims" : "Claim payments"}
                </CardTitle>
                <p className="text-xs text-muted-foreground mt-1">
                  {totalSoFar} of{" "}
                  {expectedTotal ?? "?"} parsed
                  {stream.failed > 0
                    ? ` · ${stream.failed} failed validation`
                    : ""}
                </p>
              </div>
              <div className="text-right">
                <div className="display num text-[16px]">
                  {expectedTotal ? `${progressPct}%` : "…"}
                </div>
                <div className="text-[10.5px] uppercase tracking-[0.14em] text-muted-foreground">
                  progress
                </div>
              </div>
            </div>
            <div className="mt-3 h-1 w-full rounded-full bg-muted overflow-hidden">
              <div
                className="h-full bg-accent transition-[width] duration-200"
                style={{ width: `${progressPct}%` }}
              />
            </div>
          </CardHeader>
          <CardContent className="pt-0">
            <div className="max-h-[640px] overflow-y-auto -mx-2 px-2 space-y-2">
              {stream.items.length === 0 && running ? (
                <div className="text-center py-8 text-muted-foreground text-sm">
                  Waiting for first claim…
                </div>
              ) : null}
              {stream.items.map((item, i) =>
                item.kind === "837p" ? (
                  <ClaimCard837 key={`837-${item.data.claim_id}-${i}`} claim={item.data} />
                ) : (
                  <ClaimCard835
                    key={`835-${item.data.payer_claim_control_number}-${i}`}
                    claim={item.data}
                  />
                )
              )}
            </div>
          </CardContent>
        </Card>
      ) : null}

      {parsedBatches.length > 0 ? (
        <Card>
          <CardHeader className="pb-3">
            <CardTitle className="text-[14px]">Recent batches</CardTitle>
            <p className="text-xs text-muted-foreground mt-1">
              Parsed files, newest first. Click a row to revisit it (in a
              future view).
            </p>
          </CardHeader>
          <CardContent className="pt-0">
            <ul className="divide-y divide-border/40">
              {parsedBatches.map((b) => (
                <li
                  key={b.id}
                  className="flex items-center gap-3 py-3 first:pt-0 last:pb-0"
                >
                  <Badge variant={b.kind === "837p" ? "default" : "muted"}>
                    {b.kind === "837p" ? "837P" : "835"}
                  </Badge>
                  <div className="flex-1 min-w-0">
                    <div className="text-sm font-medium truncate">
                      {b.inputFilename}
                    </div>
                    <div className="text-[11px] text-muted-foreground num">
                      {fmt.dateShort(b.parsedAt)} · {b.claimCount} {b.kind === "837p" ? "claims" : "payments"}
                    </div>
                  </div>
                  <div className="text-right shrink-0">
                    <div className="display num text-[13px]">
                      {b.passed} / {b.claimCount}
                    </div>
                    <div className="text-[11px] text-muted-foreground">
                      passed
                    </div>
                  </div>
                </li>
              ))}
            </ul>
          </CardContent>
        </Card>
      ) : null}
    </div>
  );
}
