// Pure accrual A/P matching logic — no DB/client imports so it stays unit-testable.
//
// A bank debit to a vendor either settles an existing bill (the expense was already
// booked when the bill was entered → the payment must only clear A/P) or it is a
// direct expense (no matching bill → book it now).

export const BILL_MATCH_WINDOW_DAYS = 30;
export const BILL_MATCH_TOLERANCE = 0.01;
// Bill statuses a payment can still settle (excludes paid / void / draft).
export const SETTLEABLE_BILL_STATUSES = ["open", "overdue", "partially_paid"];

export type BillMatchInput = {
  companyId: string;
  vendorId: string | null;
  amount: number;
  date: string; // YYYY-MM-DD
};

export type MatchableBill = {
  total: number | string;
  paid_amount?: number | string | null;
  issue_date?: string | null;
  due_date?: string | null;
};

function shiftDays(date: string, days: number): Date {
  const d = new Date(date + "T00:00:00");
  d.setDate(d.getDate() + days);
  return d;
}

/**
 * Pure predicate: does a single bill settle this bank debit?
 *   • debit date within ±30 days of the bill's due date (falling back to issue date)
 *   • remaining balance equals the debit within $0.01 (full settlement), or the
 *     debit is ≤ the remaining balance (partial payment)
 * Status/vendor filtering happens in the query; this only judges amount + date.
 */
export function debitMatchesBill(bill: MatchableBill, input: Pick<BillMatchInput, "amount" | "date">): boolean {
  if (!(input.amount > 0)) return false;
  const remaining = Number(bill.total) - Number(bill.paid_amount ?? 0);
  if (remaining <= BILL_MATCH_TOLERANCE) return false;
  const ref = new Date(((bill.due_date ?? bill.issue_date) ?? input.date) + "T00:00:00");
  if (ref < shiftDays(input.date, -BILL_MATCH_WINDOW_DAYS) || ref > shiftDays(input.date, BILL_MATCH_WINDOW_DAYS)) return false;
  const full = Math.abs(remaining - input.amount) <= BILL_MATCH_TOLERANCE;
  const partial = input.amount <= remaining + BILL_MATCH_TOLERANCE;
  return full || partial;
}
