// "I got paid" — AR Task 6. Reached three ways, all landing on this same
// screen (#receive-payment?customerId=&invoiceId=):
//   - Record screen's "Money came in" card -> "From a customer invoice"
//     (Record.tsx) — no params, starts at a blank customer picker.
//   - #invoices' "Receive payment" button (InvoicesScreen.tsx) — no params
//     (or a pre-set customerId if a customer filter is already active).
//   - An open invoice's own "Receive payment" button (InvoiceDetail.tsx) —
//     both customerId AND invoiceId, so only THAT invoice starts checked.
//
// Flow: pick a customer -> its open invoices load as checkbox rows (full
// open balance prefilled per row, editable — see openInvoiceLines.ts) ->
// pick a deposit account -> date -> confirm sentence -> submit. The
// "amount received" field defaults to the sum of every CHECKED line (same
// touched/untouched convention as InvoiceForm.tsx's due-date-from-terms
// default) but stays independently editable, since an overpayment or an
// entirely-unapplied deposit both need an amount that isn't just the sum of
// applications. POSTs to /api/c/payments (server/src/payments.ts) via
// receive_payment() — every derived figure in the confirm sentence and the
// running "Applying $X of $Y" line is computed here in whole-cents BigInt
// arithmetic (openInvoiceLines.ts), never a parsed JS float (CLAUDE.md §2);
// the success panel's own sentence is the server's, verbatim, same
// convention as Record.tsx/InvoiceForm.tsx.

import { useEffect, useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { apiPost, ApiError } from "../lib/api";
import { todayIso } from "../lib/date";
import { fmtMoney } from "../lib/money";
import { ErrorBanner } from "../components/ErrorBanner";
import { BackLink } from "../components/BackLink";
import { SearchablePicker, type PickerGroup } from "../components/SearchablePicker";
import type { ApiAccount } from "../accounts/subtypes";
import { accountPickerGroups } from "../accounts/pickerGroups";
import type { NavTarget } from "../lib/useHashView";
import { OpenInvoiceLinesTable } from "./OpenInvoiceLinesTable";
import {
  buildApplications,
  centsOfEntered,
  centsToEntered,
  lineAmountError,
  seedLines,
  sumCheckedCents,
  type OpenInvoiceApiRow,
  type OpenInvoiceLine,
} from "./openInvoiceLines";

// Same shape as Record.tsx's AMOUNT_RE / server/src/payments.ts's
// amountSchema — up to 2 decimal places, no leading zeros.
const AMOUNT_RE = /^(0|[1-9]\d{0,12})(\.\d{1,2})?$/;

function amountError(raw: string): string | null {
  const trimmed = raw.trim();
  if (trimmed === "") return "Enter how much you received.";
  if (!AMOUNT_RE.test(trimmed) || !/[1-9]/.test(trimmed)) {
    return "Enter an amount like 120 or 120.50.";
  }
  return null;
}

interface CustomerOption {
  id: string;
  name: string;
}

interface OpenInvoicesResponse {
  invoices: OpenInvoiceApiRow[];
  unappliedCredit: string;
}

interface PaymentResult {
  paymentId: string;
  summary: string;
}

type Stage =
  | { name: "form" }
  | { name: "success"; result: PaymentResult; appliedInvoices: { id: string; no: string | null }[] };

export function ReceivePayment({
  navigate,
  initialCustomerId,
  initialInvoiceId,
}: {
  navigate: (target: NavTarget) => void;
  initialCustomerId?: string;
  initialInvoiceId?: string;
}) {
  const { selectedCompanyId } = useCompany();
  const { data: customers } = useApiGet<CustomerOption[]>("/api/c/customers", selectedCompanyId);
  const { data: accounts } = useApiGet<ApiAccount[]>("/api/c/accounts", selectedCompanyId);

  const [stage, setStage] = useState<Stage>({ name: "form" });
  const [customerId, setCustomerId] = useState<string | null>(initialCustomerId ?? null);
  const [date, setDate] = useState(todayIso());
  const [amount, setAmount] = useState("");
  const [amountTouched, setAmountTouched] = useState(false);
  const [depositAccountId, setDepositAccountId] = useState<string | null>(null);
  const [lines, setLines] = useState<OpenInvoiceLine[]>([]);
  const [seededFor, setSeededFor] = useState<string | null>(null);
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  const openInvoicesPath = customerId ? `/api/c/customers/${customerId}/open-invoices` : null;
  const {
    data: openInvoices,
    loading: openInvoicesLoading,
    error: openInvoicesError,
    retry: retryOpenInvoices,
  } = useApiGet<OpenInvoicesResponse>(openInvoicesPath, selectedCompanyId);

  // Re-seed the checkbox rows exactly once per customer — the very first
  // time this customer's open invoices load, restrict "only checked" to the
  // invoice this screen was opened FOR (see the file header); switching
  // customers by hand afterward always starts every row checked, since
  // there's no single "the" invoice anymore once the picker itself changed.
  useEffect(() => {
    if (!openInvoices || !customerId || seededFor === customerId) return;
    const onlyInvoiceId = customerId === initialCustomerId ? initialInvoiceId : undefined;
    setLines(seedLines(openInvoices.invoices, { onlyInvoiceId }));
    setSeededFor(customerId);
    setAmountTouched(false);
  }, [openInvoices, customerId, seededFor, initialCustomerId, initialInvoiceId]);

  // Amount received defaults to the sum of every checked line, same
  // touched/untouched convention as InvoiceForm.tsx's due-date default —
  // once the user edits the amount box directly, it stops following the
  // lines (an overpayment or a fully-unapplied deposit both need to be able
  // to differ from the applied sum).
  useEffect(() => {
    if (amountTouched) return;
    setAmount(centsToEntered(sumCheckedCents(lines)));
  }, [lines, amountTouched]);

  const customerGroups: PickerGroup[] = [
    { label: "Customers", options: (customers ?? []).map((c) => ({ id: c.id, label: c.name })) },
  ];
  // Asset-restricted "deposit to" picker under the standard sections (Bank
  // accounts / ... — Chart Management Task 3); the restriction is unchanged.
  const moneyGroups: PickerGroup[] = accountPickerGroups(accounts, ["asset"]);

  const selectedCustomer = customers?.find((c) => c.id === customerId) ?? null;
  const depositAccountName = accounts?.find((a) => a.id === depositAccountId)?.name ?? null;

  function selectCustomer(id: string) {
    setCustomerId(id);
    setLines([]);
    setSeededFor(null);
  }

  const amtErr = amountError(amount);
  const amountCents = centsOfEntered(amount);
  const appliedCents = sumCheckedCents(lines);
  const remainderCents = amountCents - appliedCents;
  const linesValid = lines.every((l) => lineAmountError(l) === null);
  const overApplied = amtErr === null && remainderCents < 0n;

  const formValid =
    customerId !== null &&
    depositAccountId !== null &&
    date !== "" &&
    amtErr === null &&
    linesValid &&
    !overApplied;

  function previewSentence(): string | null {
    if (!formValid || !selectedCustomer || !depositAccountName) return null;
    const amountLabel = `$${centsToEntered(amountCents)}`;
    const checked = lines.filter((l) => l.checked && l.amount.trim() !== "");
    if (checked.length === 0) {
      return `This will record ${amountLabel} received from ${selectedCustomer.name} into ${depositAccountName} — kept entirely as customer credit.`;
    }
    const label = checked.length === 1 ? "invoice" : "invoices";
    const parts = checked.map((l) => `#${l.invoiceNo ?? l.invoiceId}`).join(", ");
    let sentence = `This will record ${amountLabel} received from ${selectedCustomer.name} into ${depositAccountName} — applied to ${label} ${parts}.`;
    if (remainderCents > 0n) {
      sentence += ` $${centsToEntered(remainderCents)} kept as customer credit.`;
    }
    return sentence;
  }

  function toggleLine(invoiceId: string) {
    setLines((ls) => ls.map((l) => (l.invoiceId === invoiceId ? { ...l, checked: !l.checked } : l)));
  }

  function changeLineAmount(invoiceId: string, value: string) {
    setLines((ls) => ls.map((l) => (l.invoiceId === invoiceId ? { ...l, amount: value } : l)));
  }

  function resetToBlank() {
    setStage({ name: "form" });
    setCustomerId(null);
    setDate(todayIso());
    setAmount("");
    setAmountTouched(false);
    setDepositAccountId(null);
    setLines([]);
    setSeededFor(null);
    setSubmitError(null);
  }

  async function handleSubmit() {
    if (!formValid || !selectedCompanyId || !customerId || !depositAccountId) return;
    setSubmitting(true);
    setSubmitError(null);
    const appliedInvoices = lines
      .filter((l) => l.checked && l.amount.trim() !== "")
      .map((l) => ({ id: l.invoiceId, no: l.invoiceNo }));
    try {
      const result = await apiPost<PaymentResult>(
        "/api/c/payments",
        {
          customerId,
          paymentDate: date,
          amount: amount.trim(),
          depositAccountId,
          applications: buildApplications(lines),
        },
        selectedCompanyId,
      );
      setStage({ name: "success", result, appliedInvoices });
    } catch (err) {
      setSubmitError(err instanceof ApiError ? err.message : "Couldn't record that payment. Try again.");
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div className="wrap">
      <BackLink label="Back" />
      <header className="top">
        <h1>Receive a payment</h1>
      </header>

      {stage.name === "form" && (
        <div className="card record-form">
          {submitError && <ErrorBanner message={submitError} onRetry={() => void handleSubmit()} />}

          <SearchablePicker
            id="pay-customer"
            label="Customer"
            placeholder="Search customers…"
            groups={customerGroups}
            value={customerId}
            onChange={selectCustomer}
          />

          {customerId && openInvoicesError && (
            <ErrorBanner message={openInvoicesError} onRetry={retryOpenInvoices} />
          )}
          {customerId && openInvoicesLoading && !openInvoices && (
            <p className="msg-inline">Loading open invoices&hellip;</p>
          )}

          {customerId && openInvoices && openInvoices.unappliedCredit !== "0.0000" && (
            <p className="sub">
              {selectedCustomer?.name ?? "This customer"} already has {fmtMoney(openInvoices.unappliedCredit)} in
              unapplied credit from previous payments.
            </p>
          )}

          {customerId && openInvoices && openInvoices.invoices.length === 0 && (
            <p className="sub">No open invoices for this customer — this payment will be kept as credit.</p>
          )}

          {customerId && lines.length > 0 && (
            <OpenInvoiceLinesTable lines={lines} onToggle={toggleLine} onAmountChange={changeLineAmount} />
          )}

          {customerId && (
            <>
              <div className="field">
                <label htmlFor="pay-amount">How much did you receive?</label>
                <input
                  id="pay-amount"
                  className="text-input"
                  type="text"
                  inputMode="decimal"
                  placeholder="0.00"
                  value={amount}
                  onChange={(ev) => {
                    setAmountTouched(true);
                    setAmount(ev.target.value);
                  }}
                />
                {amtErr && <p className="field-error">{amtErr}</p>}
                {!amtErr && overApplied && (
                  <p className="field-error">You're applying more than the amount received.</p>
                )}
                {!amtErr && !overApplied && lines.some((l) => l.checked) && (
                  <p className="sub">
                    Applying ${centsToEntered(appliedCents)} of ${centsToEntered(amountCents)}
                    {remainderCents > 0n ? ` — $${centsToEntered(remainderCents)} kept as credit.` : "."}
                  </p>
                )}
              </div>

              <SearchablePicker
                id="pay-deposit"
                label="Where did it land?"
                placeholder="Search money accounts…"
                groups={moneyGroups}
                value={depositAccountId}
                onChange={setDepositAccountId}
              />

              <div className="field">
                <label htmlFor="pay-date">Date</label>
                <input
                  id="pay-date"
                  className="text-input"
                  type="date"
                  value={date}
                  onChange={(ev) => setDate(ev.target.value)}
                />
              </div>

              {previewSentence() && <p className="record-preview">{previewSentence()}</p>}

              <div className="wizard-actions">
                <button type="button" className="btn" disabled={!formValid || submitting} onClick={() => void handleSubmit()}>
                  {submitting ? "Recording…" : "Record payment"}
                </button>
              </div>
            </>
          )}
        </div>
      )}

      {stage.name === "success" && (
        <div className="card success-screen">
          <h2>Done</h2>
          <p>{stage.result.summary}</p>
          <div className="wizard-actions">
            <button type="button" className="btn" onClick={resetToBlank}>
              Record another payment
            </button>
            <button type="button" className="btn" onClick={() => navigate({ view: "payment", id: stage.result.paymentId })}>
              View payment
            </button>
          </div>
          {stage.appliedInvoices.length > 0 && (
            <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid var(--grid)" }}>
              <p className="sub">Invoices this was applied to:</p>
              <div className="reversal-links">
                {stage.appliedInvoices.map((inv) => (
                  <button
                    key={inv.id}
                    type="button"
                    className="acct-link"
                    onClick={() => navigate({ view: "invoice", id: inv.id })}
                  >
                    {inv.no ? `Invoice #${inv.no}` : "Draft invoice"}
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
