// #checks/print-run — AP Task 5. Reached only from ChecksScreen.tsx's
// "Preview" button, carrying everything the run needs as route params
// (accountId, paymentIds, format, offsetX, offsetY — see lib/useHashView.ts's
// header). App.tsx renders this with NO Sidebar/app chrome, same convention
// as the statement/invoice print views.
//
// On mount this calls POST /api/c/checks/print EXACTLY ONCE (guarded by
// assignStartedRef, since React.StrictMode double-invokes effects in dev —
// see main.tsx) — that call is the one that actually assigns sequential
// check numbers (assign_check_numbers(), db/migrations/0009_ap.sql) and
// persists format/offsetX/offsetY as the company's new defaults. Once
// assigned, the numbers are fixed: re-rendering this same page (the design
// brief's "reprint this run" — a jam, a second copy) must never call that
// endpoint again, so the assignment result lives only in this component's
// own state from here on, never refetched.
//
// Voucher format additionally needs each payment's applied-bills list for
// its two stub copies (ref/date/amount) — POST /checks/print's own response
// doesn't carry that (it only returns what a CHECK needs: payee/amount/
// amount-in-words/memo/date), so this fetches GET /api/c/bill-payments/:id
// per assigned payment, once, right after assignment succeeds.

import { useEffect, useRef, useState, type CSSProperties } from "react";
import { useCompany } from "../lib/company";
import { apiGet, apiPost, ApiError } from "../lib/api";
import { fmtDate } from "../lib/date";
import { fmtMoney } from "../lib/money";
import { ErrorBanner } from "../components/ErrorBanner";
import type { NavTarget } from "../lib/useHashView";

interface Assignment {
  paymentId: string;
  checkNo: string;
  vendorName: string;
  amount: string;
  amountWords: string;
  date: string;
  memo: string | null;
}

interface PrintResponse {
  assignments: Assignment[];
  format: string;
  offsets: { x: string; y: string };
}

interface BillApplicationRow {
  billId: string;
  refNo: string | null;
  billDate: string;
  amount: string;
}

interface BillPaymentDetailForStub {
  applications: BillApplicationRow[];
}

type Status =
  | { name: "assigning" }
  | { name: "error"; message: string; isCollision: boolean }
  | { name: "done"; assignments: Assignment[]; format: string; offsetX: string; offsetY: string; stubsByPayment: Map<string, BillApplicationRow[]> };

function parsePaymentIds(raw: string | undefined): string[] {
  return (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
}

export function CheckPrintPage({
  params,
  navigate,
}: {
  params: Record<string, string>;
  navigate: (target: NavTarget) => void;
}) {
  const { selectedCompanyId } = useCompany();
  const assignStartedRef = useRef(false);
  const [status, setStatus] = useState<Status>({ name: "assigning" });

  const accountId = params.accountId;
  const paymentIds = parsePaymentIds(params.paymentIds);
  const requestedFormat = params.format;
  const requestedOffsetX = params.offsetX;
  const requestedOffsetY = params.offsetY;

  useEffect(() => {
    if (assignStartedRef.current || !selectedCompanyId || !accountId || paymentIds.length === 0) return;
    assignStartedRef.current = true;

    (async () => {
      try {
        const res = await apiPost<PrintResponse>(
          "/api/c/checks/print",
          {
            accountId,
            paymentIds,
            format: requestedFormat || undefined,
            offsetX: requestedOffsetX !== undefined ? Number(requestedOffsetX) : undefined,
            offsetY: requestedOffsetY !== undefined ? Number(requestedOffsetY) : undefined,
          },
          selectedCompanyId,
        );

        let stubsByPayment = new Map<string, BillApplicationRow[]>();
        if (res.format === "voucher") {
          const details = await Promise.all(
            res.assignments.map((a) =>
              apiGet<BillPaymentDetailForStub>(`/api/c/bill-payments/${a.paymentId}`, selectedCompanyId).catch(
                () => ({ applications: [] as BillApplicationRow[] }),
              ),
            ),
          );
          stubsByPayment = new Map(res.assignments.map((a, i) => [a.paymentId, details[i]!.applications]));
        }

        setStatus({
          name: "done",
          assignments: res.assignments,
          format: res.format,
          offsetX: res.offsets.x,
          offsetY: res.offsets.y,
          stubsByPayment,
        });
      } catch (err) {
        const message = err instanceof ApiError ? err.message : "Couldn't print those checks. Try again.";
        const isCollision = err instanceof ApiError && err.status === 409;
        setStatus({ name: "error", message, isCollision });
      }
    })();
  }, [selectedCompanyId, accountId, paymentIds.join(","), requestedFormat, requestedOffsetX, requestedOffsetY]);

  const transformStyle =
    status.name === "done"
      ? ({ "--check-offset-x": `${status.offsetX}mm`, "--check-offset-y": `${status.offsetY}mm` } as CSSProperties)
      : undefined;

  return (
    <div className="print-page">
      <div className="print-toolbar no-print">
        <button type="button" className="back-link" onClick={() => window.history.back()}>
          ← Back
        </button>
        {status.name === "done" && (
          <button type="button" className="btn" onClick={() => window.print()}>
            Print
          </button>
        )}
      </div>

      {status.name === "assigning" && (
        <p className="msg-inline no-print print-status">Assigning check numbers&hellip;</p>
      )}

      {status.name === "error" && (
        <div className="no-print print-status">
          <ErrorBanner message={status.message} onRetry={() => window.history.back()} />
          {status.isCollision && (
            <p className="sub" style={{ marginTop: 10 }}>
              <button type="button" className="acct-link" onClick={() => navigate("settings")}>
                Go to Settings → Checks
              </button>
            </p>
          )}
        </div>
      )}

      {status.name === "done" && (
        <>
          <div className="no-print print-status check-run-confirm">
            <h2>{status.assignments.length} check{status.assignments.length === 1 ? "" : "s"} assigned</h2>
            <ul className="entry-lines">
              {status.assignments.map((a) => (
                <li key={a.paymentId}>
                  #{a.checkNo} — {a.vendorName} — {fmtMoney(a.amount)}
                </li>
              ))}
            </ul>
            <div className="wizard-actions">
              <button type="button" className="btn" onClick={() => navigate("checks")}>
                Done
              </button>
            </div>
          </div>

          <div className="check-run" style={transformStyle}>
            {status.format === "voucher"
              ? status.assignments.map((a) => (
                  <VoucherPage key={a.paymentId} assignment={a} bills={status.stubsByPayment.get(a.paymentId) ?? []} />
                ))
              : chunk(status.assignments, 3).map((group, i) => <ThreePerPagePage key={i} assignments={group} />)}
          </div>
        </>
      )}
    </div>
  );
}

function chunk<T>(items: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
  return out;
}

function CheckStrip({ assignment }: { assignment: Assignment }) {
  return (
    <div className="check-strip check-strip-check">
      <div className="check-field check-field-date">{fmtDate(assignment.date)}</div>
      <div className="check-field check-field-payee">{assignment.vendorName}</div>
      <div className="check-field check-field-amount">{fmtMoney(assignment.amount)}</div>
      <div className="check-field check-field-words">{assignment.amountWords}</div>
      {assignment.memo && <div className="check-field check-field-memo">Memo: {assignment.memo}</div>}
      <div className="check-field check-field-no">#{assignment.checkNo}</div>
    </div>
  );
}

function ThreePerPagePage({ assignments }: { assignments: Assignment[] }) {
  return (
    <div className="check-page">
      {assignments.map((a) => (
        <CheckStrip key={a.paymentId} assignment={a} />
      ))}
    </div>
  );
}

function StubStrip({ assignment, bills }: { assignment: Assignment; bills: BillApplicationRow[] }) {
  return (
    <div className="check-strip check-strip-stub">
      <div className="check-stub-head">
        {assignment.vendorName} · Check #{assignment.checkNo} · {fmtDate(assignment.date)}
      </div>
      {bills.length === 0 ? (
        <div className="check-stub-row">{fmtMoney(assignment.amount)} — not applied to a specific bill</div>
      ) : (
        <table className="check-stub-table">
          <thead>
            <tr>
              <th>Bill</th>
              <th>Date</th>
              <th className="num">Amount</th>
            </tr>
          </thead>
          <tbody>
            {bills.map((b) => (
              <tr key={b.billId}>
                <td>{b.refNo ? `#${b.refNo}` : "—"}</td>
                <td>{fmtDate(b.billDate)}</td>
                <td className="num">{fmtMoney(b.amount)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

function VoucherPage({ assignment, bills }: { assignment: Assignment; bills: BillApplicationRow[] }) {
  return (
    <div className="check-page">
      <CheckStrip assignment={assignment} />
      <StubStrip assignment={assignment} bills={bills} />
      <StubStrip assignment={assignment} bills={bills} />
    </div>
  );
}
