import type { Expense, Participant, ParticipantId, Settlement } from '../types'; import { computeSplit } from './split'; export function calculateBalances( participants: Participant[], expenses: Expense[], ): { netBalances: Record; settlements: Settlement[] } { const netBalances: Record = {}; for (const participant of participants) { netBalances[participant.id] = 0; } // A settlement payment (fromId pays toId) is folded in as a regular // expense: payerId = fromId, participantIds = [toId]. The math is // identical to a one-person split, so no separate handling is needed here. for (const expense of expenses) { netBalances[expense.payerId] = (netBalances[expense.payerId] ?? 0) + expense.amountCents; const shares = computeSplit(expense.amountCents, expense.participantIds); for (const [participantId, share] of Object.entries(shares)) { const id = Number(participantId); netBalances[id] = (netBalances[id] ?? 0) - share; } } const settlements = simplifyDebts(netBalances); return { netBalances, settlements }; } function simplifyDebts( netBalances: Record, ): Settlement[] { const creditors: { id: ParticipantId; amount: number }[] = []; const debtors: { id: ParticipantId; amount: number }[] = []; for (const [id, amount] of Object.entries(netBalances)) { if (amount > 0) creditors.push({ id: Number(id), amount }); else if (amount < 0) debtors.push({ id: Number(id), amount: -amount }); } creditors.sort((a, b) => b.amount - a.amount); debtors.sort((a, b) => b.amount - a.amount); const settlements: Settlement[] = []; let ci = 0; let di = 0; while (ci < creditors.length && di < debtors.length) { const creditor = creditors[ci]!; const debtor = debtors[di]!; const amount = Math.min(creditor.amount, debtor.amount); if (amount > 0) { settlements.push({ fromId: debtor.id, toId: creditor.id, amountCents: amount, }); } creditor.amount -= amount; debtor.amount -= amount; if (creditor.amount === 0) ci++; if (debtor.amount === 0) di++; } return settlements; }