import { useCallback, useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import {
  AlertCircle,
  ArrowRight,
  CircleDashed,
  GitCompareArrows,
} from "lucide-react";
import { ApiError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { EmptyState } from "@/components/ui/empty-state";
import { ErrorState } from "@/components/ui/error-state";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/PageHeader";
import {
  BatchDiffView,
  BatchDiffViewSkeleton,
} from "@/components/BatchDiffView";
import { RemitDrawer } from "@/components/RemitDrawer";
import { useBatches } from "@/hooks/useBatches";
import { useBatchDiff } from "@/hooks/useBatchDiff";
import { useRemitDrawerUrlState } from "@/hooks/useRemitDrawerUrlState";
import type { BatchSummary as ApiBatchSummary } from "@/lib/api";
import { cn } from "@/lib/utils";

// ---------------------------------------------------------------------------
// Inline URL state — uses react-router's useSearchParams so the page
// participates in the standard routing lifecycle (MemoryRouter in
// tests, BrowserRouter in production). `replace: true` keeps the
// picker tweaks from polluting the back-button stack.
// ---------------------------------------------------------------------------

function readIdsFromParams(params: URLSearchParams): {
  a: string | null;
  b: string | null;
} {
  const a = params.get("a");
  const b = params.get("b");
  return {
    a: a && a.length > 0 ? a : null,
    b: b && b.length > 0 ? b : null,
  };
}

// ---------------------------------------------------------------------------
// Kind badge — small inline label so the operator can spot which batch
// is which at a glance. Re-themed for the dark surface; the
// data-testid contract from `BatchesList.KindBadge` is preserved
// (prefixed `diff-picker-` so the BatchDiff tests can still assert
// on it).
// ---------------------------------------------------------------------------

function KindBadge({ kind }: { kind: ApiBatchSummary["kind"] }) {
  const cls =
    kind === "837p"
      ? "text-sky-300 border-sky-400/30 bg-sky-400/10"
      : "text-amber-300 border-amber-400/30 bg-amber-400/10";
  return (
    <span
      data-testid={`diff-picker-kind-${kind}`}
      className={cn(
        "inline-flex items-center gap-1 rounded-full border px-1.5 py-px text-[9.5px] font-semibold uppercase tracking-[0.14em] font-mono",
        cls,
      )}
    >
      {kind}
    </span>
  );
}

/**
 * Side A / Side B picker card. Dark-theme chrome with a hairline-width
 * left accent line in the side color (electric blue for A, amber for B).
 * Excludes the value already chosen on the other side so the operator
 * can't pick the same batch twice.
 */
function BatchPicker({
  label,
  side,
  value,
  onChange,
  items,
  excludeId,
  testid,
}: {
  label: string;
  side: "A" | "B";
  value: string | null;
  onChange: (id: string) => void;
  items: ApiBatchSummary[];
  excludeId: string | null;
  testid: string;
}) {
  const visible = useMemo(
    () => items.filter((it) => it.id !== excludeId),
    [items, excludeId],
  );
  const selected = items.find((it) => it.id === value);
  // A is electric blue (project --accent). B is amber (project --warning).
  const accentClass =
    side === "A"
      ? "bg-accent"
      : "bg-[hsl(var(--warning))]";
  const sideBadgeClass =
    side === "A"
      ? "bg-accent/15 text-accent"
      : "bg-[hsl(var(--warning)/0.15)] text-[hsl(var(--warning))]";
  return (
    <div
      data-testid={testid}
      className="relative rounded-xl border border-border/60 bg-card/40 overflow-hidden"
    >
      {/* Left accent line — the only "color" element on the card, 2px
          wide so it reads as a marker, not decoration. */}
      <div
        aria-hidden
        className={cn("absolute left-0 top-4 bottom-4 w-px", accentClass)}
      />
      <div className="flex items-center justify-between px-4 pt-3 pb-2">
        <div className="flex items-center gap-2.5">
          <div
            aria-hidden
            className={cn(
              "h-5 w-5 rounded-md flex items-center justify-center mono font-semibold text-[11px]",
              sideBadgeClass,
            )}
          >
            {side}
          </div>
          <span className="mono text-[10.5px] uppercase tracking-[0.18em] font-semibold text-muted-foreground">
            {label}
          </span>
        </div>
        {selected ? (
          <span className="display mono tabular-nums text-[12.5px] text-foreground">
            {selected.claimCount}{" "}
            <span className="mono not-italic uppercase tracking-[0.14em] text-[10px] text-muted-foreground/60">
              claims
            </span>
          </span>
        ) : null}
      </div>
      <div className="px-4 pb-4">
        <Select value={value ?? ""} onValueChange={(v) => onChange(v)}>
          <SelectTrigger>
            {selected ? (
              <span className="flex items-center gap-2 truncate">
                <KindBadge kind={selected.kind} />
                <span className="font-mono num text-[12.5px]">{selected.id}</span>
                <span className="text-[12px] truncate text-muted-foreground">
                  · {selected.inputFilename}
                </span>
              </span>
            ) : (
              <SelectValue placeholder="Pick a batch…" />
            )}
          </SelectTrigger>
          <SelectContent>
            {visible.length === 0 ? (
              <div className="px-3 py-2 text-xs text-muted-foreground">
                No other batches available.
              </div>
            ) : (
              visible.map((b) => (
                <SelectItem
                  key={b.id}
                  value={b.id}
                  data-testid={`diff-picker-option-${b.id}`}
                >
                  <span className="flex items-center gap-2">
                    <KindBadge kind={b.kind} />
                    <span className="font-mono num text-[12px]">{b.id}</span>
                    <span className="text-muted-foreground text-[12px] truncate">
                      · {b.inputFilename}
                    </span>
                  </span>
                </SelectItem>
              ))
            )}
          </SelectContent>
        </Select>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Not-found branch — surfaces when the backend reports 404 for one of
// the picks. Most likely cause: a bookmark referencing a now-deleted
// batch, or another agent deleting between the picker load and the
// diff fetch.
// ---------------------------------------------------------------------------

function NotFoundState({
  a,
  b,
  onReset,
}: {
  a: string;
  b: string;
  onReset: () => void;
}) {
  return (
    <ErrorState
      message="One of these batches was not found."
      detail={`The backend couldn't find batch ${a} or ${b}. It may have been deleted between picker load and diff fetch.`}
      onRetry={onReset}
    />
  );
}

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

export function BatchDiff() {
  // URL → state via react-router. Keeps MemoryRouter (tests) and
  // BrowserRouter (production) symmetric: both update the
  // useSearchParams hook on every navigation.
  const [searchParams, setSearchParams] = useSearchParams();
  // SP21 Phase 4 Task 4.5: mount the RemitDrawer so a deep link to
  // /batch-diff?remit=REM-1 opens the drawer in-place. The BatchDiff
  // data model (BatchClaimDiffSummary) is a claim-level projection —
  // there are no per-remit IDs in the diff payload to wrap, so no
  // click-to-drill is wired here. The drawer still mounts so the
  // `?remit=` URL contract is honored when the user lands on this
  // page with the param set (e.g. from an external link).
  const { remitId, open, close } = useRemitDrawerUrlState();
  const { a, b } = useMemo(
    () => readIdsFromParams(searchParams),
    [searchParams],
  );

  const updateIds = useCallback(
    (next: { a: string | null; b: string | null }) => {
      const params = new URLSearchParams(searchParams);
      if (next.a === null) params.delete("a");
      else params.set("a", next.a);
      if (next.b === null) params.delete("b");
      else params.set("b", next.b);
      setSearchParams(params, { replace: true });
    },
    [searchParams, setSearchParams],
  );

  const setA = useCallback(
    (id: string) => updateIds({ a: id, b }),
    [b, updateIds],
  );
  const setB = useCallback(
    (id: string) => updateIds({ a, b: id }),
    [a, updateIds],
  );
  const reset = useCallback(() => updateIds({ a: null, b: null }), [updateIds]);

  const {
    data: batches,
    isLoading: loadingBatches,
    isError: batchesError,
    error: batchesErrorMsg,
    refetch: refetchBatches,
  } = useBatches(100);

  // Only fire the diff once both pickers have a value. The hook also
  // gates on this internally (defense-in-depth), so a half-picked URL
  // never hits the backend.
  const diff = useBatchDiff(a, b);
  const ready = a !== null && b !== null;

  // --- error / status derivation ------------------------------------
  const errKind: "network" | "not_found" | null = diff.error
    ? diff.error instanceof ApiError && diff.error.status === 404
      ? "not_found"
      : "network"
    : null;

  // The ghost watermark carries the picker state, scaled like the
  // other pages. When both picks are in: "A→B", otherwise the count
  // of available batches, otherwise "DIFF".
  const watermark = ready
    ? `${a}→${b}`
    : batches && batches.length > 0
      ? batches.length.toLocaleString()
      : "DIFF";

  // -----------------------------------------------------------------
  // Stagger choreography — hero lands first, picks at 140ms,
  // diff at 240ms, footer at 320ms. Total < 500ms.
  // -----------------------------------------------------------------
  const heroDelay = 0;
  const picksDelay = 140;
  const diffDelay = 240;
  const footerDelay = 320;

  // --- render -------------------------------------------------------
  return (
    <div
      className="space-y-6 lg:space-y-10 animate-fade-in"
      data-testid="batch-diff-page"
    >
      {/* =================================================================
          HERO — dark editorial header
          ================================================================= */}
      <section
        className="relative animate-fade-in overflow-hidden"
        style={{ animationDelay: `${heroDelay}ms` }}
      >
        <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="Batch diff · A vs B"
              title={
                <>
                  Compare{" "}
                  <span className="italic text-muted-foreground/85">
                    batches.
                  </span>
                </>
              }
              subtitle={
                <>
                  Pick two parsed batches — typically a submitted 837P and its
                  corrected follow-up — and see what was added, removed, or
                  changed between them.
                </>
              }
            />
          </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",
              ready
                ? "border-[hsl(var(--success)/0.4)] bg-[hsl(var(--success)/0.08)] text-[hsl(var(--success))]"
                : "border-border/60 bg-card/60 text-muted-foreground"
            )}
          >
            <span
              className={cn(
                "relative inline-flex h-1.5 w-1.5 rounded-full",
                ready ? "bg-[hsl(var(--success))]" : "bg-muted-foreground/60"
              )}
            >
              {ready ? (
                <span className="absolute inline-flex h-full w-full rounded-full bg-[hsl(var(--success))] opacity-60 animate-ping" />
              ) : null}
            </span>
            <GitCompareArrows className="h-3.5 w-3.5" strokeWidth={1.75} />
            {ready ? "Ready to diff" : "Awaiting picks"}
          </div>
        </div>

        {/* On the wire — small inline status row showing A → B,
            mirrors the watermark visually and tells the operator at a
            glance which picks are armed. */}
        <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>On the wire</span>
          <span
            className={cn(
              "display mono not-italic text-[14px]",
              a ? "text-accent" : "text-muted-foreground/40"
            )}
          >
            {a ? "A" : "—"}
          </span>
          <ArrowRight
            className="h-3.5 w-3.5"
            strokeWidth={1.5}
            style={{ color: "hsl(var(--muted-foreground) / 0.4)" }}
          />
          <span
            className={cn(
              "display mono not-italic text-[14px]",
              b ? "text-[hsl(var(--warning))]" : "text-muted-foreground/40"
            )}
          >
            {b ? "B" : "—"}
          </span>
          <span className="text-muted-foreground/30" aria-hidden>
            ·
          </span>
          <span>{ready ? "ready to diff" : "two picks needed"}</span>
        </div>
      </section>

      {/* =================================================================
          PICKS — single Card wrapping the two BatchPicker cards.
          Same chrome language as the rest of the app.
          ================================================================= */}
      <section
        aria-label="The picks"
        className="animate-fade-in-up"
        style={{ animationDelay: `${picksDelay}ms` }}
      >
        <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" />
                  The picks
                </div>
                <h2 className="display text-[26px] leading-[1.05] tracking-[-0.02em]">
                  One on each <span className="italic">side.</span>
                </h2>
              </div>
              <p className="text-[12.5px] text-muted-foreground/80 max-w-sm">
                A is the &ldquo;before&rdquo; — B is the &ldquo;after&rdquo;.
                A picked batch can&rsquo;t be picked again on the other side.
              </p>
            </div>

            <div className="pt-5 border-t border-border/40">
              {batchesError ? (
                <ErrorState
                  message="Couldn't load batches from the backend."
                  detail={
                    batchesErrorMsg instanceof Error
                      ? batchesErrorMsg.message
                      : String(batchesErrorMsg)
                  }
                  onRetry={() => refetchBatches()}
                />
              ) : (
                <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                  <BatchPicker
                    label="A · left"
                    side="A"
                    value={a}
                    onChange={setA}
                    items={batches ?? []}
                    excludeId={b}
                    testid="diff-picker-a"
                  />
                  <BatchPicker
                    label="B · right"
                    side="B"
                    value={b}
                    onChange={setB}
                    items={batches ?? []}
                    excludeId={a}
                    testid="diff-picker-b"
                  />
                </div>
              )}
            </div>
          </CardContent>
        </Card>
      </section>

      {/* =================================================================
          DIFF — single Card wrapping the BatchDiffView. Same chrome
          language as the rest of the app. Branches on `ready` to
          pick between the awaiting-picks empty state, the loading
          skeleton, the not-found error, the network error, and the
          actual diff view.
          ================================================================= */}
      <section
        aria-label="The diff"
        className="animate-fade-in-up"
        style={{ animationDelay: `${diffDelay}ms` }}
      >
        <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" />
                  The diff
                </div>
                <h2 className="display text-[26px] leading-[1.05] tracking-[-0.02em]">
                  What <span className="italic">moved.</span>
                </h2>
              </div>
              {ready ? (
                <p className="text-[12.5px] text-muted-foreground/80 max-w-sm">
                  Three buckets — added in B, removed from A, changed in place.
                  Unchanged claims are summarized but not listed.
                </p>
              ) : null}
            </div>

            <div className="pt-5 border-t border-border/40">
              {!ready ? (
                <div
                  className="rounded-md border border-dashed border-border/60 bg-card/40"
                  data-testid="batch-diff-awaiting"
                >
                  <EmptyState
                    icon={
                      <GitCompareArrows
                        className="h-4 w-4"
                        strokeWidth={1.5}
                      />
                    }
                    eyebrow="Batch diff · awaiting picks"
                    message="Choose one batch for A and one for B to compute the diff."
                  />
                </div>
              ) : loadingBatches ? (
                <BatchDiffViewSkeleton />
              ) : errKind === "not_found" ? (
                <NotFoundState a={a as string} b={b as string} onReset={reset} />
              ) : diff.isError ? (
                <ErrorState
                  message="Couldn't compute the diff between these two batches."
                  detail={
                    diff.error instanceof Error
                      ? diff.error.message
                      : String(diff.error)
                  }
                  onRetry={() => diff.refetch()}
                />
              ) : diff.isLoading || !diff.data ? (
                <BatchDiffViewSkeleton />
              ) : (
                <BatchDiffView data={diff.data} />
              )}
            </div>

            {/* Action row — refresh + clear, only when ready. Lives
                inside the Card so it sits at the bottom of the diff
                surface, not as a separate paper footer. */}
            {ready ? (
              <div className="pt-5 border-t border-border/40 flex items-center justify-between gap-3 flex-wrap">
                <div className="text-[12.5px] text-muted-foreground/80 min-w-0">
                  {diff.isFetching ? (
                    <span className="inline-flex items-center gap-1.5">
                      <CircleDashed
                        className="h-3 w-3 animate-spin"
                        strokeWidth={1.75}
                      />
                      Refreshing…
                    </span>
                  ) : (
                    <>
                      Comparing{" "}
                      <span className="display mono not-italic text-foreground">
                        {a}
                      </span>{" "}
                      with{" "}
                      <span className="display mono not-italic text-foreground">
                        {b}
                      </span>
                      .
                    </>
                  )}
                </div>
                <div className="flex items-center gap-2 flex-wrap">
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={() => diff.refetch()}
                    data-testid="diff-refresh-button"
                  >
                    Refresh
                  </Button>
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={reset}
                    data-testid="diff-clear-button"
                  >
                    <AlertCircle className="h-3.5 w-3.5 mr-1" strokeWidth={1.75} />
                    Clear picks
                  </Button>
                </div>
              </div>
            ) : null}
          </CardContent>
        </Card>
      </section>

      {/* =================================================================
          FOOTER — single hairline-separated status row. Replaces the
          warm-paper "End of sheet 01" treatment with a quiet
          instrument-style status line in the same rhythm as the
          other pages.
          ================================================================= */}
      <section
        aria-label="Sheet 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 · Batch diff
        </div>
        <div className="flex items-center gap-3 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60">
          <span>{ready ? "A vs B" : "awaiting picks"}</span>
          <span className="text-muted-foreground/30" aria-hidden>
            ·
          </span>
          <span>
            {batches?.length ?? 0} batch{(batches?.length ?? 0) === 1 ? "" : "es"}{" "}
            on file
          </span>
        </div>
        <div className="flex items-center gap-1.5 mono text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground/60">
          <span>refresh</span>
          <kbd className="rounded-sm border border-border/60 bg-card/40 px-1.5 py-0.5 not-italic text-foreground/80">
            R
          </kbd>
        </div>
      </section>

      {/* SP21 Phase 4 Task 4.5: RemitDrawer mount. There are no
          per-remit rows in the diff payload (the page is a
          claim-level projection), so this drawer is for deep-link
          support only — clicking a claim row does not open it.
          When the user lands on /batch-diff?remit=REM-1, the drawer
          opens in-place. The `remits` list is empty so j/k is a
          no-op while the drawer is open. */}
      <RemitDrawer
        remitId={remitId}
        remits={[]}
        onClose={close}
        onNavigate={open}
        onToggleHelp={() => {
          // BatchDiff has no cheatsheet surface; `?` is a no-op
          // here, but the prop is required by the drawer's contract.
        }}
      />
    </div>
  );
}
