import { useMemo } from "react";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { ApiError } from "@/lib/api";
import { useRemitDetail } from "@/hooks/useRemitDetail";
import { useDrawerKeyboard } from "@/hooks/useDrawerKeyboard";
import { RemitDrawerHeader } from "./RemitDrawerHeader";
import { FinancialSummaryCard } from "./FinancialSummaryCard";
import { PartiesGrid } from "./PartiesGrid";
import { ClaimPaymentsTable } from "./ClaimPaymentsTable";
import { CasAdjustmentsPanel } from "./CasAdjustmentsPanel";
import { RemitDrawerSkeleton } from "./RemitDrawerSkeleton";
import { RemitDrawerError } from "./RemitDrawerError";

type RemitDrawerProps = {
  /**
   * Currently-open remit id, or `null` when the drawer is closed.
   * When `null`, the drawer renders nothing (no DOM) and the keyboard
   * listener is disabled — matches the `ClaimDrawer` contract so the
   * two drawers behave identically.
   */
  remitId: string | null;
  /**
   * Full ordered list of remit ids in the current view. Used to derive
   * j/k navigation targets without an extra round-trip to the server.
   * The list must contain the current `remitId` for j/k to do
   * anything; an unknown id is treated defensively as "navigation
   * disabled" rather than throwing.
   */
  remits: { id: string }[];
  /** Fired when the user dismisses the drawer (X button, header close, or Escape). */
  onClose: () => void;
  /** Fired when the user navigates via j/k — receives the new remit id. */
  onNavigate: (newId: string) => void;
  /** Fired when the user presses `?` to toggle the keyboard-help overlay. */
  onToggleHelp: () => void;
};

/**
 * Root remittance-detail drawer (RemitDrawer).
 *
 * Twin of `ClaimDrawer` — same dialog shell, same keyboard contract,
 * same skeleton/error/loaded tri-state. Composes:
 *
 *  1. **Data** — `useRemitDetail(remitId)` returns
 *     `{ data, isLoading, isError, error, refetch }`. The hook
 *     short-circuits when `remitId === null` so a closed drawer is
 *     free (no fetch, no cache slot).
 *
 *  2. **Keyboard** — `useDrawerKeyboard` wires j/k/ArrowDown/ArrowUp/
 *     Escape/`?` on `window`. Listener is only attached when the
 *     drawer is open (`enabled: remitId !== null`).
 *
 *  3. **Layout** — right-anchored side panel via the project's
 *     `Dialog` primitive, with sections composed from the leaf
 *     components (header + financial summary + parties + claim
 *     payments + CAS adjustments). Each section owns its own visual
 *     language so the layout stays composable.
 *
 * j/k navigation wraps around at both ends. `useMemo` keeps the
 * navigation callbacks stable across renders so the keyboard effect
 * doesn't re-subscribe on every parent update.
 *
 * Error branching mirrors the claim drawer:
 *   - `ApiError(404)` → "not_found" (no retry, the remit is gone)
 *   - anything else   → "network" (retry available)
 */
export function RemitDrawer({
  remitId,
  remits,
  onClose,
  onNavigate,
  onToggleHelp,
}: RemitDrawerProps) {
  const { data, isLoading, isError, error, refetch } = useRemitDetail(remitId);

  // j/k navigation: derive next/prev IDs based on the current remit's
  // index in the parent list. Wrap around at both ends. If the current
  // remit isn't in the list (stale deep link, etc.) the callbacks are
  // no-ops — defensive against bad input from the parent.
  const { onNext, onPrev } = useMemo(() => {
    if (remitId === null) {
      return { onNext: () => {}, onPrev: () => {} };
    }
    const idx = remits.findIndex((r) => r.id === remitId);
    if (idx === -1 || remits.length === 0) {
      return { onNext: () => {}, onPrev: () => {} };
    }
    return {
      onNext: () => {
        const nextId = remits[(idx + 1) % remits.length].id;
        onNavigate(nextId);
      },
      onPrev: () => {
        // Add `remits.length` before the modulo so the negative index
        // (first → wrap to last) wraps correctly without a separate
        // branch.
        const prevId = remits[(idx - 1 + remits.length) % remits.length].id;
        onNavigate(prevId);
      },
    };
  }, [remitId, remits, onNavigate]);

  useDrawerKeyboard({
    enabled: remitId !== null,
    onNext,
    onPrev,
    onClose,
    onToggleHelp,
  });

  // Closed drawer: render nothing. The Dialog's `open` prop must also
  // reflect this (so Radix tears down its portal / focus trap), and the
  // early-return keeps a closed drawer free of any DOM at all.
  if (remitId === null) return null;

  const errorKind: "not_found" | "network" | null = isError
    ? error instanceof ApiError && error.status === 404
      ? "not_found"
      : "network"
    : null;

  return (
    <Dialog
      open={remitId !== null}
      onOpenChange={(open) => {
        if (!open) onClose();
      }}
    >
      <DialogContent
        // Right-anchored side panel — mirrors the ClaimDrawer's
        // positioning override. The Dialog primitive's default
        // centered layout is suppressed so the drawer reads as a
        // side-panel, not a modal.
        className="fixed right-0 top-0 h-full w-full max-w-2xl translate-x-0 translate-y-0 rounded-none border-l border-[color:var(--m-border-heavy)]/60 bg-[color:var(--m-surface)] p-0 shadow-[-24px_0_60px_-12px_rgba(0,0,0,0.6)]"
        data-testid="remit-drawer"
        aria-describedby={undefined}
      >
        {isLoading ? (
          <RemitDrawerSkeleton />
        ) : errorKind ? (
          <RemitDrawerError
            kind={errorKind}
            onRetry={() => {
              void refetch();
            }}
            onClose={onClose}
          />
        ) : data ? (
          <div
            className="flex h-full flex-col overflow-y-auto"
            data-testid="remit-drawer-content"
          >
            <RemitDrawerHeader remit={data} onClose={onClose} />
            <div className="flex flex-col divide-y divide-[color:var(--m-border-heavy)]/12">
              <FinancialSummaryCard remit={data} />
              <PartiesGrid parties={data} fallbackName={data.payerName} />
              <ClaimPaymentsTable remit={data} />
              {data.adjustments && data.adjustments.length > 0 ? (
                <CasAdjustmentsPanel adjustments={data.adjustments} />
              ) : null}
            </div>
          </div>
        ) : null}
      </DialogContent>
    </Dialog>
  );
}
