import { useState } from "react";
import { ArrowRight, Link2 } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useAckClaims } from "@/hooks/useAckClaims";
import { useMergedTail } from "@/hooks/useMergedTail";
import { useTailStream } from "@/hooks/useTailStream";
import { api } from "@/lib/api";
import { ClaimStateBadge } from "@/components/ui/claim-state-badge";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { fmt } from "@/lib/format";
import type {
  ClaimAck,
  ClaimAckKind,
  ClaimState,
} from "@/types";

/**
 * Local UI state for the manual-match dropdown. The dropdown fetches
 * the bare `ClaimSummary` projection from the backend (which lists
 * every claim by patient_control_number-ish score) — for the first
 * cut, the dropdown just lists the persisted claim population by id
 * with a typed filter. The backend's actual candidate-list logic is
 * out of scope for the SP28 frontend (mirrors the
 * `/api/inbox/ack-orphans?kind=…` candidate list shape — D7).
 */
type CandidateClaim = {
  id: string;
  patientName: string;
  billedAmount: number;
  state: ClaimState;
};

/**
 * Per-AK2 / per-ClaimStatus card. For 999/277CA rows, renders the
 * linked claim (PCN, current state badge, charge, and a link to
 * ClaimDrawer). For TA1 rows with `claimId === null`, renders the
 * originating Batch instead.
 */
function ClaimLinkCard({
  link,
  onOpenClaim,
}: {
  link: ClaimAck;
  onOpenClaim: (claimId: string) => void;
}) {
  if (link.claimId === null) {
    // TA1 batch-level link — render the originating Batch id instead
    // of a claim (D4). The `batchId` field is populated for TA1 rows
    // only; other kinds leave it null.
    return (
      <div
        className="flex flex-col gap-2 rounded-md border px-3 py-2.5"
        style={{
          backgroundColor: "hsl(var(--card) / 0.40)",
          borderColor: "hsl(var(--border) / 0.5)",
        }}
        data-testid="matched-claim-card-ta1"
      >
        <div className="flex items-center gap-2">
          <span className="eyebrow text-[10px] text-muted-foreground/70">
            Originating batch
          </span>
          {link.ak2Index !== null ? (
            <span className="mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground/60">
              AK2 #{link.ak2Index}
            </span>
          ) : null}
        </div>
        <div
          className="mono text-[12.5px] text-foreground"
          data-testid="matched-claim-batch-id"
        >
          {link.batchId ?? "—"}
        </div>
      </div>
    );
  }

  return (
    <div
      className="flex flex-col gap-2 rounded-md border px-3 py-2.5"
      style={{
        backgroundColor: "hsl(var(--card) / 0.40)",
        borderColor: "hsl(var(--border) / 0.5)",
      }}
      data-testid="matched-claim-card"
    >
      <div className="flex items-center gap-2 flex-wrap">
        <span
          className="mono inline-flex items-center rounded-sm border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em]"
          style={{
            color: "hsl(var(--muted-foreground))",
            backgroundColor: "hsl(var(--muted) / 0.30)",
            borderColor: "hsl(var(--border) / 0.5)",
          }}
          data-testid="matched-claim-set-control"
          title={`set_control_number: ${link.setControlNumber ?? "—"}`}
        >
          {link.setControlNumber ?? "—"}
        </span>
        {link.ak2Index !== null ? (
          <span
            className="mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground/60"
            title="AK2 set-response index"
          >
            AK2 #{link.ak2Index}
          </span>
        ) : null}
        {link.setAcceptRejectCode ? (
          <span
            className="mono inline-flex items-center rounded-sm border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em]"
            style={{
              color: "hsl(var(--muted-foreground))",
              backgroundColor: "hsl(var(--muted) / 0.30)",
              borderColor: "hsl(var(--border) / 0.5)",
            }}
          >
            {link.setAcceptRejectCode}
          </span>
        ) : null}
      </div>

      <div className="flex items-center gap-2 flex-wrap">
        <span
          className="mono truncate text-[12.5px] text-foreground"
          data-testid="matched-claim-id"
        >
          {link.claimId}
        </span>
        {link.claimState && link.claimState !== "n/a" ? (
          <ClaimStateBadge
            state={link.claimState as ClaimState}
            data-testid="matched-claim-state-badge"
          />
        ) : null}
      </div>

      <div className="flex items-center gap-2">
        <button
          type="button"
          onClick={() => onOpenClaim(link.claimId as string)}
          className={cn(
            "inline-flex items-center gap-1 rounded-sm border px-2 py-1 text-[10.5px] mono font-semibold uppercase tracking-[0.14em] transition-colors",
            "border-border/60 bg-card/40 text-muted-foreground hover:bg-card/80 hover:text-foreground",
          )}
          data-testid="open-claim-drawer"
          aria-label={`Open claim ${link.claimId}`}
        >
          open claim
          <ArrowRight className="h-3 w-3" strokeWidth={1.75} aria-hidden />
        </button>
        {link.linkedAt ? (
          <span className="ml-auto mono text-[10.5px] text-muted-foreground/60">
            linked {fmt.dateShort(link.linkedAt)}
          </span>
        ) : null}
      </div>
    </div>
  );
}

/**
 * Inline "Link to claim…" affordance for the orphan case. Per D5/D9,
 * any logged-in user can run the manual-match endpoint — the dropdown
 * doesn't gate on role. On success the panel refetches and swaps to
 * the populated card view (live-tail also delivers the new link).
 */
function LinkToClaimDropdown({
  kind,
  ackId,
  onLinked,
}: {
  kind: ClaimAckKind;
  ackId: number;
  onLinked: () => void;
}) {
  const [open, setOpen] = useState(false);
  const [candidates, setCandidates] = useState<CandidateClaim[]>([]);
  const [loading, setLoading] = useState(false);
  const [submitting, setSubmitting] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  async function loadCandidates() {
    setLoading(true);
    setError(null);
    try {
      // Use the existing listClaims endpoint as a coarse candidate
      // pool. The backend's full /api/inbox/ack-orphans candidate
      // scoring is a separate concern — for the dropdown we just need
      // a typed claim id picker. The match is performed by the
      // backend's `POST /api/acks/{kind}/{id}/match-claim` endpoint.
      const res = await api.listClaims({ limit: 25 });
      setCandidates(
        res.items.map((c) => ({
          id: (c as unknown as { id: string }).id,
          patientName:
            (c as unknown as { patientName?: string }).patientName ?? "",
          billedAmount:
            (c as unknown as { billedAmount?: number }).billedAmount ?? 0,
          state:
            (c as unknown as { state?: ClaimState }).state ?? "submitted",
        })),
      );
      setOpen(true);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setLoading(false);
    }
  }

  async function link(claimId: string) {
    setSubmitting(claimId);
    setError(null);
    try {
      await api.matchAckToClaim(kind, ackId, claimId);
      setOpen(false);
      onLinked();
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setSubmitting(null);
    }
  }

  return (
    <div className="flex flex-col gap-2" data-testid="link-to-claim-dropdown">
      {!open ? (
        <Button
          variant="outline"
          size="sm"
          onClick={() => void loadCandidates()}
          disabled={loading}
          data-testid="link-to-claim-toggle"
          className="self-start"
        >
          <Link2 className="h-3.5 w-3.5" strokeWidth={1.75} aria-hidden />
          {loading ? "Loading candidates…" : "Link to claim…"}
        </Button>
      ) : (
        <div className="flex flex-col gap-1">
          <div className="eyebrow text-[10px] text-muted-foreground/70">
            Pick a claim to link this {kind} ack to
          </div>
          <div
            className="flex max-h-64 flex-col overflow-y-auto rounded-md border"
            style={{
              backgroundColor: "hsl(var(--card) / 0.40)",
              borderColor: "hsl(var(--border) / 0.5)",
            }}
            data-testid="link-to-claim-list"
          >
            {candidates.length === 0 ? (
              <p className="px-3 py-3 text-[12px] text-muted-foreground/70">
                No claims available.
              </p>
            ) : (
              candidates.map((c) => (
                <button
                  key={c.id}
                  type="button"
                  disabled={submitting !== null}
                  onClick={() => void link(c.id)}
                  className={cn(
                    "flex flex-col items-start gap-1 border-b px-3 py-2 text-left text-[12px] transition-colors last:border-b-0",
                    "hover:bg-card/80 disabled:opacity-50",
                  )}
                  style={{ borderColor: "hsl(var(--border) / 0.4)" }}
                  data-testid="link-to-claim-option"
                  data-claim-id={c.id}
                >
                  <span className="mono text-foreground">{c.id}</span>
                  <span className="text-[11px] text-muted-foreground/80">
                    {c.patientName || "—"} · {fmt.usd(c.billedAmount)}
                  </span>
                </button>
              ))
            )}
          </div>
          <button
            type="button"
            onClick={() => setOpen(false)}
            className="self-start text-[11px] text-muted-foreground/70 hover:text-foreground"
            data-testid="link-to-claim-cancel"
          >
            Cancel
          </button>
        </div>
      )}
      {error ? (
        <p
          className="text-[11px] text-destructive"
          data-testid="link-to-claim-error"
        >
          {error}
        </p>
      ) : null}
    </div>
  );
}

/**
 * SP28: AckDrawer's new `<MatchedClaim />` panel.
 *
 * Mounted as the FIRST panel (before "Set responses") so the
 * operator's eye-flow on an ack row is "which claim does this ack
 * acknowledge → what was the envelope-level ack code → what were
 * the per-set responses". Per-AK2 granularity: each AK2 / ClaimStatus
 * gets its own card so a 999 with two AK2s renders two cards.
 *
 * For 999/277CA: renders one card per linked claim.
 * For TA1: renders one card per envelope linking to the originating
 * `Batch` (D4 — `claimId` is null, `batchId` is populated).
 *
 * Live-tail: subscribes to `useTailStream("claim-acks")` and merges
 * via `useMergedTail` with the per-ack filter
 * `link.ackId === ackId` so a manual match made elsewhere shows up
 * here in real time. Per `cyclone-frontend-page` convention #3, the
 * subscription lives on the drawer (this component), not on the
 * data hook.
 *
 * Empty state: when no link exists yet, render the
 * "Link to claim…" dropdown that calls `api.matchAckToClaim` and
 * refetches on success (per D5/D9 — any logged-in user can manually
 * link an orphan).
 */
export function MatchedClaimPanel({
  kind,
  ackId,
}: {
  kind: ClaimAckKind;
  ackId: number;
}) {
  const { data, isLoading, refetch } = useAckClaims(kind, ackId);
  const navigate = useNavigate();
  // Live-tail: open the shared claim-acks stream and filter to this
  // ack. The base list (`data ?? []`) is the page's authoritative
  // snapshot; the merged result also includes any new rows that
  // arrived since the snapshot fetch.
  useTailStream("claim-acks");
  const merged = useMergedTail(
    "claim-acks",
    data ?? [],
    (link) => link.ackId === ackId,
  );

  const visible = merged.filter((l) => l.ackId === ackId);

  return (
    <section
      className="flex flex-col gap-3 px-6 py-4"
      data-testid="matched-claim-panel"
      aria-label="Matched claim"
    >
      <header className="flex items-center justify-between gap-3">
        <div className="eyebrow flex items-center gap-2">
          <span className="inline-block h-px w-6 bg-foreground/20" />
          Matched claim
          <span
            className="mono ml-1 text-[10px] tabular-nums text-muted-foreground/70"
            data-testid="matched-claim-count"
          >
            ({visible.length})
          </span>
        </div>
      </header>

      {isLoading && visible.length === 0 ? (
        <div
          className="flex flex-col gap-2"
          data-testid="matched-claim-loading"
        >
          <Skeleton variant="row" />
          <Skeleton variant="row" />
        </div>
      ) : visible.length === 0 ? (
        <div
          className="flex flex-col gap-2 rounded-md border border-dashed border-border/60 bg-card/40 px-4 py-3"
          data-testid="matched-claim-empty"
        >
          <p className="text-[12.5px] text-muted-foreground/80">
            {kind === "ta1"
              ? "This TA1 envelope hasn't been linked to a Batch yet."
              : "No claim links yet for this ack."}
          </p>
          {/* Per D5/D9: any logged-in user can run the manual-match
              endpoint. The dropdown lets the operator pick from a
              coarse candidate pool; the backend does the actual join. */}
          <LinkToClaimDropdown
            kind={kind}
            ackId={ackId}
            onLinked={() => {
              void refetch();
            }}
          />
        </div>
      ) : (
        <div className="flex flex-col gap-2" data-testid="matched-claim-list">
          {visible.map((link) => (
            <ClaimLinkCard
              key={link.id}
              link={link}
              onOpenClaim={(claimId) => {
                // Cross-page navigation — mirrors the Inbox →
                // ClaimDrawer pattern. The ClaimDrawer is mounted on
                // the /claims page; navigating there with ?claim=<id>
                // restores the drawer with the deep-link.
                navigate(
                  `/claims?claim=${encodeURIComponent(claimId)}`,
                );
              }}
            />
          ))}
        </div>
      )}
    </section>
  );
}