// ---------------------------------------------------------------------------
// Inbox page (sub-project 6).
//
// Full-bleed dark working surface for the four-lane triage workflow.
// Wires useInboxLanes + Lane + InboxHeader + BulkBar. Bulk actions call
// the inbox API client and refetch on completion.
// ---------------------------------------------------------------------------

import { useState } from "react";
import { Lane, type LaneRow } from "@/components/inbox/Lane";
import { InboxHeader } from "@/components/inbox/InboxHeader";
import { BulkBar } from "@/components/inbox/BulkBar";
import { useInboxLanes } from "@/hooks/useInboxLanes";
import {
  exportInboxCsvUrl,
  dismissCandidates,
  resubmitRejected,
  resubmitRejectedWithDownload,
} from "@/lib/inbox-api";
import { downloadBlob } from "@/lib/download";
import { RoleGate } from "@/auth/RoleGate";

type LaneKey = "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();
  const [selected, setSelected] = useState<Record<LaneKey, string[]>>({
    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();
    }
  }

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

  if (loading) {
    return (
      <div
        className="min-h-screen p-8 mono flex items-center gap-2"
        style={{ background: "var(--tt-bg)", 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>
    );
  }

  if (error) {
    return (
      <div
        className="min-h-screen p-8 mono flex flex-col gap-2"
        style={{ background: "var(--tt-bg)", 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>
    );
  }

  const needEyes =
    lanes.rejected.length + lanes.candidates.length + lanes.unmatched.length;

  return (
    <div
      className="min-h-screen"
      style={{ background: "var(--tt-bg)", color: "var(--tt-ink)" }}
    >
      <InboxHeader needEyesCount={needEyes} doneTodayCount={lanes.done_today.length} />
      <main className="px-6 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)}
        />
        <Lane
          name="CANDIDATES"
          accent="amber"
          rows={lanes.candidates}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("candidates", ids)}
        />
        <Lane
          name="UNMATCHED"
          accent="ink-blue"
          rows={lanes.unmatched}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("unmatched", ids)}
        />
        <Lane
          name="DONE"
          accent="muted"
          rows={lanes.done_today}
          onRowClick={() => {}}
          onSelectionChange={(ids) => setLaneSelected("done_today", ids)}
        />
      </main>

      {/* Per-lane bulk bars. Each shows when its lane has a selection.
          The two write-affordance BulkBars (rejected → resubmit,
          candidates → dismiss) are gated by RoleGate so a viewer-role
          account can still SEE the rows and select them but cannot
          trigger a state change. The export buttons live inside the
          same BulkBar component but are read-only, so we let them
          remain visible to viewers. */}
      <RoleGate allow={["admin", "user"]} fallback={null}>
        <BulkBar
          lane="rejected"
          count={selected.rejected.length}
          onResubmit={onResubmit}
          onDismiss={() => {}}
          onExport={() => onExport("rejected")}
        />
      </RoleGate>
      <RoleGate allow={["admin", "user"]} fallback={null}>
        <BulkBar
          lane="candidates"
          count={selected.candidates.length}
          onResubmit={() => {}}
          onDismiss={onDismiss}
          onExport={() => onExport("candidates")}
        />
      </RoleGate>
      <BulkBar
        lane="unmatched"
        count={selected.unmatched.length}
        onResubmit={() => {}}
        onDismiss={() => {}}
        onExport={() => onExport("unmatched")}
      />
      <BulkBar
        lane="done_today"
        count={selected.done_today.length}
        onResubmit={() => {}}
        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 88% 56% / 0.18), 0 24px 60px -12px hsl(0 0% 0% / 0.7), inset 0 1px 0 0 hsl(0 0% 100% / 0.04)",
            }}
          >
            <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>
  );
}
