// ---------------------------------------------------------------------------
// 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 relative"
      style={{ background: "var(--tt-bg)", color: "var(--tt-ink)" }}
    >
      {/* Ambient halo — a soft, fixed radial that creates a subtle
          lighter zone behind the InboxHeader. The inbox has its own
          dark ticker-tape background so it doesn't receive the body
          wash; this overlay restores the "lit from above" feel that
          the main pages get from the body::before softbox, in the
          same warm-amber accent family as the rest of the surface.
          Pointer-events disabled. */}
      <div
        aria-hidden
        className="pointer-events-none fixed inset-x-0 top-0 z-0 h-[55vh]"
        style={{
          background: `
            radial-gradient(ellipse 60% 38% at 50% 0%, hsla(36, 60%, 30%, 0.10), transparent 65%),
            radial-gradient(ellipse 35% 28% at 88% 8%, hsla(36, 70%, 50%, 0.07), transparent 60%)
          `,
        }}
      />
      {/* 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} />

      {/* Lane grid transitions directly into the summary strip below.
          The previous "fold" divider with italic serif arrows and
          "Five lanes · one queue" caption was the page's last warm-
          paper print-shop artifact — removed so the ticker-tape
          aesthetic carries all the way through. */}

      <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}
          // SP21 Phase 5 Task 5.4: rejected claims drill into the
          // ClaimDrawer. All rows here are claims (kind === "claim"),
          // so the branch is straightforward — the type union still
          // requires the defensive check, but a row click on a claim
          // here is always a claim drill.
          onRowClick={(row) => {
            if (row.kind === "claim") {
              navigate(`/claims?claim=${encodeURIComponent(row.id)}`);
            }
          }}
          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}
          // SP21 Phase 5 Task 5.4: payer-rejected claims also drill
          // into the ClaimDrawer — same shape as the rejected lane.
          onRowClick={(row) => {
            if (row.kind === "claim") {
              navigate(`/claims?claim=${encodeURIComponent(row.id)}`);
            }
          }}
          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}
          // SP21 Phase 5 Task 5.4: done_today rows are also claim
          // drills — same as rejected / payer_rejected. The drawer
          // surfaces the claim's current state + recent history,
          // which is exactly what an operator wants when reviewing
          // "what got shipped today".
          onRowClick={(row) => {
            if (row.kind === "claim") {
              navigate(`/claims?claim=${encodeURIComponent(row.id)}`);
            }
          }}
          onSelectionChange={(ids) => setLaneSelected("done_today", ids)}
        />
      </main>

      {/* ----------------------------------------------------------------
          TICKER-TAPE QUEUE SUMMARY
          A dark terminal panel below the lanes that aggregates the
          eye-flow across the queue. Sits on the same --tt-bg-elev
          surface as the Lane cards so the page reads as one continuous
          instrument instead of mixing a paper-toned "ledger" panel
          into the terminal aesthetic.
          ---------------------------------------------------------------- */}
      <section
        aria-label="Queue summary"
        className="relative mx-6 lg:mx-10 mt-5 mb-6"
      >
        <div
          className="relative rounded-md overflow-hidden"
          style={{
            // "Lit from above" gradient — same treatment as the lane
            // cards and the main .surface panels, so the queue
            // summary reads as a slightly lifted, subtly lighter
            // surface against the ticker-tape backdrop.
            background:
              "linear-gradient(180deg, hsl(220 18% 11.5%) 0%, hsl(220 18% 9.5%) 55%, hsl(220 18% 8.5%) 100%)",
            border: "1px solid hsl(220 8% 18%)",
            boxShadow:
              "inset 0 1px 0 0 hsl(0 0% 100% / 0.05), 0 1px 2px 0 hsl(0 0% 0% / 0.4)",
          }}
        >
          {/* Header row — machine-tag eyebrow + editorial headline +
              aggregate Need eyes count, separated from the lane metrics
              by a hairline. */}
          <div
            className="px-5 lg:px-6 py-3.5 flex items-center justify-between gap-4 flex-wrap"
            style={{ borderBottom: "1px solid hsl(220 8% 16%)" }}
          >
            <div className="min-w-0">
              <div className="flex items-center gap-2.5 mb-1.5">
                <span
                  aria-hidden
                  className="inline-block h-1.5 w-1.5 rounded-full animate-pulse-dot"
                  style={{ background: "var(--tt-amber)" }}
                />
                <span
                  className="mono uppercase"
                  style={{
                    color: "var(--tt-amber)",
                    fontSize: 10.5,
                    letterSpacing: "0.22em",
                    fontWeight: 600,
                  }}
                >
                  Eye-flow
                </span>
                <span
                  className="mono uppercase"
                  style={{
                    color: "var(--tt-muted)",
                    fontSize: 10,
                    letterSpacing: "0.18em",
                  }}
                  aria-hidden
                >
                  · five lanes, one queue
                </span>
              </div>
              <h2
                className="display italic"
                style={{
                  color: "var(--tt-ink)",
                  fontSize: "clamp(20px, 1.9vw, 24px)",
                  lineHeight: 1.1,
                  fontWeight: 400,
                  letterSpacing: "-0.02em",
                }}
              >
                at a glance.
              </h2>
            </div>
            <div className="text-right" aria-label="Aggregate metrics">
              <div
                className="mono uppercase"
                style={{
                  color: "var(--tt-ink-dim)",
                  fontSize: 10,
                  letterSpacing: "0.20em",
                  fontWeight: 500,
                }}
              >
                Need eyes
              </div>
              <div
                className="display tabular-nums"
                style={{
                  color: "var(--tt-amber)",
                  fontSize: 28,
                  lineHeight: 1,
                  fontWeight: 400,
                  marginTop: 4,
                }}
              >
                {needEyes}
              </div>
            </div>
          </div>

          {/* Lane metrics row — each tile carries its own accent dot
              and count, so the operator can read the queue breakdown
              at a glance without scanning back up to the Lane grid. */}
          <div
            className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5"
            style={{ borderTop: "1px solid hsl(220 8% 16%)" }}
          >
            <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 — ticker-tape metric for the eye-flow strip. Each tile
// carries the lane's accent dot + label + count in the same color
// family the Lane component uses (oxblood / amber / ink-blue / muted),
// so the operator can read the queue breakdown at a glance without
// scanning back up to the lane grid.
// ---------------------------------------------------------------------------
const SUMMARY_ACCENTS: Record<
  "oxblood" | "amber" | "ink" | "muted",
  { dot: string; value: string }
> = {
  oxblood: {
    dot: "var(--tt-oxblood)",
    value: "var(--tt-ink)",
  },
  amber: {
    dot: "var(--tt-amber)",
    value: "var(--tt-amber)",
  },
  ink: {
    dot: "var(--tt-ink-blue)",
    value: "var(--tt-ink)",
  },
  muted: {
    dot: "var(--tt-muted)",
    value: "var(--tt-ink-dim)",
  },
};

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-6 last:pr-5 lg:last:pr-6"
      )}
      style={{ borderRight: "1px solid hsl(220 8% 14%)" }}
    >
      <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,
            boxShadow: `0 0 6px ${a.dot}`,
          }}
        />
        <span
          className="mono uppercase"
          style={{
            color: "var(--tt-ink-dim)",
            fontSize: 10.5,
            letterSpacing: "0.18em",
            fontWeight: 500,
          }}
        >
          {label}
        </span>
      </div>
      <div
        className="display tabular-nums"
        style={{
          color: a.value,
          fontSize: "clamp(22px, 1.9vw, 28px)",
          lineHeight: 1,
          fontWeight: 400,
          letterSpacing: "-0.02em",
        }}
      >
        {value}
      </div>
      <div
        className="mono uppercase mt-1.5"
        style={{
          color: "var(--tt-muted)",
          fontSize: 9.5,
          letterSpacing: "0.16em",
          fontWeight: 500,
        }}
      >
        {value === 0 ? "clear" : value === 1 ? "row" : "rows"}
      </div>
    </div>
  );
}
