// "I paid a bill" — AP Task 5. Reached three ways, all landing on this same
// screen (#pay-bills?vendorId=&billId=):
//   - Record screen's "Money went out" card -> "A bill I owe" (Record.tsx) —
//     no params, starts at a blank vendor picker.
//   - #bills' "Pay bills" button (BillsScreen.tsx) — no params (or a
//     pre-set vendorId if a vendor filter is already active).
//   - An open bill's own "Pay this bill" button (BillDetail.tsx) — both
//     vendorId AND billId, so only THAT bill starts checked.
//
// Flow: pick a vendor -> its open bills load as checkbox rows (full open
// balance prefilled per row, editable — see openBillLines.ts) -> pick how
// the money left (print a check / a check already written / electronic) ->
// pick a money account -> date -> confirm sentence -> submit. The "amount"
// field defaults to the sum of every CHECKED line (same touched/untouched
// convention as ReceivePayment.tsx's) but stays independently editable,
// since an overpayment or an entirely-unapplied payment both need an amount
// that isn't just the sum of applications. POSTs to /api/c/bill-payments
// (server/src/billPayments.ts) via pay_bills() — every derived figure in the
// confirm sentence and the running "Applying $X of $Y" line is computed here
// in whole-cents BigInt arithmetic (openBillLines.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/ReceivePayment.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 { OpenBillLinesTable } from "./OpenBillLinesTable";
import {
  buildApplications,
  centsOfEntered,
  centsToEntered,
  lineAmountError,
  seedLines,
  sumCheckedCents,
  type OpenBillApiRow,
  type OpenBillLine,
} from "./openBillLines";

// Same shape as Record.tsx's AMOUNT_RE / server/src/billPayments.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 paid.";
  if (!AMOUNT_RE.test(trimmed) || !/[1-9]/.test(trimmed)) {
    return "Enter an amount like 120 or 120.50.";
  }
  return null;
}

// A hand-entered check number: same shape as billPayments.ts's checkNoSchema
// (whole number, floor of 1, capped at 18 digits).
const CHECK_NO_RE = /^\d{1,18}$/;

function checkNoError(raw: string): string | null {
  const trimmed = raw.trim();
  if (trimmed === "") return "Enter the check number already written.";
  if (!CHECK_NO_RE.test(trimmed) || !/[1-9]/.test(trimmed)) {
    return "Enter a whole check number of 1 or more.";
  }
  return null;
}

type MethodChoice = "print" | "manual" | "electronic";

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

interface OpenBillsResponse {
  bills: OpenBillApiRow[];
  unappliedCredit: string;
}

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

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

export function PayBills({
  navigate,
  initialVendorId,
  initialBillId,
}: {
  navigate: (target: NavTarget) => void;
  initialVendorId?: string;
  initialBillId?: string;
}) {
  const { selectedCompanyId } = useCompany();
  const { data: vendors } = useApiGet<VendorOption[]>("/api/c/vendors", selectedCompanyId);
  const { data: accounts } = useApiGet<ApiAccount[]>("/api/c/accounts", selectedCompanyId);

  const [stage, setStage] = useState<Stage>({ name: "form" });
  const [vendorId, setVendorId] = useState<string | null>(initialVendorId ?? null);
  const [date, setDate] = useState(todayIso());
  const [amount, setAmount] = useState("");
  const [amountTouched, setAmountTouched] = useState(false);
  const [moneyAccountId, setMoneyAccountId] = useState<string | null>(null);
  const [method, setMethod] = useState<MethodChoice>("print");
  const [checkNoInput, setCheckNoInput] = useState("");
  const [checkNoTouched, setCheckNoTouched] = useState(false);
  const [lines, setLines] = useState<OpenBillLine[]>([]);
  const [seededFor, setSeededFor] = useState<string | null>(null);
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  const openBillsPath = vendorId ? `/api/c/vendors/${vendorId}/open-bills` : null;
  const {
    data: openBills,
    loading: openBillsLoading,
    error: openBillsError,
    retry: retryOpenBills,
  } = useApiGet<OpenBillsResponse>(openBillsPath, selectedCompanyId);

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

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

  const vendorGroups: PickerGroup[] = [
    { label: "Vendors", options: (vendors ?? []).map((v) => ({ id: v.id, label: v.name })) },
  ];
  // Asset-restricted "paid from" picker under the standard sections (Bank
  // accounts / ... — Chart Management Task 3); the restriction is unchanged.
  const moneyGroups: PickerGroup[] = accountPickerGroups(accounts, ["asset"]);

  const selectedVendor = vendors?.find((v) => v.id === vendorId) ?? null;
  const moneyAccountName = accounts?.find((a) => a.id === moneyAccountId)?.name ?? null;

  function selectVendor(id: string) {
    setVendorId(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 checkNoErr = method === "manual" ? checkNoError(checkNoInput) : null;

  const formValid =
    vendorId !== null &&
    moneyAccountId !== null &&
    date !== "" &&
    amtErr === null &&
    linesValid &&
    !overApplied &&
    (method !== "manual" || checkNoErr === null);

  function methodPhrase(): string {
    if (method === "print") return "by a check queued to print";
    if (method === "manual") {
      const n = checkNoInput.trim();
      return n && moneyAccountName ? `by check #${n} from ${moneyAccountName}` : "by a check";
    }
    return "electronically";
  }

  function previewSentence(): string | null {
    if (!formValid || !selectedVendor || !moneyAccountName) return null;
    const amountLabel = `$${centsToEntered(amountCents)}`;
    const checked = lines.filter((l) => l.checked && l.amount.trim() !== "");
    const methodBit = methodPhrase();
    if (checked.length === 0) {
      return `This will pay ${amountLabel} to ${selectedVendor.name} ${methodBit} — kept entirely as vendor credit.`;
    }
    const label = checked.length === 1 ? "bill" : "bills";
    const parts = checked.map((l) => `#${l.refNo ?? l.billId}`).join(", ");
    let sentence = `This will pay ${amountLabel} to ${selectedVendor.name} ${methodBit} — applied to ${label} ${parts}.`;
    if (remainderCents > 0n) {
      sentence += ` $${centsToEntered(remainderCents)} kept as vendor credit.`;
    }
    return sentence;
  }

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

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

  function resetToBlank() {
    setStage({ name: "form" });
    setVendorId(null);
    setDate(todayIso());
    setAmount("");
    setAmountTouched(false);
    setMoneyAccountId(null);
    setMethod("print");
    setCheckNoInput("");
    setCheckNoTouched(false);
    setLines([]);
    setSeededFor(null);
    setSubmitError(null);
  }

  async function handleSubmit() {
    if (!formValid || !selectedCompanyId || !vendorId || !moneyAccountId) return;
    setSubmitting(true);
    setSubmitError(null);
    const appliedBills = lines
      .filter((l) => l.checked && l.amount.trim() !== "")
      .map((l) => ({ id: l.billId, no: l.refNo }));
    try {
      const result = await apiPost<PaymentResult>(
        "/api/c/bill-payments",
        {
          vendorId,
          paymentDate: date,
          amount: amount.trim(),
          moneyAccountId,
          method: method === "electronic" ? "electronic" : "check",
          checkNo: method === "manual" ? checkNoInput.trim() : undefined,
          toPrint: method === "print" ? true : undefined,
          applications: buildApplications(lines),
        },
        selectedCompanyId,
      );
      setStage({ name: "success", result, appliedBills, queued: method === "print" });
    } 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>Pay a bill</h1>
      </header>

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

          <SearchablePicker
            id="pay-bill-vendor"
            label="Vendor"
            placeholder="Search vendors…"
            groups={vendorGroups}
            value={vendorId}
            onChange={selectVendor}
          />

          {vendorId && openBillsError && <ErrorBanner message={openBillsError} onRetry={retryOpenBills} />}
          {vendorId && openBillsLoading && !openBills && (
            <p className="msg-inline">Loading open bills&hellip;</p>
          )}

          {vendorId && openBills && openBills.unappliedCredit !== "0.0000" && (
            <p className="sub">
              {selectedVendor?.name ?? "This vendor"} already has {fmtMoney(openBills.unappliedCredit)} in
              unapplied credit from previous payments.
            </p>
          )}

          {vendorId && openBills && openBills.bills.length === 0 && (
            <p className="sub">No open bills for this vendor — this payment will be kept as credit.</p>
          )}

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

          {vendorId && (
            <>
              <div className="field">
                <label htmlFor="pay-bill-amount">How much did you pay?</label>
                <input
                  id="pay-bill-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 paid.</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>

              <div className="field">
                <label>How did the money leave?</label>
                <div className="method-choice">
                  <label className="method-option">
                    <input
                      type="radio"
                      name="pay-method"
                      checked={method === "print"}
                      onChange={() => setMethod("print")}
                    />
                    <span>Print a check</span>
                  </label>
                  <label className="method-option">
                    <input
                      type="radio"
                      name="pay-method"
                      checked={method === "manual"}
                      onChange={() => setMethod("manual")}
                    />
                    <span>Check I already wrote</span>
                  </label>
                  <label className="method-option">
                    <input
                      type="radio"
                      name="pay-method"
                      checked={method === "electronic"}
                      onChange={() => setMethod("electronic")}
                    />
                    <span>Electronic or card</span>
                  </label>
                </div>
              </div>

              {method === "manual" && (
                <div className="field">
                  <label htmlFor="pay-bill-checkno">Check number</label>
                  <input
                    id="pay-bill-checkno"
                    className="text-input"
                    type="text"
                    inputMode="numeric"
                    value={checkNoInput}
                    onChange={(ev) => setCheckNoInput(ev.target.value)}
                    onBlur={() => setCheckNoTouched(true)}
                  />
                  {checkNoTouched && checkNoErr && <p className="field-error">{checkNoErr}</p>}
                </div>
              )}

              <SearchablePicker
                id="pay-bill-account"
                label="Paid from?"
                placeholder="Search money accounts…"
                groups={moneyGroups}
                value={moneyAccountId}
                onChange={setMoneyAccountId}
              />

              <div className="field">
                <label htmlFor="pay-bill-date">Date</label>
                <input
                  id="pay-bill-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…" : "Pay bill"}
                </button>
              </div>
            </>
          )}
        </div>
      )}

      {stage.name === "success" && (
        <div className="card success-screen">
          <h2>Done</h2>
          <p>{stage.result.summary}</p>
          {stage.queued && (
            <p className="sub">
              It's waiting in{" "}
              <button type="button" className="acct-link" onClick={() => navigate("checks")}>
                Checks to print
              </button>
              .
            </p>
          )}
          <div className="wizard-actions">
            <button type="button" className="btn" onClick={resetToBlank}>
              Pay another bill
            </button>
            <button
              type="button"
              className="btn"
              onClick={() => navigate({ view: "bill-payment", id: stage.result.paymentId })}
            >
              View payment
            </button>
          </div>
          {stage.appliedBills.length > 0 && (
            <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid var(--grid)" }}>
              <p className="sub">Bills this was applied to:</p>
              <div className="reversal-links">
                {stage.appliedBills.map((b) => (
                  <button key={b.id} type="button" className="acct-link" onClick={() => navigate({ view: "bill", id: b.id })}>
                    {/* A bill's refNo is optional and vendor-supplied, unlike
                        an invoice's server-assigned number — a blank one here
                        just means "no reference", never "draft" (this list
                        only ever holds bills that were just paid, so they're
                        never drafts). Mirrors BillDetail.tsx/BillsScreen.tsx's
                        identical fix. */}
                    {b.no ? `Bill #${b.no}` : "Bill (no reference)"}
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
