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 }