import { ArrowRight } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useClaimAcks } from "@/hooks/useClaimAcks";
import { useMergedTail } from "@/hooks/useMergedTail";
import { useTailStream } from "@/hooks/useTailStream";
import { EmptyState } from "@/components/ui/empty-state";
import { cn } from "@/lib/utils";
import { fmt } from "@/lib/format";
import type { ClaimAck, ClaimAckKind } from "@/types";

/**
 * Color palette for the per-AK2 / per-ClaimStatus accept-reject code
 * pill. Mirrors the `Acks.tsx` `AckCodeBadge` palette so the
 * operator's eye-flow is identical between the Acks page and this
 * panel.
 *
 *   - "A" (accepted)      → success green
 *   - "R" (rejected)      → destructive red
 *   - "E" (errors)        → warning amber
 *   - "X" (rejected w/ 999 AK5) → destructive red
 *   - anything else       → muted (treats STC category codes like A1/A2/A6 as
 *     operator-readable rather than literal A/E letters)
 */
function codeTone(code: string | null | undefined): {
  fg: string;
  bg: string;
  border: string;
} {
  if (!code) {
    return {
      fg: "hsl(var(--muted-foreground))",
      bg: "hsl(var(--muted) / 0.30)",
      border: "hsl(var(--border) / 0.5)",
    };
  }
  const upper = code.toUpperCase();
  if (upper === "A") {
    return {
      fg: "hsl(var(--success))",
      bg: "hsl(var(--success) / 0.10)",
      border: "hsl(var(--success) / 0.30)",
    };
  }
  if (upper === "R" || upper === "X") {
    return {
      fg: "hsl(var(--destructive))",
      bg: "hsl(var(--destructive) / 0.10)",
      border: "hsl(var(--destructive) / 0.30)",
    };
  }
  if (upper === "E" || upper === "P") {
    return {
      fg: "hsl(var(--warning))",
      bg: "hsl(var(--warning) / 0.10)",
      border: "hsl(var(--warning) / 0.30)",
    };
  }
  // STC category code (A1/A2/A3/A4/A6/A7) — keep the operator-visible
  // tone as warning so a 277CA "rejected" stands out, but render the
  // literal code rather than the badge letter so the operator can
  // tell which STC category triggered the link.
  return {
    fg: "hsl(var(--warning))",
    bg: "hsl(var(--warning) / 0.10)",
    border: "hsl(var(--warning) / 0.30)",
  };
}

function AckKindBadge({ kind }: { kind: ClaimAckKind }) {
  // The ack kind itself is one of "999" / "277ca" / "ta1". TA1 rows
  // don't appear in this panel (filtered out per D4), but the type
  // union still allows it — render it consistently with the Acks page.
  const label =
    kind === "999" ? "999" : kind === "277ca" ? "277CA" : "TA1";
  return (
    <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="ack-kind-badge"
    >
      {label}
    </span>
  );
}

/**
 * One compact row in the Acknowledgments panel. Renders the ack
 * code (color-coded), the ack kind, the linked set_control_number
 * (the operator-readable join key), the parsed_at date, and a `→`
 * link to the matching AckDrawer.
 */
function AckLinkRow({
  link,
  onOpenAck,
}: {
  link: ClaimAck;
  onOpenAck: (ackId: number, kind: ClaimAckKind) => void;
}) {
  const tone = codeTone(link.setAcceptRejectCode);
  return (
    <div
      className="flex items-center gap-3 rounded-md border px-3 py-2"
      style={{
        backgroundColor: "hsl(var(--card) / 0.40)",
        borderColor: "hsl(var(--border) / 0.5)",
      }}
      data-testid="acknowledgments-row"
    >
      {/* Accept/reject pill */}
      <span
        className="mono inline-flex items-center rounded-sm border px-2 py-0.5 text-[10.5px] font-semibold uppercase tracking-[0.14em]"
        style={{
          color: tone.fg,
          backgroundColor: tone.bg,
          borderColor: tone.border,
        }}
        data-testid="ack-code-pill"
      >
        {link.setAcceptRejectCode ?? "—"}
      </span>

      {/* AK2 index chip (999 only) — gives the operator a quick
          "which set-response" reference for the per-AK2 granularity. */}
      {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}

      <AckKindBadge kind={link.ackKind} />

      {link.setControlNumber ? (
        <span
          className="mono truncate text-[11.5px] text-muted-foreground/80"
          title={`set_control_number: ${link.setControlNumber}`}
        >
          {link.setControlNumber}
        </span>
      ) : null}

      <span className="ml-auto mono text-[10.5px] text-muted-foreground/60">
        {link.linkedAt ? fmt.dateShort(link.linkedAt) : "—"}
      </span>

      <button
        type="button"
        onClick={() => onOpenAck(link.ackId, link.ackKind)}
        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-ack-drawer"
        aria-label={`Open ${link.ackKind} ACK ${link.ackId}`}
      >
        open
        <ArrowRight className="h-3 w-3" strokeWidth={1.75} aria-hidden />
      </button>
    </div>
  );
}

/**
 * SP28: ClaimDrawer's new `<Acknowledgments />` panel.
 *
 * Mounted between `<ServiceLinesTable />` and the trailing panels.
 * Lists every `claim_acks` row that links to the current claim:
 *   - One row per AK2 set-response (for 999 acks — per-AK2 granularity)
 *   - One row per ClaimStatus (for 277CA)
 *   - TA1 batch-level rows are filtered out (D4 — claim_id IS NULL)
 *
 * Live-tail: subscribes to `useTailStream("claim-acks")` and merges
 * via `useMergedTail` with the per-claim filter
 * `link.claimId === claimId` so a manual match on the AckDrawer or a
 * new auto-link from a 999 ack shows up on the ClaimDrawer in real
 * time. Per `cyclone-frontend-page` convention #3, the subscription
 * lives on the drawer (this component), not on `useClaimAcks`.
 *
 * Empty state: when the claim has no acks yet, render the existing
 * `<EmptyState />` with copy "No acknowledgments received yet for
 * this claim." — so a fresh claim gets a clear non-error empty state
 * rather than a quiet section.
 */
export function AcknowledgmentsPanel({
  claimId,
}: {
  claimId: string;
}) {
  const { data, isLoading } = useClaimAcks(claimId);
  const navigate = useNavigate();
  // Live-tail: open the shared claim-acks stream and filter to this
  // claim. The base list (`data ?? []`) is the page's authoritative
  // snapshot; the merged result also includes any new rows that
  // arrived since the snapshot fetch (manual match, new ack ingest).
  useTailStream("claim-acks");
  const merged = useMergedTail(
    "claim-acks",
    data ?? [],
    (link) => link.claimId === claimId,
  );

  // TA1 batch-level rows have `claimId === null`. The backend filters
  // them out of the per-claim endpoint, but defensive belt-and-
  // suspenders: skip any row that doesn't claim this claim id (the
  // filterFn already does this for the tail slice; we apply the same
  // to baseItems-derived merged items to be safe).
  const visible = merged.filter((l) => l.claimId === claimId);

  return (
    <section
      className="flex flex-col gap-3 px-6 py-4"
      data-testid="acknowledgments-panel"
      aria-label="Acknowledgments"
    >
      <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" />
          Acknowledgments
          <span
            className="mono ml-1 text-[10px] tabular-nums text-muted-foreground/70"
            data-testid="acknowledgments-count"
          >
            ({visible.length})
          </span>
        </div>
      </header>

      {isLoading && visible.length === 0 ? (
        <p
          className="text-[12.5px] text-muted-foreground/70"
          data-testid="acknowledgments-loading"
        >
          Loading acknowledgments…
        </p>
      ) : visible.length === 0 ? (
        <div
          className="rounded-md border border-dashed border-border/60 bg-card/40"
          data-testid="acknowledgments-empty"
        >
          <EmptyState
            eyebrow="Acknowledgments · none yet"
            message="No acknowledgments received yet for this claim."
          />
        </div>
      ) : (
        <div className="flex flex-col gap-2" data-testid="acknowledgments-list">
          {visible.map((link) => (
            <AckLinkRow
              key={link.id}
              link={link}
              onOpenAck={(ackId, kind) => {
                // Cross-page navigation: the AckDrawer is mounted on
                // the /acks page, not on /claims. Push the user to
                // /acks?ack=<id> so the Acks page mounts the drawer
                // with the deep-link restored (per
                // `useAckDrawerUrlState`). Mirrors the Inbox →
                // RemitDrawer pattern, where the inbox row click
                // navigates to /inbox?remit=<id>.
                //
                // `kind` is kept in the callback signature so a
                // future enhancement can route to a TA1-specific
                // page (today the AckDrawer page handles only 999
                // acks — TA1 navigation would need a different page
                // or a TA1 drawer on the same Acks surface).
                navigate(`/acks?ack=${encodeURIComponent(String(ackId))}`);
                void kind;
              }}
            />
          ))}
        </div>
      )}
    </section>
  );
}