// ---------------------------------------------------------------------------
// Inbox page (sub-project 6, SP14).
//
// Full-bleed dark working surface for the five-lane triage workflow.
// Wires useInboxLanes + Lane + InboxHeader + BulkBar. Bulk actions call
// the inbox API client and refetch on completion.
//
// SP14: added the `payer_rejected` lane (277CA STC A4/A6/A7) and the
// Acknowledge bulk action. The Payer-Rejected lane sits between
// `rejected` (envelope) and `candidates` (recon opportunities) so the
// operator's eye flow is: envelope problems → payer problems →
// auto-recon opportunities → claims still in flight → done today.
// ---------------------------------------------------------------------------

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { cn } from "@/lib/utils";
import { Lane, type LaneRow } from "@/components/inbox/Lane";
import { InboxHeader } from "@/components/inbox/InboxHeader";
import { BulkBar } from "@/components/inbox/BulkBar";
import { RemitDrawer } from "@/components/RemitDrawer";
import { useRemitDrawerUrlState } from "@/hooks/useRemitDrawerUrlState";
import { useInboxLanes } from "@/hooks/useInboxLanes";
import {
  exportInboxCsvUrl,
  dismissCandidates,
  resubmitRejected,
  resubmitRejectedWithDownload,
  acknowledgePayerRejected,
} from "@/lib/inbox-api";
import { downloadBlob } from "@/lib/download";

type LaneKey =
  | "rejected"
  | "payer_rejected"
  | "candidates"
  | "unmatched"
  | "done_today";

function rowKey(row: LaneRow): string {
  return row.kind === "remit" ? (row as { payer_claim_control_number: string }).payer_claim_control_number : row.id;
}

export default function Inbox() {
  const { lanes, loading, error, refetch } = useInboxLanes();
  // SP21 Phase 4 Task 4.4: drill-down from inbox rows. The hook reads
  // `?remit=` off `window.location.search` so opening a row from the
  // /inbox URL pushes `?remit=ID` onto /inbox itself (it doesn't
  // navigate to /remittances). The drawer just opens in-place.
  const navigate = useNavigate();
  const { remitId, open, close } = useRemitDrawerUrlState();
  const [selected, setSelected] = useState<Record<LaneKey, string[]>>({
    rejected: [],
    payer_rejected: [],
    candidates: [],
    unmatched: [],
    done_today: [],
  });

  function setLaneSelected(lane: LaneKey, ids: string[]) {
    setSelected((prev) => ({ ...prev, [lane]: ids }));
  }

  // Resubmit-bundle download modal state (SP8). When the user selects N>1
  // rejected claims and hits Resubmit, we surface a confirm modal that
  // lets them choose "Resubmit only" (just move the state, no download)
  // vs "Resubmit + Download" (move the state AND hand them a zip of the
  // regenerated 837 files). Single-claim resubmit skips the modal —
  // there's nothing to bundle, the download is implicit in the action.
  const [bundleModalOpen, setBundleModalOpen] = useState(false);
  const [bundleSubmitting, setBundleSubmitting] = useState(false);

  async function performResubmit(ids: string[], withDownload: boolean) {
    if (withDownload) {
      const { blob, filename, serializeErrors } =
        await resubmitRejectedWithDownload(ids);
      downloadBlob(filename, blob);
      // Surface per-claim serialization failures (rare — usually means
      // raw_json is corrupted for one of the claims). Fail-soft: don't
      // throw, the user still got the zip with the claims that did work.
      if (serializeErrors.length > 0) {
        console.warn(
          "resubmit bundle: skipped",
          serializeErrors.length,
          "claims (couldn't regenerate 837):",
          serializeErrors,
        );
      }
    } else {
      await resubmitRejected(ids);
    }
    setSelected((prev) => ({ ...prev, rejected: [] }));
    await refetch();
  }

  async function onResubmit() {
    const ids = selected.rejected;
    if (ids.length === 0) return;
    if (ids.length === 1) {
      // No bundle to download — single file, just do it. The user can
      // always grab the .x12 from the claim drawer afterward.
      setBundleSubmitting(true);
      try {
        await performResubmit(ids, false);
      } finally {
        setBundleSubmitting(false);
      }
      return;
    }
    setBundleModalOpen(true);
  }

  async function onResubmitOnly() {
    const ids = selected.rejected;
    setBundleModalOpen(false);
    setBundleSubmitting(true);
    try {
      await performResubmit(ids, false);
    } finally {
      setBundleSubmitting(false);
    }
  }

  async function onResubmitAndDownload() {
    const ids = selected.rejected;
    setBundleModalOpen(false);
    setBundleSubmitting(true);
    try {
      await performResubmit(ids, true);
    } finally {
      setBundleSubmitting(false);
    }
  }

  async function onDismiss() {
    if (selected.candidates.length === 0) return;
    // Pair each selected remit with its first candidate claim (the row's
    // top match). This matches the dismiss UX described in the spec: the
    // user has already seen the top score, so we trust it for dismissal.
    const pairs: Array<{ claim_id: string; remit_id: string }> = [];
    for (const r of lanes.candidates) {
      if (!selected.candidates.includes(rowKey(r))) continue;
      const top = r.candidates[0];
      if (top) pairs.push({ claim_id: top.claim_id, remit_id: r.id });
    }
    if (pairs.length) {
      await dismissCandidates(pairs);
      setSelected((prev) => ({ ...prev, candidates: [] }));
      await refetch();
    }
  }

  async function onAcknowledge() {
    // SP14: drop selected payer-rejected claims from the working
    // surface. The backend persists an `acknowledged_at` timestamp;
    // the original 277CA rejection (status code, reason, source 277CA
    // id) stays intact for the SP11 audit log.
    const ids = selected.payer_rejected;
    if (ids.length === 0) return;
    await acknowledgePayerRejected(ids);
    setSelected((prev) => ({ ...prev, payer_rejected: [] }));
    await refetch();
  }

  function onExport(lane: LaneKey) {
    if (selected[lane].length === 0) return;
    window.open(exportInboxCsvUrl(lane), "_blank");
  }

  if (loading) {
    return (
      <div
        className="min-h-screen"
        style={{ background: "var(--tt-bg)", color: "var(--tt-ink)" }}
      >
        <InboxHeader needEyesCount={0} doneTodayCount={0} />
        <div
          className="px-6 lg:px-10 py-10 mono flex items-center gap-2"
          style={{ color: "var(--tt-ink-dim)", fontSize: 12 }}
        >
          <span
            aria-hidden
            className="inline-block h-1.5 w-1.5 rounded-full animate-pulse-dot"
            style={{ background: "var(--tt-amber)" }}
          />
          loading inbox…
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div
        className="min-h-screen"
        style={{ background: "var(--tt-bg)", color: "var(--tt-ink)" }}
      >
        <InboxHeader needEyesCount={0} doneTodayCount={0} />
        <div
          className="px-6 lg:px-10 py-10 mono flex flex-col gap-2"
          style={{ color: "var(--tt-oxblood)", fontSize: 12 }}
        >
          <span
            className="uppercase tracking-[0.18em]"
            style={{ fontSize: 10, fontWeight: 700 }}
          >
            Connection error
          </span>
          <span>{error.message}</span>
        </div>
      </div>
    );
  }

  // SP14: payer_rejected is also actionable — a 277CA rejection that
  // needs operator follow-up. Without it, payer-side rejections would
  // hide behind the 999 envelope "rejected" lane and the operator
  // could miss a high-value denial.
  const needEyes =
    lanes.rejected.length +
    lanes.payer_rejected.length +
    lanes.candidates.length +
    lanes.unmatched.length;

  return (
    <div
      className="min-h-screen"
      style={{ background: "var(--tt-bg)", color: "var(--tt-ink)" }}
    >
      {/* SP21 Phase 4 Task 4.4: RemitDrawer mounts here so a row click
          in the candidates / unmatched lanes drills into the parent
          remit. The drawer portals into document.body (Radix Dialog),
          so the surrounding dark surface is decorative — the drawer
          overlays it when open. The hook reads `?remit=` off the URL,
          so deep links restore the open remit on reload. */}
      <RemitDrawer
        remitId={remitId}
        remits={[]}
        onClose={close}
        onNavigate={open}
        onToggleHelp={() => {
          // No cheatsheet on the inbox surface — `?` is a no-op
          // here, but the prop is required by the drawer's contract.
        }}
      />
      <InboxHeader needEyesCount={needEyes} doneTodayCount={lanes.done_today.length} />

      {/* Fold — a thin amber rule + italic serif annotation that
          transitions the editorial header into the lane grid below. */}
      <div
        aria-hidden
        className="relative h-10 flex items-center"
        style={{ background: "var(--tt-bg)" }}
      >
        <div
          className="absolute inset-x-0 top-1/2 -translate-y-1/2 h-px"
          style={{
            background:
              "linear-gradient(to right, transparent, var(--tt-amber) 12%, var(--tt-amber) 88%, transparent)",
            opacity: 0.35,
          }}
        />
        <div
          className="relative z-10 mx-auto px-3 flex items-center gap-2"
          style={{ background: "var(--tt-bg)" }}
        >
          <span
            className="display italic"
            style={{ color: "var(--tt-amber)", fontSize: 14 }}
          >
            ↘
          </span>
          <span
            className="mono uppercase"
            style={{
              color: "var(--tt-ink-dim)",
              fontSize: 10.5,
              letterSpacing: "0.24em",
              fontWeight: 500,
            }}
          >
            Five lanes · one queue
          </span>
          <span
            className="display italic"
            style={{ color: "var(--tt-amber)", fontSize: 14 }}
          >
            ↙
          </span>
        </div>
      </div>

      <main className="px-6 lg:px-10 pb-24 pt-4 flex gap-4 items-stretch flex-wrap">
        <Lane
          name="REJECTED"
          accent="oxblood"
          rows={lanes.rejected}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("rejected", ids)}
        />
        {/*
          SP14: payer-rejected (277CA STC A4/A6/A7). Distinct accent
          from the 999 envelope rejected lane — the operator's eye
          flow is: 999 problems → payer problems → reconciliation
          opportunities → claims still in flight → done today.
        */}
        <Lane
          name="PAYER REJECTED"
          accent="oxblood"
          rows={lanes.payer_rejected}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("payer_rejected", ids)}
        />
        <Lane
          name="CANDIDATES"
          accent="amber"
          rows={lanes.candidates}
          // Candidates are remits waiting for a claim match — drill
          // straight into the RemitDrawer for the source remit.
          onRowClick={(row) => open(row.id)}
          onSelectionChange={(ids) => setLaneSelected("candidates", ids)}
        />
        <Lane
          name="UNMATCHED"
          accent="ink-blue"
          rows={lanes.unmatched}
          // Unmatched is a mixed bucket (kind === "claim" | "remit" per
          // the InboxClaimRow union). Defensive branch — today the
          // backend only emits "claim" rows here, but the type allows
          // both, and the next data-source change shouldn't require a
          // page edit.
          onRowClick={(row) => {
            if (row.kind === "remit") {
              open(row.id);
            } else if (row.kind === "claim") {
              navigate(
                `/claims?claim=${encodeURIComponent(row.id)}`,
              );
            }
          }}
          onSelectionChange={(ids) => setLaneSelected("unmatched", ids)}
        />
        <Lane
          name="DONE"
          accent="muted"
          rows={lanes.done_today}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("done_today", ids)}
        />
      </main>

      {/* ----------------------------------------------------------------
          PAPER-TONED SUMMARY STRIP
          A cream "ledger" panel below the lanes that shows the aggregate
          eye-flow across the queue. Mirrors the "Magazine Spread" pattern
          used on the Dashboard/Claims/etc pages so the Inbox feels part
          of the same document set, not a stand-alone terminal.
          ---------------------------------------------------------------- */}
      <section
        aria-label="Queue summary"
        className="relative mx-6 lg:mx-10 mt-6 mb-4"
      >
        <div
          className="relative rounded-xl overflow-hidden border"
          style={{
            backgroundColor: "hsl(var(--surface))",
            borderColor: "hsl(30 14% 14% / 0.10)",
            boxShadow:
              "inset 0 1px 0 0 hsl(0 0% 100% / 0.5), 0 1px 0 0 hsl(30 14% 22% / 0.06), 0 12px 40px -16px hsl(0 0% 0% / 0.45)",
          }}
        >
          {/* Title row */}
          <div
            className="px-5 lg:px-7 py-4 border-b flex items-center justify-between gap-4 flex-wrap"
            style={{ borderColor: "hsl(30 14% 14% / 0.10)" }}
          >
            <div>
              <div
                className="mono text-[10.5px] uppercase tracking-[0.20em] font-semibold mb-1.5"
                style={{ color: "hsl(var(--surface-ink-3))" }}
              >
                Queue ledger
              </div>
              <h2
                className="display leading-[0.95] tracking-[-0.03em]"
                style={{
                  color: "hsl(var(--surface-ink))",
                  fontSize: "clamp(22px, 2.2vw, 28px)",
                  fontWeight: 400,
                }}
              >
                The eye-flow, <span className="italic">at a glance.</span>
              </h2>
            </div>
            <div
              className="text-right"
              aria-label="Aggregate metrics"
            >
              <div
                className="mono text-[10px] uppercase tracking-[0.18em] font-semibold"
                style={{ color: "hsl(var(--surface-ink-3))" }}
              >
                Need eyes
              </div>
              <div
                className="display tabular-nums"
                style={{
                  color: "hsl(var(--surface-ink))",
                  fontSize: 24,
                  lineHeight: 1.1,
                }}
              >
                {needEyes}
              </div>
            </div>
          </div>

          {/* Lane metrics row — paper tiles that mirror each lane's accent */}
          <div className="grid grid-cols-2 lg:grid-cols-5 divide-x divide-y lg:divide-y-0" style={{ borderColor: "hsl(30 14% 14% / 0.08)" }}>
            <SummaryTile
              label="Rejected"
              value={lanes.rejected.length}
              tone="oxblood"
            />
            <SummaryTile
              label="Payer rejected"
              value={lanes.payer_rejected.length}
              tone="oxblood"
            />
            <SummaryTile
              label="Candidates"
              value={lanes.candidates.length}
              tone="amber"
            />
            <SummaryTile
              label="Unmatched"
              value={lanes.unmatched.length}
              tone="ink"
            />
            <SummaryTile
              label="Done today"
              value={lanes.done_today.length}
              tone="muted"
            />
          </div>
        </div>
      </section>

      {/* Per-lane bulk bars. Each shows when its lane has a selection. */}
      <BulkBar
        lane="rejected"
        count={selected.rejected.length}
        onResubmit={onResubmit}
        onAcknowledge={() => {}}
        onDismiss={() => {}}
        onExport={() => onExport("rejected")}
      />
      <BulkBar
        lane="payer_rejected"
        count={selected.payer_rejected.length}
        onResubmit={() => {}}
        onAcknowledge={onAcknowledge}
        onDismiss={() => {}}
        onExport={() => onExport("payer_rejected")}
      />
      <BulkBar
        lane="candidates"
        count={selected.candidates.length}
        onResubmit={() => {}}
        onAcknowledge={() => {}}
        onDismiss={onDismiss}
        onExport={() => onExport("candidates")}
      />
      <BulkBar
        lane="unmatched"
        count={selected.unmatched.length}
        onResubmit={() => {}}
        onAcknowledge={() => {}}
        onDismiss={() => {}}
        onExport={() => onExport("unmatched")}
      />
      <BulkBar
        lane="done_today"
        count={selected.done_today.length}
        onResubmit={() => {}}
        onAcknowledge={() => {}}
        onDismiss={() => {}}
        onExport={() => onExport("done_today")}
      />

      {/* Resubmit bundle modal — SP8. Shown when N>1 rejected claims are
          selected; lets the user choose to also download a zip of the
          regenerated 837 files. Single-claim resubmit skips this. */}
      {bundleModalOpen && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center p-4"
          style={{ background: "rgba(0,0,0,0.65)", backdropFilter: "blur(4px)" }}
          data-testid="resubmit-bundle-modal"
          role="dialog"
          aria-label="Resubmit and download bundle"
        >
          <div
            className="w-[min(34rem,90vw)] rounded-md border p-6 mono"
            style={{
              background: "var(--tt-bg)",
              borderColor: "var(--tt-amber)",
              color: "var(--tt-ink)",
              boxShadow:
                "0 0 0 1px hsl(36 75% 58% / 0.18), 0 24px 60px -12px hsl(0 0% 0% / 0.5), inset 0 1px 0 0 hsl(0 0% 100% / 0.03)",
            }}
          >
            <div className="flex items-center gap-2 mb-3">
              <span
                aria-hidden
                className="inline-block h-2 w-2 rounded-full animate-pulse-dot"
                style={{
                  background: "var(--tt-amber)",
                  boxShadow: "0 0 8px var(--tt-amber)",
                }}
              />
              <span
                className="eyebrow"
                style={{ color: "var(--tt-amber)" }}
              >
                Resubmit batch
              </span>
            </div>

            <h2
              className="display text-2xl mb-3"
              style={{ color: "var(--tt-ink)", letterSpacing: "-0.02em" }}
            >
              Resubmit {selected.rejected.length} claims
            </h2>

            <p
              className="mb-6 text-sm leading-relaxed"
              style={{ color: "var(--tt-ink-dim)" }}
            >
              Move these claims back to{" "}
              <span
                className="px-1.5 py-0.5 rounded-sm"
                style={{
                  background: "var(--tt-amber)",
                  color: "var(--tt-bg)",
                  fontWeight: 700,
                }}
              >
                submitted
              </span>
              ? Optionally download a bundle of regenerated 837 files
              (one <code>.x12</code> per claim) — useful if you're
              about to ship them to the clearinghouse.
            </p>

            <div className="flex justify-end gap-2.5">
              <button
                type="button"
                onClick={onResubmitOnly}
                disabled={bundleSubmitting}
                data-testid="resubmit-modal-only"
                className="px-4 py-2 rounded-sm text-[11px] uppercase tracking-[0.14em] transition-all hover:bg-[color:var(--tt-amber)]/10 disabled:opacity-50 focus-visible:outline-none focus-visible:bg-[color:var(--tt-amber)]/10"
                style={{
                  border: "1px solid var(--tt-amber)",
                  color: "var(--tt-amber)",
                  background: "transparent",
                  fontWeight: 600,
                }}
              >
                Resubmit only
              </button>
              <button
                type="button"
                onClick={onResubmitAndDownload}
                disabled={bundleSubmitting}
                data-testid="resubmit-modal-download"
                className="px-4 py-2 rounded-sm text-[11px] uppercase tracking-[0.14em] transition-all hover:brightness-110 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--tt-amber)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--tt-bg)]"
                style={{
                  background: "var(--tt-amber)",
                  color: "var(--tt-bg)",
                  fontWeight: 700,
                }}
              >
                Resubmit + Download
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Subtle progress overlay while a bulk resubmit is in flight, so
          the user knows the click registered even when the network is slow.
          Single-claim resubmit also benefits — no visible feedback before
          this and the inbox can sit there for a few hundred ms. */}
      {bundleSubmitting && (
        <div
          className="fixed inset-0 z-40 cursor-wait"
          style={{ background: "rgba(0,0,0,0.15)", backdropFilter: "blur(1px)" }}
          data-testid="resubmit-progress-overlay"
          aria-hidden="true"
        />
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// SummaryTile — paper-toned metric for the queue ledger strip. Mirrors
// the lane accent (oxblood / amber / ink-blue / muted) so the eye-flow
// reads at a glance.
// ---------------------------------------------------------------------------
const SUMMARY_ACCENTS: Record<
  "oxblood" | "amber" | "ink" | "muted",
  { dot: string; tint: string; text: string }
> = {
  oxblood: {
    dot: "hsl(358 70% 42%)",
    tint: "hsl(358 70% 92%)",
    text: "hsl(358 70% 30%)",
  },
  amber: {
    dot: "hsl(36 92% 50%)",
    tint: "hsl(36 82% 88%)",
    text: "hsl(36 92% 30%)",
  },
  ink: {
    dot: "hsl(212 80% 45%)",
    tint: "hsl(212 85% 92%)",
    text: "hsl(212 80% 30%)",
  },
  muted: {
    dot: "hsl(30 8% 50%)",
    tint: "hsl(36 22% 90%)",
    text: "hsl(30 8% 30%)",
  },
};

function SummaryTile({
  label,
  value,
  tone,
}: {
  label: string;
  value: number;
  tone: "oxblood" | "amber" | "ink" | "muted";
}) {
  const a = SUMMARY_ACCENTS[tone];
  return (
    <div
      className={cn(
        "relative px-4 py-4 first:pl-5 lg:first:pl-7 last:pr-5 lg:last:pr-7"
      )}
    >
      <div className="flex items-center gap-2 mb-2">
        <span
          aria-hidden
          className="block h-1.5 w-1.5 rounded-full"
          style={{ backgroundColor: a.dot }}
        />
        <span
          className="mono text-[10px] uppercase tracking-[0.20em] font-semibold"
          style={{ color: "hsl(var(--surface-ink-3))" }}
        >
          {label}
        </span>
      </div>
      <div
        className="display tabular-nums tracking-[-0.03em]"
        style={{
          color: "hsl(var(--surface-ink))",
          fontSize: "clamp(24px, 2vw, 30px)",
          lineHeight: 1,
          fontWeight: 400,
        }}
      >
        {value}
      </div>
      <div
        className="mono text-[10px] uppercase tracking-[0.16em] mt-1.5 inline-flex items-center gap-1 px-1.5 py-0.5 rounded-sm"
        style={{
          color: a.text,
          backgroundColor: a.tint,
        }}
      >
        {value === 0 ? "Clear" : value === 1 ? "row" : "rows"}
      </div>
    </div>
  );
}
