// ════════════════════════════════════════════════════════════════════════
// PropMystro · G1 · pm-d-ledger.jsx  (faithful port of pm-ledger.jsx +
// pm-money-data.jsx ledger helpers). Rent charges are DERIVED per rental
// period over a rolling 12-month window — never stored — so the ledger and
// arrears always agree with the tenancy terms. Running-balance table, arrears
// clock (oldest-unpaid allocation, >30d → critical), record payment / credit
// / payment-plan / reminder, the portfolio Ledger hub with drilldown, and the
// property Finance tab. Tables: payments · arrears_plans · expenses ·
// activity_log ('ledger' feature). → window.PMLedgerHub, window.PMFinance
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useCallback } = React;

  const todayISO = () => new Date().toISOString().slice(0, 10);
  const fmt = (d) => d ? new Date(d).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }) : '—';
  const gbp = (n) => '£' + (Number(n) || 0).toLocaleString('en-GB', { maximumFractionDigits: 2 });
  const iso = (d) => d.toISOString().slice(0, 10);
  const addMonths = (i, n) => { const d = new Date(i + 'T00:00:00Z'); d.setUTCMonth(d.getUTCMonth() + n); return iso(d); };
  const daysBetween = (a, b) => Math.round((new Date(b + 'T00:00:00Z') - new Date(a + 'T00:00:00Z')) / 864e5);
  const clampDay = (d) => Math.min(Number(d) || 1, 28);
  function periodStartOnOrAfter(isoDate, payDay) {
    const ref = new Date(isoDate + 'T00:00:00Z'); const day = clampDay(payDay);
    const cand = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), day));
    if (cand < ref) cand.setUTCMonth(cand.getUTCMonth() + 1);
    return iso(cand);
  }
  const ledgerWindow = () => ({ from: addMonths(todayISO(), -12), to: todayISO() });
  function Spin() { return <span className="spin dark" />; }

  // ── derived rent charges (verbatim model) ────────────────────────────────
  function genRentCharges(ty, fromIso, toIso) {
    if (!ty || !ty.start_date) return [];
    const day = clampDay(ty.pay_day);
    const lower = ty.start_date > fromIso ? ty.start_date : fromIso;
    const upper = (ty.end_date && ty.end_date < toIso) ? ty.end_date : toIso;
    let cur = periodStartOnOrAfter(lower, day);
    const out = []; let guard = 0;
    while (cur <= upper && guard++ < 400) {
      out.push({ id: 'ch-' + ty.id + '-' + cur, kind: 'charge', dueDate: cur, amount: Number(ty.rent_pcm) || 0 });
      cur = addMonths(cur, 1);
    }
    return out;
  }
  function ledgerFor(ty, payments) {
    const { from, to } = ledgerWindow();
    const charges = genRentCharges(ty, from, to);
    const pays = payments.filter(p => p.tenancy_id === ty.id).map(p => ({ ...p, kind: p.kind || 'payment', dueDate: p.date }));
    const rows = [...charges, ...pays].sort((a, b) => (a.dueDate || '').localeCompare(b.dueDate || '') || (a.kind === 'charge' ? -1 : 1));
    let bal = 0;
    rows.forEach(r => { if (r.kind === 'charge') bal += r.amount; else bal -= Math.abs(r.amount); r.runningBalance = bal; });
    const totalDue = charges.reduce((s, c) => s + c.amount, 0);
    const totalPaid = pays.reduce((s, p) => s + Math.abs(p.amount), 0);
    return { rows, totalDue, totalPaid, balance: totalDue - totalPaid };
  }
  function arrearsOf(ty, payments) {
    if (!ty) return { balance: 0, credit: 0, daysOverdue: 0, status: 'paid', monthsBehind: 0 };
    const today = todayISO(); const { from } = ledgerWindow();
    const charges = genRentCharges(ty, from, today);
    const paid = payments.filter(p => p.tenancy_id === ty.id).reduce((s, p) => s + Math.abs(Number(p.amount) || 0), 0);
    const due = charges.reduce((s, c) => s + c.amount, 0);
    const balance = due - paid;
    if (balance <= 0) return { balance: 0, credit: Math.max(0, -balance), daysOverdue: 0, oldestUnpaid: null, status: 'paid', monthsBehind: 0 };
    let rem = paid, oldest = null;
    for (const c of charges) { if (rem >= c.amount) rem -= c.amount; else { oldest = c.dueDate; break; } }
    const daysOverdue = oldest ? daysBetween(oldest, today) : 0;
    const overdue = balance > 0 && daysOverdue > 0;
    const status = !overdue ? (balance > 0 ? 'due' : 'paid') : (daysOverdue > 30 ? 'crit' : 'warn');
    return { balance, credit: 0, daysOverdue, oldestUnpaid: oldest, overdue, status, monthsBehind: ty.rent_pcm ? balance / ty.rent_pcm : 0 };
  }
  const arrearsPill = (a) => a.status === 'paid' ? (a.credit > 0 ? <span className="badge ok">In credit {gbp(a.credit)}</span> : <span className="badge ok">Paid up</span>)
    : a.status === 'due' ? <span className="badge none">Due now · {gbp(a.balance)}</span>
    : <span className={'badge ' + (a.status === 'crit' ? 'bad' : 'warn')}>{gbp(a.balance)} owed · {a.daysOverdue}d</span>;

  // ── modals ────────────────────────────────────────────────────────────────
  function PayModal({ sb, ty, lead, property, kind, prefill, onClose, onDone, toast }) {
    const isCredit = kind === 'credit';
    const [f, setF] = useState({ date: (prefill && prefill.date) || todayISO(), amount: String((prefill && prefill.amount) || (isCredit ? '' : ty.rent_pcm || '')), method: 'bank_transfer', ref: (prefill && prefill.ref) || '', note: '' });
    const [busy, setBusy] = useState(false);
    const save = async () => {
      setBusy(true);
      const { error } = await sb.from('payments').insert({
        tenancy_id: ty.id, property_id: ty.property_id, date: f.date, amount: Number(f.amount) || 0,
        kind: isCredit ? 'credit' : 'payment', method: isCredit ? null : f.method, ref: f.ref || null, note: f.note || null,
      });
      setBusy(false);
      if (error) return toast(/ledger|feature/.test(error.message) ? 'The ledger needs the Professional plan.' : error.message);
      toast(isCredit ? 'Credit applied' : 'Payment recorded'); onDone();
    };
    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 460 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>{isCredit ? 'CREDIT / ADJUSTMENT' : 'RECORD PAYMENT'}</div><h2>{lead ? lead.full_name : 'Tenant'}{property ? ' · ' + property.address : ''}</h2>{isCredit && <p className="modal-sub">e.g. a goodwill credit during an outage — reduces the balance like a payment.</p>}</div><button className="x" onClick={onClose}>✕</button></div>
      <div className="modal-form">
        <div className="field"><label>Date</label><input type="date" value={f.date} onChange={e => setF({ ...f, date: e.target.value })} /></div>
        <div className="field"><label>Amount (£)</label><input value={f.amount} autoFocus onChange={e => setF({ ...f, amount: e.target.value.replace(/[^\d.]/g, '') })} /></div>
        {!isCredit && <div className="field"><label>Method</label><select value={f.method} onChange={e => setF({ ...f, method: e.target.value })}><option value="bank_transfer">Bank transfer</option><option value="standing_order">Standing order</option><option value="card">Card</option><option value="cash">Cash</option></select></div>}
        <div className="field"><label>{isCredit ? 'Reason' : 'Reference'}</label><input value={isCredit ? f.note : f.ref} onChange={e => setF(isCredit ? { ...f, note: e.target.value } : { ...f, ref: e.target.value })} placeholder={isCredit ? 'e.g. boiler outage goodwill' : 'optional'} /></div>
      </div>
      <div className="modal-foot" style={{ justifyContent: 'flex-end', gap: 8 }}>
        <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" style={{ width: 'auto' }} disabled={busy || !(Number(f.amount) > 0)} onClick={save}>{busy ? <Spin /> : isCredit ? 'Apply credit' : 'Record payment'}</button>
      </div>
    </div></div>;
  }
  function PlanModal({ sb, account, ty, plan, arrears, onClose, onDone, toast }) {
    const [monthly, setMonthly] = useState(String((plan && plan.monthly) || ''));
    const [note, setNote] = useState((plan && plan.note) || '');
    const [busy, setBusy] = useState(false);
    const save = async () => {
      setBusy(true);
      const { error } = await sb.from('arrears_plans').upsert({ tenancy_id: ty.id, account_id: account.id, monthly: Number(monthly) || 0, note: note || null, agreed_at: todayISO(), by: 'Landlord' }, { onConflict: 'tenancy_id' });
      setBusy(false);
      if (error) return toast(error.message);
      toast('Payment plan saved'); onDone();
    };
    const clear = async () => { await sb.from('arrears_plans').delete().eq('tenancy_id', ty.id); toast('Plan removed'); onDone(); };
    const months = Number(monthly) > 0 ? Math.ceil(arrears.balance / Number(monthly)) : null;
    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 460 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>PAYMENT PLAN</div><h2>Clear {gbp(arrears.balance)} of arrears</h2><p className="modal-sub">An agreed extra amount on top of the rent each month. Tracked here — the statutory position is unaffected.</p></div><button className="x" onClick={onClose}>✕</button></div>
      <div className="modal-form">
        <div className="field"><label>Extra per month (£)</label><input value={monthly} autoFocus onChange={e => setMonthly(e.target.value.replace(/[^\d.]/g, ''))} /></div>
        <div className="field full"><label>Agreement note</label><input value={note} onChange={e => setNote(e.target.value)} placeholder="e.g. agreed by phone 14 Jun, review in 3 months" /></div>
      </div>
      {months && <div style={{ padding: '0 24px' }}><div className="alert alert-info"><span className="ic">ℹ</span><div>{gbp(Number(monthly))}/mo clears the current balance in about <b>{months} month{months === 1 ? '' : 's'}</b>.</div></div></div>}
      <div className="modal-foot" style={{ justifyContent: 'space-between', gap: 8 }}>
        {plan ? <button className="btn btn-ghost" onClick={clear}>Remove plan</button> : <span />}
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" style={{ width: 'auto' }} disabled={busy || !(Number(monthly) > 0)} onClick={save}>{busy ? <Spin /> : 'Save plan'}</button>
        </div>
      </div>
    </div></div>;
  }

  // ── per-tenancy ledger (running balance + actions) ───────────────────────
  function TenancyLedger({ sb, account, ty, lead, property, payments, plans, reload, toast, compact }) {
    const [modal, setModal] = useState(null);
    const led = ledgerFor(ty, payments);
    const arr = arrearsOf(ty, payments);
    const plan = plans.find(p => p.tenancy_id === ty.id);
    const remind = async () => {
      await sb.from('activity_log').insert({ who: 'Landlord', action: 'Arrears reminder sent to ' + (lead ? lead.full_name : 'tenant') + ' · balance ' + gbp(arr.balance), entity_type: 'tenancy', entity_id: ty.id });
      toast('Reminder logged' + (lead && lead.email ? ' — email goes out with the Communicate module' : ''));
    };
    return <React.Fragment>
      <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}>
        <div className="summary" style={{ margin: 0 }}>
          <div className="sumcard"><div className="n">{gbp(ty.rent_pcm)}</div><div className="l">Rent pcm · day {clampDay(ty.pay_day)}</div></div>
          <div className="sumcard"><div className="n">{gbp(led.totalPaid)}</div><div className="l">Received · 12mo</div></div>
          <div className={'sumcard ' + (arr.status === 'crit' ? 'bad' : arr.status === 'warn' ? 'warn' : 'ok')}><div className="n">{arr.balance > 0 ? '−' + gbp(arr.balance) : gbp(arr.credit)}</div><div className="l">{arr.balance > 0 ? (arr.daysOverdue > 0 ? arr.daysOverdue + 'd overdue · ' + arr.monthsBehind.toFixed(1) + ' mo' : 'Due now') : arr.credit > 0 ? 'In credit' : 'Paid up'}</div></div>
        </div>
        {plan && <div className="banner trial" style={{ margin: '12px 0 0' }}><span>📋 Payment plan: <b>{gbp(plan.monthly)}/mo extra</b>{plan.note ? ' · ' + plan.note : ''} · agreed {fmt(plan.agreed_at)}</span></div>}
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 12 }}>
          <button className="btn btn-primary btn-sm" onClick={() => setModal('pay')}>+ Record payment</button>
          <button className="btn btn-ghost btn-sm" onClick={() => setModal('credit')}>Credit / adjust</button>
          {arr.balance > 0 && <button className="btn btn-ghost btn-sm" onClick={() => setModal('plan')}>{plan ? 'Edit payment plan' : 'Payment plan'}</button>}
          {arr.balance > 0 && <button className="btn btn-ghost btn-sm" onClick={remind}>Send reminder</button>}
        </div>
      </div>
      <div style={{ overflowX: 'auto', maxHeight: compact ? 300 : 420, overflowY: 'auto' }}><table className="prop-table">
        <thead><tr><th>Date</th><th>Item</th><th style={{ textAlign: 'right' }}>Charge</th><th style={{ textAlign: 'right' }}>Paid</th><th style={{ textAlign: 'right' }}>Balance</th></tr></thead>
        <tbody>
          {led.rows.length === 0 && <tr><td colSpan={5} style={{ textAlign: 'center', color: 'var(--ink-faint)', padding: 20 }}>No charges in the rolling 12-month window.</td></tr>}
          {led.rows.slice().reverse().map((r, i) => <tr key={r.id || i}>
            <td className="pmd-mono" style={{ fontSize: 12, whiteSpace: 'nowrap' }}>{fmt(r.dueDate)}</td>
            <td>{r.kind === 'charge' ? 'Rent due' : r.kind === 'credit' ? 'Credit' + (r.note ? ' · ' + r.note : '') : 'Payment' + (r.ref ? ' · ' + r.ref : '')}</td>
            <td style={{ textAlign: 'right' }}>{r.kind === 'charge' ? gbp(r.amount) : ''}</td>
            <td style={{ textAlign: 'right', color: 'var(--ok)' }}>{r.kind !== 'charge' ? gbp(Math.abs(r.amount)) : ''}</td>
            <td style={{ textAlign: 'right', fontWeight: 600, color: r.runningBalance > 0 ? 'var(--red)' : 'var(--ink)' }}>{r.runningBalance > 0 ? '−' + gbp(r.runningBalance) : gbp(-r.runningBalance)}</td>
          </tr>)}
        </tbody>
      </table></div>
      {modal === 'pay' && <PayModal sb={sb} ty={ty} lead={lead} property={property} kind="payment" onClose={() => setModal(null)} onDone={() => { setModal(null); reload(); }} toast={toast} />}
      {modal === 'credit' && <PayModal sb={sb} ty={ty} lead={lead} property={property} kind="credit" onClose={() => setModal(null)} onDone={() => { setModal(null); reload(); }} toast={toast} />}
      {modal === 'plan' && <PlanModal sb={sb} account={account} ty={ty} plan={plan} arrears={arr} onClose={() => setModal(null)} onDone={() => { setModal(null); reload(); }} toast={toast} />}
    </React.Fragment>;
  }

  // ── shared loader ─────────────────────────────────────────────────────────
  function useMoney(sb, entitled) {
    const [data, setData] = useState(null);
    const load = useCallback(async () => {
      if (!entitled) return;
      const [ty, tn, p, pay, pl, exp] = await Promise.all([
        sb.from('tenancies').select('*'),
        sb.from('tenants').select('id,full_name,email'),
        sb.from('properties').select('id,address,postcode'),
        sb.from('payments').select('*').order('date', { ascending: true }),
        sb.from('arrears_plans').select('*'),
        sb.from('expenses').select('property_id,amount,date,category,vendor'),
      ]);
      setData({ tenancies: ty.data || [], tenants: tn.data || [], properties: p.data || [], payments: pay.data || [], plans: pl.data || [], expenses: exp.data || [] });
    }, [sb, entitled]);
    useEffect(() => { load(); }, [load]);
    return [data, load];
  }

  // ── portfolio ledger hub ──────────────────────────────────────────────────
  function LedgerHub({ sb, account, toast, openBilling, onOpenProperty }) {
    const entitled = account.features && account.features.ledger === true;
    const [data, reload] = useMoney(sb, entitled);
    const [open, setOpen] = useState(null);
    if (!entitled) return <React.Fragment>
      <div className="pagehead"><h1>Ledger</h1><p>Rent in, balances and arrears across the portfolio.</p></div>
      <div className="card card-pad"><div className="locked">
        <div className="lock-ico">£</div><h3>The ledger is a Professional feature</h3>
        <p>Running balances per tenancy, the arrears clock, credits, payment plans and reminders — the money spine of the portfolio.</p>
        <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={openBilling}>Upgrade to Professional</button>
      </div></div>
    </React.Fragment>;
    if (!data) return <div className="card card-pad"><Spin /></div>;

    const active = data.tenancies.filter(t => t.status === 'active');
    const propById = Object.fromEntries(data.properties.map(p => [p.id, p]));
    const tenById = Object.fromEntries(data.tenants.map(t => [t.id, t]));
    const rows = active.map(ty => ({ ty, arr: arrearsOf(ty, data.payments), led: ledgerFor(ty, data.payments) }))
      .sort((a, b) => (b.arr.balance - a.arr.balance) || (b.arr.daysOverdue - a.arr.daysOverdue));
    const rentRoll = active.reduce((s, t) => s + (Number(t.rent_pcm) || 0), 0);
    const collected = data.payments.filter(p => p.date >= ledgerWindow().from).reduce((s, p) => s + Math.abs(Number(p.amount) || 0), 0);
    const arrearsTotal = rows.reduce((s, r) => s + r.arr.balance, 0);
    const expected = rows.reduce((s, r) => s + r.led.totalDue, 0);
    const rate = expected > 0 ? Math.min(100, Math.round((collected / expected) * 100)) : 100;

    if (open) {
      const r = rows.find(x => x.ty.id === open);
      if (r) {
        const p = propById[r.ty.property_id]; const lead = tenById[r.ty.lead_tenant_id];
        return <React.Fragment>
          <button className="backlink" onClick={() => setOpen(null)}>← Back to ledger</button>
          <div className="pagehead"><h1>{lead ? lead.full_name : 'Tenancy'}</h1><p>{p ? p.address : ''} · {gbp(r.ty.rent_pcm)}/mo since {fmt(r.ty.start_date)}</p></div>
          <div className="card" style={{ overflow: 'hidden' }}>
            <TenancyLedger sb={sb} account={account} ty={r.ty} lead={lead} property={p} payments={data.payments} plans={data.plans} reload={reload} toast={toast} />
          </div>
        </React.Fragment>;
      }
    }

    return <React.Fragment>
      <div className="pagehead" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 14, flexWrap: 'wrap' }}>
        <div><h1>Ledger</h1><p>Every active tenancy's balance — worst first. Open one for the running ledger and actions.</p></div>
        <div className="summary" style={{ margin: 0 }}>
          <div className="sumcard"><div className="n">{gbp(rentRoll)}</div><div className="l">Rent roll /mo</div></div>
          <div className="sumcard ok"><div className="n">{rate}%</div><div className="l">Collection · 12mo</div></div>
          <div className={'sumcard' + (arrearsTotal > 0 ? ' bad' : ' ok')}><div className="n">{gbp(arrearsTotal)}</div><div className="l">Arrears</div></div>
        </div>
      </div>
      <div className="card" style={{ overflow: 'hidden' }}>
        {rows.length === 0 && <div className="empty"><div className="ico">£</div><h3>No active tenancies</h3><p>Create tenancies and the ledger tracks rent against each automatically.</p></div>}
        {rows.map(({ ty, arr }) => { const p = propById[ty.property_id]; const lead = tenById[ty.lead_tenant_id]; const plan = data.plans.find(x => x.tenancy_id === ty.id); return <div className="row clickable" key={ty.id} onClick={() => setOpen(ty.id)}>
          <span className={'hdot ' + (arr.status === 'crit' ? 'crit' : arr.status === 'warn' ? 'warn' : 'ok')}></span>
          <div className="main">
            <div className="t">{lead ? lead.full_name : 'Tenancy'}{plan ? <span className="pmd-mono" style={{ fontSize: 10, color: 'var(--ink-faint)' }}> · plan {gbp(plan.monthly)}/mo</span> : null}</div>
            <div className="s">{p ? p.address : ''} · {gbp(ty.rent_pcm)}/mo · day {clampDay(ty.pay_day)}</div>
          </div>
          {arrearsPill(arr)}
          <span style={{ color: 'var(--ink-faint)' }}>→</span>
        </div>; })}
      </div>
    </React.Fragment>;
  }

  // ── property finance tab (full ledger + this property's expenses) ─────────
  function TabFinance({ sb, property, account, toast, openBilling }) {
    const entitled = account.features && account.features.ledger === true;
    const [data, reload] = useMoney(sb, entitled);
    if (!entitled) return <div className="card-pad"><div className="locked">
      <div className="lock-ico">£</div><h3>The rent ledger is a Professional feature</h3>
      <p>Track rent received against derived charges, see arrears at a glance, apply credits and payment plans.</p>
      <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={openBilling}>Upgrade to Professional</button>
    </div></div>;
    if (!data) return <div className="card-pad"><Spin /></div>;
    const ty = data.tenancies.find(t => t.property_id === property.id && t.status === 'active');
    if (!ty) return <div className="empty"><div className="ico">£</div><h3>No active tenancy</h3><p>Create a tenancy on the Tenancy tab — the ledger then tracks rent against it.</p></div>;
    const lead = data.tenants.find(t => t.id === ty.lead_tenant_id);
    const exps = data.expenses.filter(e => e.property_id === property.id && e.date >= ledgerWindow().from).sort((a, b) => (b.date || '').localeCompare(a.date || ''));
    return <React.Fragment>
      <TenancyLedger sb={sb} account={account} ty={ty} lead={lead} property={property} payments={data.payments} plans={data.plans} reload={reload} toast={toast} compact />
      <div className="card-head" style={{ borderTop: '1px solid var(--line-soft)' }}><div><h3>Expenses · 12 months</h3><div className="sub">{exps.length} on this property · {gbp(exps.reduce((s, e) => s + (Number(e.amount) || 0), 0))}</div></div></div>
      {exps.slice(0, 6).map((e, i) => <div className="row" key={i}>
        <span className="avatar" style={{ background: 'var(--surface-2)', color: 'var(--ink-faint)' }}>£</span>
        <div className="main"><div className="t">{e.vendor || e.category}</div><div className="s">{fmt(e.date)} · {e.category}</div></div>
        <strong>{gbp(e.amount)}</strong>
      </div>)}
      {exps.length === 0 && <div className="card-pad" style={{ color: 'var(--ink-faint)', fontSize: 13.5 }}>No expenses recorded for this property in the window.</div>}
    </React.Fragment>;
  }

  window.PMLedgerHub = LedgerHub;
  window.PMFinance = TabFinance;   // replaces the thin Phase D finance tab
})();
