// Billing: invoices (receivables), supplier payables, payments log, aging, client statements
const addDays = (iso, n) => { const d = new Date(iso); d.setDate(d.getDate() + n); return d.toISOString(); };
const invPaid = (inv) => inv.payments.reduce((s, p) => s + p.amount, 0);
const invBalance = (inv) => Math.max(0, +(inv.amount - invPaid(inv)).toFixed(2));
const invStatus = (inv) => { if (inv.void) return 'Void'; const b = invBalance(inv); if (b <= 0.005) return 'Paid'; if (new Date(inv.dueAt) < TODAY) return 'Overdue'; if (invPaid(inv) > 0) return 'Partially paid'; return inv.sentAt ? 'Sent' : 'Draft'; };
const invDaysLate = (inv) => Math.max(0, Math.floor((TODAY - new Date(inv.dueAt)) / 86400000));
const agingBucket = (inv) => { const d = invDaysLate(inv); return d <= 0 ? 'Current' : d <= 30 ? '1–30' : d <= 60 ? '31–60' : '60+'; };
const INV_TONE = { Draft:'tag', Sent:'tag', 'Partially paid':'amber', Paid:'green', Overdue:'red', Void:'tag' };
const PAYABLE_TONE = { Pending:'amber', Scheduled:'tag', Paid:'green' };

// Seed invoices/payables from existing orders (idempotent migration)
const seedBilling = (s) => {
  if (s.invoices) return s;
  const net = s.settings?.netDays || 14; let n = 40; let pn = 20;
  s.invoices = []; s.payables = [];
  s.orders.filter(o => o.split).sort((a, b) => new Date(a.split.lockedAt) - new Date(b.split.lockedAt)).forEach(o => {
    const issued = o.split.lockedAt; const id = `INV-${++n}`; const inv = { id, orderId: o.id, clientId: o.clientId, amount: +o.split.total.toFixed(2), issuedAt: issued, sentAt: addDays(issued, 0), dueAt: addDays(issued, net), payments: [], void: o.status === 'Cancelled' };
    if (o.paid) inv.payments.push({ id: `PMT-${++pn}`, at: o.paidAt, amount: inv.amount, method: 'Wire', ref: `WT-${(1000 + pn * 37) % 9000 + 1000}`, by: 'Laura Parker' });
    s.invoices.push(inv);
    if (o.paid) { const dueP = addDays(o.paidAt, 7); const old = new Date(o.paidAt) < new Date(TODAY.getTime() - 86400000 * 25); s.payables.push({ id: `PAY-${++pn}`, orderId: o.id, supplierId: o.supplierId, amount: +o.split.supplier.toFixed(2), createdAt: o.paidAt, dueAt: dueP, status: old ? 'Paid' : (new Date(dueP) < TODAY ? 'Scheduled' : 'Pending'), paidAt: old ? addDays(o.paidAt, 5) : null, ref: old ? `SUP-${(2000 + pn * 53) % 9000}` : '' }); }
  });
  // one partial payment + one disputed note for realism
  const part = s.invoices.find(i => i.orderId === 'SO-1059'); if (part) part.payments.push({ id: 'PMT-91', at: addDays(part.issuedAt, 9), amount: 2000, method: 'ACH', ref: 'ACH-55012', by: 'Laura Parker', note: 'Partial — remainder on arrival' });
  const disp = s.invoices.find(i => i.orderId === 'SO-1047'); if (disp) disp.note = 'Client disputes 4 damaged sinks; credit pending inspection.';
  return s;
};

const Aging = ({ invs, onPick }) => { const b = ['Current','1–30','31–60','60+'].map(k => ({ k, v: invs.filter(i => agingBucket(i) === k).reduce((s, i) => s + invBalance(i), 0), n: invs.filter(i => agingBucket(i) === k).length })); const max = Math.max(1, ...b.map(x => x.v));
  return <div className="aging">{b.map(x => <button key={x.k} className="aging-b" onClick={() => onPick(x.k)}><span className="aging-l">{x.k}<span className="mute"> · {x.n}</span></span><div className="aging-t"><div className={`aging-f ${x.k === 'Current' ? 'ok' : x.k === '60+' ? 'bad' : 'warn'}`} style={{ width: (x.v / max * 100) + '%' }}></div></div><span className="mono">{fmt0(x.v)}</span></button>)}</div>; };

const InvoiceRow = ({ inv, st, onOpen }) => { const c = st.clients.find(x => x.id === inv.clientId); const s = invStatus(inv); const late = invDaysLate(inv);
  return <tr className={`row${s === 'Overdue' ? ' row-high' : s === 'Partially paid' ? ' row-warn' : ''}`} onClick={() => onOpen(inv.id)}>
    <td><span className="mono strong">{inv.id}</span></td><td>{c?.name}</td><td><span className="mono">{inv.orderId}</span></td><td>{fdate(inv.issuedAt)}</td>
    <td className={s === 'Overdue' ? 'red' : ''}>{fdate(inv.dueAt)}{s === 'Overdue' && <span className="mute"> · {late}d</span>}</td>
    <td className="num mono">{fmt(inv.amount)}</td><td className="num mono">{fmt(invPaid(inv))}</td><td className="num mono strong">{fmt(invBalance(inv))}</td><td><Pill tone={INV_TONE[s]}>{s}</Pill></td></tr>; };

const InvoiceModal = ({ st, inv, act, nav, user, onClose }) => {
  const o = st.orders.find(x => x.id === inv.orderId); const c = st.clients.find(x => x.id === inv.clientId); const s = invStatus(inv); const bal = invBalance(inv);
  const [pay, setPay] = useState(null); const canEdit = user.role !== 'client';
  return <Modal title={<span className="mono">{inv.id}</span>} onClose={onClose} width={720}>
    <div className="inv">
      <div className="inv-h"><div><b>{c?.name}</b><div className="mute small">{c?.contact} · {c?.email}</div></div><div className="inv-s"><Pill tone={INV_TONE[s]}>{s}</Pill><span className="mute small">Issued {fdateY(inv.issuedAt)} · Due {fdateY(inv.dueAt)}</span></div></div>
      {o && <table className="t"><thead><tr><th>Description</th><th className="num">Qty</th><th className="num">Unit price</th><th className="num">Total</th></tr></thead><tbody>{o.items.map((i, k) => <tr key={k}><td className="td-desc">{i.desc}</td><td className="num mono">{i.qty.toLocaleString()}</td><td className="num mono">{fmt(i.price)}</td><td className="num mono">{fmt(i.qty * i.price)}</td></tr>)}</tbody>
        <tfoot><tr><td colSpan="3">Invoice total</td><td className="num mono strong">{fmt(inv.amount)}</td></tr><tr><td colSpan="3">Paid</td><td className="num mono">{fmt(invPaid(inv))}</td></tr><tr><td colSpan="3"><b>Balance due</b></td><td className={`num mono strong${bal > 0 ? ' red' : ''}`}>{fmt(bal)}</td></tr></tfoot></table>}
      {inv.note && <div className="note note-warn">{I.alert} {inv.note}</div>}
      <div><span className="kicker">Payments</span>{inv.payments.length === 0 ? <Empty>No payments recorded.</Empty> : <ul className="sl">{inv.payments.map(p => <li key={p.id}><span><b>{fmt(p.amount)}</b> <span className="mute">· {p.method} · {p.ref}{p.note ? ' · ' + p.note : ''}</span></span><span className="mute">{fdateY(p.at)} · {p.by}</span></li>)}</ul>}</div>
      {canEdit && !inv.void && <div className="admin-row">
        {bal > 0 && <Btn v="primary" icon={I.dollar} onClick={() => setPay({ amount: bal, method: 'Wire', at: TODAY.toISOString().slice(0, 10), ref: '', note: '' })} tour="record-payment">Record payment</Btn>}
        {!inv.sentAt && <Btn onClick={() => act.sendInvoice(inv.id)}>Mark as sent</Btn>}
        {s === 'Overdue' && <Btn onClick={() => act.remind(inv.id)}>Log reminder</Btn>}
        <Btn onClick={() => { nav('order', { id: inv.orderId }); onClose(); }}>Open order</Btn>
        {user.role === 'admin' && bal > 0 && <Btn className="btn btn-outline btn-danger" onClick={() => { if (confirm(`Void ${inv.id}?`)) { act.voidInvoice(inv.id); onClose(); } }}>Void</Btn>}
      </div>}
      {pay && <div className="pay-form"><span className="kicker">New payment</span><div className="form-grid">
        <Field label="Amount" req><input className="inp inp-n wide" type="number" step="0.01" max={bal} value={pay.amount} onChange={e => setPay({ ...pay, amount: +e.target.value })}></input></Field>
        <Field label="Method"><Select value={pay.method} onChange={v => setPay({ ...pay, method: v })} options={['Wire','ACH','Check','Card','Cash']}></Select></Field>
        <Field label="Date"><input className="inp" type="date" value={pay.at} onChange={e => setPay({ ...pay, at: e.target.value })}></input></Field>
        <Field label="Reference" req><input className="inp" placeholder="WT-4471 / check no." value={pay.ref} onChange={e => setPay({ ...pay, ref: e.target.value })}></input></Field>
        <Field label="Note"><input className="inp" placeholder="optional" value={pay.note} onChange={e => setPay({ ...pay, note: e.target.value })}></input></Field></div>
        {pay.amount < bal && pay.amount > 0 && <div className="note note-warn">{I.alert} Partial payment — {fmt(bal - pay.amount)} will remain open.</div>}
        <div className="admin-row"><Btn v="primary" disabled={!(pay.amount > 0) || pay.amount > bal + 0.005 || !pay.ref.trim()} onClick={() => { act.recordPayment(inv.id, pay); setPay(null); }}>Save payment</Btn><Btn onClick={() => setPay(null)}>Cancel</Btn></div></div>}
    </div>
  </Modal>;
};

const Billing = ({ st, nav, act, user, q, params = {} }) => {
  const [tab, setTab] = useState(params.tab || 'ar'); const [f, setF] = useState({ status: '', client: params.client || '', bucket: '' }); const [open, setOpen] = useState(params.id || null);
  const [toast, show] = useToast();
  const invs = st.invoices.filter(i => !i.void || f.status === 'Void');
  const openInvs = invs.filter(i => invBalance(i) > 0 && !i.void);
  const overdue = openInvs.filter(i => invStatus(i) === 'Overdue');
  const collected30 = st.invoices.flatMap(i => i.payments).filter(p => new Date(p.at) > new Date(TODAY.getTime() - 86400000 * 30)).reduce((s, p) => s + p.amount, 0);
  const payables = st.payables; const payDue = payables.filter(p => p.status !== 'Paid');
  const list = invs.filter(i => (!f.status || invStatus(i) === f.status) && (!f.client || i.clientId === f.client) && (!f.bucket || (invBalance(i) > 0 && agingBucket(i) === f.bucket)) && (!q || (i.id + i.orderId + st.clients.find(c => c.id === i.clientId)?.name).toLowerCase().includes(q.toLowerCase()))).sort((a, b) => new Date(b.issuedAt) - new Date(a.issuedAt));
  const payments = st.invoices.flatMap(i => i.payments.map(p => ({ ...p, inv: i }))).sort((a, b) => new Date(b.at) - new Date(a.at));
  const statements = st.clients.map(c => { const is = st.invoices.filter(i => i.clientId === c.id && !i.void); const billed = is.reduce((s, i) => s + i.amount, 0); const paid = is.reduce((s, i) => s + invPaid(i), 0); const od = is.filter(i => invStatus(i) === 'Overdue'); return { c, n: is.length, billed, paid, bal: billed - paid, od: od.length, odAmt: od.reduce((s, i) => s + invBalance(i), 0), oldest: Math.max(0, ...od.map(invDaysLate)) }; }).sort((a, b) => b.bal - a.bal);
  const exportAR = () => { download('soflolg-receivables.csv', toCSV([['Invoice','Client','Order','Issued','Due','Status','Days late','Amount','Paid','Balance'], ...openInvs.map(i => [i.id, st.clients.find(c => c.id === i.clientId)?.name, i.orderId, fdateY(i.issuedAt), fdateY(i.dueAt), invStatus(i), invDaysLate(i), i.amount.toFixed(2), invPaid(i).toFixed(2), invBalance(i).toFixed(2)])])); show('Receivables exported'); };
  const exportPay = () => { download('soflolg-payables.csv', toCSV([['Payable','Supplier','Order','Due','Status','Amount','Reference'], ...payDue.map(p => [p.id, st.suppliers.find(s => s.id === p.supplierId)?.name, p.orderId, fdateY(p.dueAt), p.status, p.amount.toFixed(2), p.ref])])); show('Pay run exported'); };
  const inv = open && st.invoices.find(i => i.id === open);
  return <div className="page">
    <Toast msg={toast}></Toast>
    <PageHead title="Billing" kicker="Invoices, payments, payables"><Btn icon={I.download} onClick={tab === 'ap' ? exportPay : exportAR}>{tab === 'ap' ? 'Export pay run (CSV)' : 'Export receivables (CSV)'}</Btn></PageHead>
    <div className="stats" data-tour="bill-stats">
      <Stat label="Outstanding" value={fmt0(openInvs.reduce((s, i) => s + invBalance(i), 0))} sub={`${openInvs.length} open invoices`} onClick={() => { setTab('ar'); setF({ status: '', client: '', bucket: '' }); }}></Stat>
      <Stat label="Overdue" value={fmt0(overdue.reduce((s, i) => s + invBalance(i), 0))} sub={`${overdue.length} invoices · oldest ${Math.max(0, ...overdue.map(invDaysLate))}d`} tone={overdue.length ? 'red' : ''} onClick={() => { setTab('ar'); setF({ status: 'Overdue', client: '', bucket: '' }); }}></Stat>
      <Stat label="Collected · 30d" value={fmt0(collected30)} sub={`${payments.filter(p => new Date(p.at) > new Date(TODAY.getTime() - 86400000 * 30)).length} payments`} onClick={() => setTab('pmts')}></Stat>
      <Stat label="Owed to suppliers" value={fmt0(payDue.reduce((s, p) => s + p.amount, 0))} sub={`${payDue.length} payables · ${payDue.filter(p => new Date(p.dueAt) < TODAY).length} past due`} tone={payDue.some(p => new Date(p.dueAt) < TODAY) ? 'warn' : ''} onClick={() => setTab('ap')}></Stat>
    </div>
    <div className="filters">{[['ar','Receivables'],['ap','Supplier payables'],['pmts','Payments log'],['stmt','Client balances']].map(([k, l]) => <button key={k} className={`chip${tab === k ? ' on' : ''}`} onClick={() => setTab(k)}>{l}</button>)}</div>
    {tab === 'ar' && <>
      <Card title="Aging · open balances" tour="aging"><Aging invs={openInvs} onPick={(b) => setF({ ...f, bucket: f.bucket === b ? '' : b, status: '' })}></Aging></Card>
      <div className="filters"><Select value={f.client} onChange={v => setF({ ...f, client: v })} options={st.clients} placeholder="All clients"></Select><Select value={f.status} onChange={v => setF({ ...f, status: v, bucket: '' })} options={['Draft','Sent','Partially paid','Overdue','Paid','Void']} placeholder="All statuses"></Select>{f.bucket && <button className="chip on" onClick={() => setF({ ...f, bucket: '' })}>Aging {f.bucket} ×</button>}{(f.client || f.status) && <button className="chip-x" onClick={() => setF({ status: '', client: '', bucket: '' })}>Clear</button>}</div>
      <Card pad={false} tour="inv-table">{list.length === 0 ? <Empty>No invoices match.</Empty> : <div className="tw"><table className="t"><thead><tr><th>Invoice</th><th>Client</th><th>Order</th><th>Issued</th><th>Due</th><th className="num">Amount</th><th className="num">Paid</th><th className="num">Balance</th><th>Status</th></tr></thead><tbody>{list.map(i => <InvoiceRow key={i.id} inv={i} st={st} onOpen={setOpen}></InvoiceRow>)}</tbody></table></div>}</Card>
    </>}
    {tab === 'ap' && <Card title="Supplier payables · from client-paid orders" pad={false} tour="payables">{payables.length === 0 ? <Empty>Nothing owed.</Empty> : <div className="tw"><table className="t"><thead><tr><th>Payable</th><th>Supplier</th><th>Order</th><th>Due</th><th className="num">Amount</th><th>Status</th><th>Reference</th><th></th></tr></thead><tbody>
      {[...payables].sort((a, b) => (a.status === 'Paid') - (b.status === 'Paid') || new Date(a.dueAt) - new Date(b.dueAt)).map(p => { const late = p.status !== 'Paid' && new Date(p.dueAt) < TODAY; return <tr key={p.id} className={late ? 'row-warn' : ''}><td><span className="mono strong">{p.id}</span></td><td>{st.suppliers.find(s => s.id === p.supplierId)?.name}</td><td><button className="lnk mono" onClick={() => nav('order', { id: p.orderId })}>{p.orderId}</button></td><td className={late ? 'red' : ''}>{fdate(p.dueAt)}</td><td className="num mono strong">{fmt(p.amount)}</td><td><Pill tone={PAYABLE_TONE[p.status]}>{p.status}</Pill></td><td className="mono">{p.ref || <span className="mute">—</span>}</td>
        <td className="num">{p.status === 'Pending' && user.role !== 'client' && <Btn sm onClick={() => act.payableStatus(p.id, 'Scheduled')}>Schedule</Btn>}{p.status !== 'Paid' && user.role !== 'client' && <Btn sm v="dark" onClick={() => { const ref = prompt('Payment reference (wire / check no.)'); if (ref) act.payableStatus(p.id, 'Paid', ref); }}>Mark paid</Btn>}{p.status === 'Paid' && <span className="mute small">{fdate(p.paidAt)}</span>}</td></tr>; })}
    </tbody></table></div>}</Card>}
    {tab === 'pmts' && <Card title="Every payment received" pad={false}>{payments.length === 0 ? <Empty>No payments yet.</Empty> : <div className="tw"><table className="t"><thead><tr><th>Date</th><th>Payment</th><th>Invoice</th><th>Client</th><th>Method</th><th>Reference</th><th className="num">Amount</th><th>Recorded by</th></tr></thead><tbody>
      {payments.map(p => <tr key={p.id} className="row" onClick={() => setOpen(p.inv.id)}><td>{fdateY(p.at)}</td><td><span className="mono">{p.id}</span></td><td><span className="mono">{p.inv.id}</span></td><td>{st.clients.find(c => c.id === p.inv.clientId)?.name}</td><td>{p.method}</td><td className="mono">{p.ref}</td><td className="num mono strong">{fmt(p.amount)}</td><td className="mute">{p.by}</td></tr>)}
    </tbody></table></div>}</Card>}
    {tab === 'stmt' && <Card title="Balance by client" pad={false} tour="balances"><div className="tw"><table className="t"><thead><tr><th>Client</th><th className="num">Invoices</th><th className="num">Billed</th><th className="num">Paid</th><th className="num">Balance</th><th>Overdue</th><th></th></tr></thead><tbody>
      {statements.map(r => <tr key={r.c.id} className={r.od ? 'row-high' : ''}><td><b>{r.c.name}</b><div className="mute small">{r.c.contact}</div></td><td className="num">{r.n}</td><td className="num mono">{fmt(r.billed)}</td><td className="num mono">{fmt(r.paid)}</td><td className="num mono strong">{fmt(r.bal)}</td><td>{r.od ? <Pill tone="red">{r.od} · {fmt0(r.odAmt)} · {r.oldest}d</Pill> : <Pill tone="green">Current</Pill>}</td><td className="num"><Btn sm onClick={() => { setTab('ar'); setF({ status: '', client: r.c.id, bucket: '' }); }}>Invoices</Btn></td></tr>)}
    </tbody></table></div></Card>}
    {inv && <InvoiceModal st={st} inv={inv} act={act} nav={nav} user={user} onClose={() => setOpen(null)}></InvoiceModal>}
  </div>;
};

// Alerts strip for the dashboard
const BillingAlerts = ({ st, nav }) => {
  const od = st.invoices.filter(i => invStatus(i) === 'Overdue'); const partial = st.invoices.filter(i => invStatus(i) === 'Partially paid'); const payLate = st.payables.filter(p => p.status !== 'Paid' && new Date(p.dueAt) < TODAY);
  const unbilled = st.orders.filter(o => o.split && o.status !== 'Cancelled' && !st.invoices.some(i => i.orderId === o.id));
  if (!od.length && !payLate.length && !unbilled.length && !partial.length) return null;
  return <div className="page page-alerts"><Card title="Billing · needs attention" className="card-bill" tour="billing-alerts"><ul className="al">
    {od.map(i => <li key={i.id} onClick={() => nav('billing', { id: i.id })}><span className="al-i red">{I.dollar}</span><span className="al-t"><b>{i.id}</b> {st.clients.find(c => c.id === i.clientId)?.name} · {invDaysLate(i)}d overdue <span className="mute">· {i.orderId}</span></span><span className="al-a">{fmt0(invBalance(i))} due{I.right}</span></li>)}
    {partial.map(i => <li key={i.id} onClick={() => nav('billing', { id: i.id })}><span className="al-i warn">{I.dollar}</span><span className="al-t"><b>{i.id}</b> partially paid <span className="mute">· {fmt0(invPaid(i))} of {fmt0(i.amount)}</span></span><span className="al-a">{fmt0(invBalance(i))} open{I.right}</span></li>)}
    {payLate.map(p => <li key={p.id} onClick={() => nav('billing', { tab: 'ap' })}><span className="al-i warn">{I.box}</span><span className="al-t"><b>{p.id}</b> supplier payment past due · {st.suppliers.find(s => s.id === p.supplierId)?.name} <span className="mute">· {p.orderId}</span></span><span className="al-a">Pay {fmt0(p.amount)}{I.right}</span></li>)}
    {unbilled.map(o => <li key={o.id} onClick={() => nav('order', { id: o.id })}><span className="al-i warn">{I.orders}</span><span className="al-t"><b>{o.id}</b> confirmed but no invoice issued</span><span className="al-a">Issue invoice{I.right}</span></li>)}
  </ul></Card></div>;
};

// Client portal: invoices & balance
const ClientInvoices = ({ st, client, nav }) => {
  const [open, setOpen] = useState(null); const invs = st.invoices.filter(i => i.clientId === client.id && !i.void).sort((a, b) => new Date(b.issuedAt) - new Date(a.issuedAt));
  const bal = invs.reduce((s, i) => s + invBalance(i), 0); const od = invs.filter(i => invStatus(i) === 'Overdue'); const inv = open && invs.find(i => i.id === open);
  return <div className="page"><PageHead title="Invoices" kicker={client.name}></PageHead>
    <div className="stats"><Stat label="Balance due" value={fmt0(bal)} sub={`${invs.filter(i => invBalance(i) > 0).length} open`} tone={od.length ? 'red' : ''}></Stat><Stat label="Overdue" value={od.length} sub={od.length ? fmt0(od.reduce((s, i) => s + invBalance(i), 0)) : '—'} tone={od.length ? 'red' : ''}></Stat><Stat label="Paid to date" value={fmt0(invs.reduce((s, i) => s + invPaid(i), 0))}></Stat></div>
    <Card pad={false} tour="client-invoices"><div className="tw"><table className="t"><thead><tr><th>{t('Invoice')}</th><th>{t('Order')}</th><th>{t('Issued')}</th><th>{t('Due')}</th><th className="num">{t('Amount')}</th><th className="num">{t('Paid')}</th><th className="num">{t('Balance')}</th><th>{t('Status')}</th></tr></thead><tbody>
      {invs.map(i => { const s = invStatus(i); return <tr key={i.id} className={`row${s === 'Overdue' ? ' row-high' : ''}`} onClick={() => setOpen(i.id)}><td><span className="mono strong">{i.id}</span></td><td><span className="mono">{i.orderId}</span></td><td>{fdate(i.issuedAt)}</td><td className={s === 'Overdue' ? 'red' : ''}>{fdate(i.dueAt)}</td><td className="num mono">{fmt(i.amount)}</td><td className="num mono">{fmt(invPaid(i))}</td><td className="num mono strong">{fmt(invBalance(i))}</td><td><Pill tone={INV_TONE[s]}>{t(s)}</Pill></td></tr>; })}
    </tbody></table></div></Card>
    {inv && <InvoiceModal st={st} inv={inv} act={{}} nav={nav} user={{ role: 'client' }} onClose={() => setOpen(null)}></InvoiceModal>}
  </div>;
};

Object.assign(window, { seedBilling, invStatus, invBalance, invPaid, invDaysLate, addDays, Billing, BillingAlerts, ClientInvoices, InvoiceModal });
