// ════════════════════════════════════════════════════════════════════════
// PropMystro · M4 · pm-d-tax.jsx  (faithful port of pm-tax*.jsx)
// Tax: per-owner MTD-ITSA quarterly tracker (calendar-quarter election) +
// SA105 annual, the s24 residential-finance-cost split (mortgage interest out
// of boxes 24–29 into box 44 + 20% basic-rate reducer), joint-ownership
// splits, the four submission hard-locks, accountant row-queries, and the
// approval chain — accountant → owner → submit — enforced by the Phase B
// RPCs (flag_tax_row / resolve_tax_row / tax_accountant_approve /
// tax_owner_approve / submit_tax). Table: tax_submissions ('tax' feature).
// → window.PMTaxHub
// ════════════════════════════════════════════════════════════════════════
(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 money = (v) => '£' + (Number(v) || 0).toLocaleString('en-GB');
  const daysUntil = (iso) => iso ? Math.round((new Date(iso + 'T00:00:00Z') - new Date(todayISO() + 'T00:00:00Z')) / 864e5) : null;
  function Spin() { return <span className="spin dark" />; }

  // ── tax years + calendar-quarter election (verbatim) ────────────────────
  function currentTaxYear() { const t = todayISO(); const y = Number(t.slice(0, 4)); return (t >= y + '-04-06') ? y + '-' + String(y + 1).slice(2) : (y - 1) + '-' + String(y).slice(2); }
  function priorTaxYear(ty) { const y = Number(ty.slice(0, 4)); return (y - 1) + '-' + String(y).slice(2); }
  const tyShort = (ty) => ty.replace('-', '–');
  function yearBounds(ty) { const y = Number(ty.slice(0, 4)); return { start: y + '-04-06', end: (y + 1) + '-04-05', filingDeadline: (y + 2) + '-01-31' }; }
  function quartersFor(ty) {
    const y = Number(ty.slice(0, 4));
    return [
      { key: 'Q1', label: 'Quarter 1', start: y + '-04-06', end: y + '-06-30', deadline: y + '-08-07' },
      { key: 'Q2', label: 'Quarter 2', start: y + '-07-01', end: y + '-09-30', deadline: y + '-11-07' },
      { key: 'Q3', label: 'Quarter 3', start: y + '-10-01', end: y + '-12-31', deadline: (y + 1) + '-02-07' },
      { key: 'Q4', label: 'Quarter 4', start: (y + 1) + '-01-01', end: (y + 1) + '-03-31', deadline: (y + 1) + '-05-07' },
    ].map(d => ({ ...d, taxYear: ty, kind: 'mtd_quarter' }));
  }
  const finalDeclFor = (ty) => { const b = yearBounds(ty); return { key: 'final', label: 'Final declaration', start: b.start, end: b.end, deadline: b.filingDeadline, taxYear: ty, kind: 'final' }; };
  const sa105For = (ty) => { const b = yearBounds(ty); return { key: 'sa105', label: 'SA105 · UK property', start: b.start, end: b.end, deadline: b.filingDeadline, taxYear: ty, kind: 'sa105' }; };
  function timing(period) {
    const t = todayISO();
    if (t < period.start) return 'upcoming';
    if (t <= period.end) return 'open';
    if (t <= period.deadline) return 'due';
    return 'overdue';
  }
  const TIMING = { open: ['none', 'Open'], due: ['warn', 'Due'], overdue: ['bad', 'Overdue'], upcoming: ['none', 'Upcoming'] };
  const STAGES = {
    open: { label: 'In progress', tone: 'none' }, draft: { label: 'Draft', tone: 'none' },
    accountant_review: { label: 'Accountant review', tone: 'warn' }, owner_approved: { label: 'Owner approved', tone: 'warn' },
    submitted: { label: 'Submitted to HMRC', tone: 'ok' },
  };
  const stageMeta = (s) => STAGES[s] || STAGES.draft;
  const SA_BOXES = [
    { box: 24, label: 'Rent, rates, insurance, ground rents' }, { box: 25, label: 'Property repairs and maintenance' },
    { box: 26, label: 'Loan interest & other financial costs', note: 'non-residential only' },
    { box: 27, label: 'Legal, management & professional fees' }, { box: 28, label: 'Costs of services, incl. wages' },
    { box: 29, label: 'Other allowable property expenses' },
  ];

  // regime: owners.mtd_enrolled → MTD-ITSA; else UTR on file → SA105 annual; else none
  const regimeOf = (o) => o.mtd_enrolled ? 'mtd' : (o.utr ? 'annual' : 'none');
  const RegimeBadge = ({ r }) => r === 'mtd' ? <span className="badge warn" style={{ background: 'var(--surface-2)', border: '1px solid var(--line)', color: 'var(--ink-soft)' }}>MTD-ITSA</span>
    : r === 'annual' ? <span className="badge none">SA105 annual</span> : <span className="badge none" style={{ opacity: .6 }}>No obligation</span>;

  // joint-split fraction for an owner on a property
  function shareFrac(p, ownerId, poByProp) {
    const rows = poByProp[p.id] || [];
    if (rows.length) { const mine = rows.find(r => r.owner_id === ownerId); return mine ? (Number(mine.split_pct) || (100 / rows.length)) / 100 : 0; }
    return p.primary_owner_id === ownerId ? 1 : 0;
  }

  // ── the figures engine (cash basis: payments received / expenses dated) ──
  function taxFigures(data, ownerId, startIso, endIso) {
    const { properties, payments, expenses, tenancies, poByProp } = data;
    const props = properties.filter(p => ownerId === 'all' ? true : shareFrac(p, ownerId, poByProp) > 0);
    const tyByProp = {}; tenancies.forEach(t => { (tyByProp[t.property_id] = tyByProp[t.property_id] || []).push(t.id); });
    let rentIncome = 0, financeCosts = 0;
    const boxes = { 24: 0, 25: 0, 26: 0, 27: 0, 28: 0, 29: 0 };
    const propRows = [], expenseRows = [];
    for (const p of props) {
      const frac = ownerId === 'all' ? 1 : shareFrac(p, ownerId, poByProp);
      const tids = tyByProp[p.id] || [];
      let pRent = 0;
      payments.forEach(x => { if (tids.includes(x.tenancy_id) && x.date >= startIso && x.date <= endIso) pRent += Number(x.amount) || 0; });
      rentIncome += pRent * frac;
      let pAllow = 0, pFin = 0;
      expenses.forEach(e => {
        if (e.property_id !== p.id || e.date < startIso || e.date > endIso) return;
        const amt = (Number(e.amount) || 0) * frac;
        const isFin = e.category === 'mortgage_interest';
        if (isFin) { financeCosts += amt; pFin += amt; }
        else { const b = [24, 25, 26, 27, 28, 29].includes(e.sa105_box) ? e.sa105_box : 29; boxes[b] += amt; pAllow += amt; }
        expenseRows.push({ ...e, frac, share: Math.round(amt), isFin });
      });
      propRows.push({ property: p, frac, rent: Math.round(pRent * frac), allowable: Math.round(pAllow), finance: Math.round(pFin) });
    }
    Object.keys(boxes).forEach(k => boxes[k] = Math.round(boxes[k]));
    rentIncome = Math.round(rentIncome); financeCosts = Math.round(financeCosts);
    const allowableTotal = Object.values(boxes).reduce((s, v) => s + v, 0);
    const netProfit = rentIncome - allowableTotal;
    const taxableProfit = Math.max(0, netProfit);
    const financeReducer = Math.round(0.20 * Math.min(financeCosts, taxableProfit));
    return { rentIncome, boxes, allowableTotal, financeCosts, box44: financeCosts, netProfit, taxableProfit, financeReducer, propRows, expenseRows };
  }

  // simulated HMRC receipt (structured so a real MTD response drops in)
  function makeReceipt(sub) {
    const seed = ((sub.id || 'x') + todayISO()).split('').reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);
    const hex = (n) => (seed * (n + 3) >>> 0).toString(16).toUpperCase().padStart(8, '0').slice(0, 8);
    return { status: 'accepted', correlationId: hex(1) + '-' + hex(2), irMark: hex(3) + hex(4), receivedAt: new Date().toISOString(), channel: 'MTD-ITSA (simulated)' };
  }

  // ── SA105 official form replica + exports ──────────────────────────
  function sa105Payload(owner, ty, fig) {
    return {
      form: 'SA105', version: '2026', taxYear: ty, basis: 'cash',
      taxpayer: { name: owner.name, utr: owner.utr || null },
      boxes: { '20': fig.rentIncome, '24': fig.boxes[24], '25': fig.boxes[25], '26': fig.boxes[26], '27': fig.boxes[27], '28': fig.boxes[28], '29': fig.boxes[29], '36': fig.netProfit, '39': fig.taxableProfit, '44': fig.box44 },
      financeReducer20pct: fig.financeReducer,
      properties: fig.propRows.map(r => ({ address: r.property.address, sharePct: Math.round(r.frac * 100), rent: r.rent, allowable: r.allowable, finance: r.finance })),
      generatedAt: new Date().toISOString(), generator: 'PropMystro',
    };
  }
  function downloadJson(owner, ty, fig) {
    const blob = new Blob([JSON.stringify(sa105Payload(owner, ty, fig), null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'SA105-' + (owner.name || 'owner').replace(/\s+/g, '-') + '-' + ty + '.json';
    a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 5000);
  }
  function sa105FormHtml(owner, ty, fig) {
    const bx = (n, v, label, shade) => '<tr><td class="l">' + label + '</td><td class="bn">' + n + '</td><td class="bv' + (shade ? ' sh' : '') + '">£ ' + (Number(v) || 0).toLocaleString('en-GB') + '</td></tr>';
    return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>SA105 · ' + owner.name + ' · ' + tyShort(ty) + '</title><style>'
      + 'body{font-family:Arial,Helvetica,sans-serif;max-width:760px;margin:28px auto;color:#000;padding:0 18px}'
      + '.hd{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:4px solid #00703c;padding-bottom:10px;margin-bottom:6px}'
      + '.hd h1{font-size:21px;margin:0}.hd .yr{font-size:13px;text-align:right}.hmrc{font-weight:700;font-size:15px;color:#00703c}'
      + '.sub{font-size:11.5px;color:#333;margin:6px 0 14px}'
      + '.who{display:flex;gap:14px;margin-bottom:14px}.who div{flex:1;border:1.5px solid #000;padding:6px 10px;font-size:12px}.who .lab{font-size:10px;text-transform:uppercase;color:#444}'
      + 'h2{font-size:13.5px;background:#e8f0eb;padding:5px 9px;margin:16px 0 6px;border-left:4px solid #00703c}'
      + 'table{width:100%;border-collapse:collapse}td{padding:5px 8px;font-size:12.5px;vertical-align:bottom}'
      + '.l{width:64%}.bn{width:8%;font-weight:700;text-align:right;color:#00703c}.bv{border:1.5px solid #000;text-align:right;font-weight:700;background:#fff}.bv.sh{background:#e8f0eb}'
      + '.note{font-size:10.5px;color:#444;margin:8px 0}'
      + '.ft{margin-top:22px;border-top:1px solid #999;padding-top:8px;font-size:10px;color:#555;display:flex;justify-content:space-between}'
      + '@media print{body{margin:8mm auto}}'
      + '</style></head><body>'
      + '<div class="hd"><div><div class="hmrc">HM Revenue &amp; Customs</div><h1>UK property</h1></div><div class="yr">Tax year 6 April ' + ty.slice(0, 4) + ' to 5 April 20' + ty.slice(5) + ' · <b>SA105</b></div></div>'
      + '<div class="sub">Complete these pages if you received income from UK land and property. Generated by PropMystro from your records — cash basis, joint shares applied.</div>'
      + '<div class="who"><div><div class="lab">Your name</div>' + owner.name + '</div><div><div class="lab">Unique Taxpayer Reference (UTR)</div>' + (owner.utr || '—') + '</div></div>'
      + '<h2>Property income</h2><table>' + bx(20, fig.rentIncome, 'Total rents and other income from property', true) + '</table>'
      + '<h2>Property expenses</h2><table>'
      + bx(24, fig.boxes[24], 'Rent, rates, insurance, ground rents etc.')
      + bx(25, fig.boxes[25], 'Property repairs and maintenance')
      + bx(26, fig.boxes[26], 'Loan interest and other financial costs (non-residential only)')
      + bx(27, fig.boxes[27], 'Legal, management and other professional fees')
      + bx(28, fig.boxes[28], 'Costs of services provided, including wages')
      + bx(29, fig.boxes[29], 'Other allowable property expenses') + '</table>'
      + '<h2>Calculating your taxable profit or loss</h2><table>'
      + bx(36, fig.netProfit, 'Adjusted profit for the year', true)
      + bx(39, fig.taxableProfit, 'Taxable profit for the year', true)
      + bx(44, fig.box44, 'Residential property finance costs (20% tax reducer)') + '</table>'
      + '<div class="note">Box 44: residential mortgage interest is not deducted in boxes 24–29; relief is given as a 20% basic-rate reducer — ' + '£' + fig.financeReducer.toLocaleString('en-GB') + ' off the bill (s.24 Finance (No.2) Act 2015).</div>'
      + '<div class="ft"><span>SA105 ' + tyShort(ty) + ' · ' + owner.name + '</span><span>Generated ' + new Date().toLocaleDateString('en-GB') + ' · PropMystro</span></div>'
      + '<scr' + 'ipt>window.onload=function(){window.print()}</scr' + 'ipt></body></html>';
  }
  function openForm(owner, ty, fig) {
    const w = window.open('', '_blank');
    if (!w) return;
    w.document.write(sa105FormHtml(owner, ty, fig));
    w.document.close();
  }

  // ── MTD provider handoff — submission-ready files for common providers ──
  // API access to HMRC is held by recognised software; until PropMystro is a
  // recognised vendor we generate a clean, submission-ready figures pack each
  // bridging tool can ingest, plus provider-specific import guidance. The box
  // values and the HMRC MTD quarterly-update categories are computed from the
  // same engine as the on-screen SA105.
  const MTD_PROVIDERS = [
    { id: 'generic', name: 'Generic / any bridging tool', hint: 'A clean box-by-box figures CSV every bridging spreadsheet accepts. Open your provider’s SA105 / UK-property template and paste the Amount column into the matching boxes.' },
    { id: '123sheets', name: '123 Sheets', hint: 'Open your 123 Sheets Income-Tax template, go to the UK-property page, and enter the SA105 box figures below. For quarterly updates, use the HMRC-category totals.' },
    { id: 'freeagent', name: 'FreeAgent', hint: 'Hand this pack to your FreeAgent-connected accountant, or post the category totals as a manual journal against your property tracking categories.' },
    { id: 'xero', name: 'Xero', hint: 'Import as a manual journal / account summary against your UK-property tracking categories, then file through your MTD-connected adviser.' },
    { id: 'quickbooks', name: 'QuickBooks', hint: 'Map each HMRC category below to your QuickBooks property chart-of-accounts, or share the pack with your QuickBooks ProAdvisor.' },
    { id: 'sage', name: 'Sage', hint: 'Bring in as a nominal-code summary for the property business; the SA105 boxes map to the UK-property pages.' },
    { id: 'taxcalc', name: 'TaxCalc / BTCSoftware', hint: 'Enter these SA105 box values directly on the UK-property (SA105) pages, then submit through the software.' },
    { id: 'accountant', name: 'My accountant (any software)', hint: 'Email this pack to your accountant — it has the SA105 boxes, the HMRC MTD quarterly categories, the s24 split and the per-property breakdown they need to file.' },
  ];

  function mtdCategories(fig) {
    return {
      income: { periodAmount: fig.rentIncome },
      expenses: {
        premisesRunningCosts: fig.boxes[24], repairsAndMaintenance: fig.boxes[25],
        financialCosts: fig.boxes[26], professionalFees: fig.boxes[27],
        costOfServices: fig.boxes[28], other: fig.boxes[29],
        residentialFinancialCost: fig.box44,
      },
    };
  }
  function csvEsc(v) {
    let s = v == null ? '' : String(v);
    if (/^[=+\-@\t\r]/.test(s) && !/^-?\d*\.?\d+$/.test(s)) s = "'" + s;   // formula-injection guard
    return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
  }
  function handoffCsv(owner, ty, fig, period, provider) {
    const cats = mtdCategories(fig);
    const rows = [];
    rows.push(['PropMystro — MTD submission pack']);
    rows.push(['Prepared for', (MTD_PROVIDERS.find(p => p.id === provider) || {}).name || 'bridging software']);
    rows.push(['Taxpayer', owner.name]);
    rows.push(['UTR', owner.utr || '(not on file)']);
    rows.push(['Tax year', tyShort(ty)]);
    rows.push(['Period', period.label + ' (' + period.start + ' to ' + period.end + ')']);
    rows.push(['Basis', 'Cash basis']);
    rows.push(['Generated', new Date().toISOString().slice(0, 10)]);
    rows.push([]);
    rows.push(['SA105 BOXES — annual Self Assessment (UK property)']);
    rows.push(['Box', 'Description', 'Amount (GBP)']);
    rows.push(['20', 'Total rents and other income from property', fig.rentIncome]);
    SA_BOXES.forEach(b => rows.push([String(b.box), b.label + (b.note ? ' (' + b.note + ')' : ''), fig.boxes[b.box]]));
    rows.push(['36', 'Adjusted profit for the year', fig.netProfit]);
    rows.push(['39', 'Taxable profit for the year', fig.taxableProfit]);
    rows.push(['44', 'Residential property finance costs (20% reducer)', fig.box44]);
    rows.push(['—', '20% finance-cost tax reducer (information)', fig.financeReducer]);
    rows.push([]);
    rows.push(['HMRC MTD QUARTERLY-UPDATE CATEGORIES']);
    rows.push(['Category', 'Type', 'Amount (GBP)']);
    rows.push(['periodAmount', 'income', cats.income.periodAmount]);
    rows.push(['premisesRunningCosts', 'expense', cats.expenses.premisesRunningCosts]);
    rows.push(['repairsAndMaintenance', 'expense', cats.expenses.repairsAndMaintenance]);
    rows.push(['financialCosts', 'expense', cats.expenses.financialCosts]);
    rows.push(['professionalFees', 'expense', cats.expenses.professionalFees]);
    rows.push(['costOfServices', 'expense', cats.expenses.costOfServices]);
    rows.push(['other', 'expense', cats.expenses.other]);
    rows.push(['residentialFinancialCost', 'expense', cats.expenses.residentialFinancialCost]);
    rows.push([]);
    rows.push(['BY PROPERTY — ' + owner.name + "'s share"]);
    rows.push(['Property', 'Share %', 'Rent', 'Allowable', 'Finance']);
    fig.propRows.forEach(r => rows.push([r.property.address, Math.round(r.frac * 100), r.rent, r.allowable, r.finance]));
    return rows.map(r => r.map(csvEsc).join(',')).join('\r\n');
  }
  function downloadHandoff(owner, ty, fig, period, provider) {
    const blob = new Blob(['\ufeff' + handoffCsv(owner, ty, fig, period, provider)], { type: 'text/csv;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'MTD-' + provider + '-' + (owner.name || 'owner').replace(/\s+/g, '-') + '-' + ty + '-' + period.key + '.csv';
    a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 5000);
  }
  function openHandoffCover(owner, ty, fig, period, provider) {
    const prov = MTD_PROVIDERS.find(p => p.id === provider) || {};
    const cats = mtdCategories(fig);
    const r = (n, l, v, sh) => '<tr><td class="bn">' + n + '</td><td class="l">' + l + '</td><td class="bv' + (sh ? ' sh' : '') + '">£' + (Number(v) || 0).toLocaleString('en-GB') + '</td></tr>';
    const html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>MTD pack · ' + owner.name + ' · ' + tyShort(ty) + '</title><style>'
      + 'body{font-family:Arial,Helvetica,sans-serif;max-width:760px;margin:28px auto;color:#1a1815;padding:0 18px}'
      + '.hd{border-bottom:3px solid #2a6fdb;padding-bottom:10px;margin-bottom:8px}.hd h1{font-size:21px;margin:4px 0 0}'
      + '.k{color:#777;font-size:10px;text-transform:uppercase;letter-spacing:.05em}'
      + '.who{display:flex;gap:30px;margin:12px 0 6px;font-size:13px}'
      + 'h2{font-size:13px;margin:20px 0 6px;color:#2a6fdb;text-transform:uppercase;letter-spacing:.04em}'
      + 'table{width:100%;border-collapse:collapse;font-size:13px}td{padding:6px 8px;border-bottom:1px solid #e6e2d8}'
      + '.bn{width:54px;color:#777;font-family:monospace;font-size:11px}.bv{text-align:right;font-weight:600;width:120px}.bv.sh{background:#f2f6ff}'
      + '.note{font-size:11.5px;color:#444;background:#f4efe6;border-radius:8px;padding:11px 14px;margin:14px 0;line-height:1.5}'
      + '.steps{font-size:12.5px;line-height:1.6;color:#333}.ft{margin-top:22px;border-top:1px solid #999;padding-top:8px;font-size:10px;color:#555;display:flex;justify-content:space-between}'
      + '@media print{body{margin:8mm auto}}</style></head><body>'
      + '<div class="hd"><div class="k">Making Tax Digital · submission pack</div><h1>UK property — ' + prov.name + '</h1></div>'
      + '<div class="who"><div><div class="k">Taxpayer</div>' + owner.name + '</div><div><div class="k">UTR</div>' + (owner.utr || '—') + '</div><div><div class="k">Tax year</div>' + tyShort(ty) + '</div><div><div class="k">Period</div>' + period.label + '</div></div>'
      + '<div class="note"><b>How to file with ' + prov.name + ':</b><br>' + prov.hint + '</div>'
      + '<h2>SA105 boxes (annual)</h2><table>'
      + r(20, 'Total rents and other income from property', fig.rentIncome, true)
      + r(24, 'Rent, rates, insurance, ground rents', fig.boxes[24]) + r(25, 'Property repairs and maintenance', fig.boxes[25])
      + r(26, 'Loan interest &amp; other financial costs (non-resi)', fig.boxes[26]) + r(27, 'Legal, management &amp; professional fees', fig.boxes[27])
      + r(28, 'Costs of services, incl. wages', fig.boxes[28]) + r(29, 'Other allowable property expenses', fig.boxes[29])
      + r(36, 'Adjusted profit for the year', fig.netProfit, true) + r(39, 'Taxable profit for the year', fig.taxableProfit, true)
      + r(44, 'Residential finance costs (20% reducer)', fig.box44) + '</table>'
      + '<div class="note">Box 44: residential mortgage interest is not deducted in boxes 24–29; relief is a 20% basic-rate reducer of £' + fig.financeReducer.toLocaleString('en-GB') + '.</div>'
      + '<h2>HMRC MTD quarterly-update categories</h2><table>'
      + r('inc', 'periodAmount (rental income)', cats.income.periodAmount, true)
      + r('exp', 'premisesRunningCosts', cats.expenses.premisesRunningCosts) + r('exp', 'repairsAndMaintenance', cats.expenses.repairsAndMaintenance)
      + r('exp', 'financialCosts (non-residential)', cats.expenses.financialCosts) + r('exp', 'professionalFees', cats.expenses.professionalFees)
      + r('exp', 'costOfServices', cats.expenses.costOfServices) + r('exp', 'other', cats.expenses.other)
      + r('exp', 'residentialFinancialCost', cats.expenses.residentialFinancialCost) + '</table>'
      + '<div class="ft"><span>MTD pack · ' + tyShort(ty) + ' · ' + owner.name + '</span><span>Generated ' + new Date().toLocaleDateString('en-GB') + ' · PropMystro</span></div>'
      + '<scr' + 'ipt>window.onload=function(){window.print()}</scr' + 'ipt></body></html>';
    const w = window.open('', '_blank'); if (!w) return; w.document.write(html); w.document.close();
  }

  function MtdHandoffModal({ data, owner, ty, onClose }) {
    const periodOpts = [{ key: 'year', label: 'Full tax year (SA105 / final declaration)', start: yearBounds(ty).start, end: yearBounds(ty).end }]
      .concat(quartersFor(ty).map(q => ({ key: q.key, label: q.label, start: q.start, end: q.end })));
    const [provider, setProvider] = useState('generic');
    const [periodKey, setPeriodKey] = useState('year');
    const period = periodOpts.find(p => p.key === periodKey) || periodOpts[0];
    const fig = taxFigures(data, owner.id, period.start, period.end);
    const prov = MTD_PROVIDERS.find(p => p.id === provider) || {};
    const totalExp = fig.allowableTotal + fig.box44;

    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 560 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div>
        <div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>MTD SUBMISSION HANDOFF</div>
        <h2>Generate a submission-ready pack</h2>
        <p className="modal-sub">Your figures, box-mapped and ready for your MTD software or accountant. Direct submission to HMRC is coming as PropMystro becomes recognised — for now this pack drops straight into any bridging tool.</p>
      </div><button className="x" onClick={onClose}>✕</button></div>
      <div className="modal-form">
        <div className="grid2">
          <div className="field" style={{ margin: 0 }}><label>Your MTD software</label>
            <select value={provider} onChange={e => setProvider(e.target.value)}>{MTD_PROVIDERS.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></div>
          <div className="field" style={{ margin: 0 }}><label>Period</label>
            <select value={periodKey} onChange={e => setPeriodKey(e.target.value)}>{periodOpts.map(p => <option key={p.key} value={p.key}>{p.label}</option>)}</select></div>
        </div>
        <div className="rr-autofill" style={{ marginTop: 12 }}>
          <div><span className="pmd-mono">INCOME</span><strong>{money(fig.rentIncome)}</strong></div>
          <div><span className="pmd-mono">EXPENSES</span><strong>{money(totalExp)}</strong></div>
          <div><span className="pmd-mono">TAXABLE</span><strong>{money(fig.taxableProfit)}</strong></div>
          <div><span className="pmd-mono">PERIOD</span><strong style={{ fontSize: 12 }}>{period.start} → {period.end}</strong></div>
        </div>
        <div className="alert alert-info" style={{ marginTop: 12 }}><span className="ic">ℹ</span><div>{prov.hint}</div></div>
      </div>
      <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
        <button className="btn btn-ghost" onClick={onClose}>Close</button>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-ghost" onClick={() => openHandoffCover(owner, ty, fig, period, provider)}>Cover sheet · Print/PDF</button>
          <button className="btn btn-primary" style={{ width: 'auto' }} onClick={() => downloadHandoff(owner, ty, fig, period, provider)}>↓ Download {prov.name === 'My accountant (any software)' ? 'pack' : 'CSV'}</button>
        </div>
      </div>
    </div></div>;
  }

  // ── SA105 box summary (the on-screen form) ──────────────────────────────
  function SA105Summary({ fig }) {
    const Row = ({ box, label, val, strong, note }) => <div className="tax-boxrow">
      <span className="pmd-mono tax-boxn">BOX {box}</span><span className="tax-boxl">{label}{note ? <span className="pmd-mono" style={{ fontSize: 9.5, color: 'var(--ink-faint)' }}> · {note}</span> : null}</span>
      <span style={{ fontWeight: strong ? 700 : 600 }}>{val}</span>
    </div>;
    return <div className="tax-sa105">
      <Row box={20} label="Total rents and other income from property" val={money(fig.rentIncome)} strong />
      {SA_BOXES.map(b => <Row key={b.box} box={b.box} label={b.label} note={b.note} val={money(fig.boxes[b.box])} />)}
      <Row box={36} label="Adjusted profit for the year" val={money(fig.netProfit)} strong />
      <Row box={39} label="Taxable profit for the year" val={money(fig.taxableProfit)} strong />
      <Row box={44} label="Residential property finance costs" val={money(fig.box44)} note="20% basic-rate reducer" />
      <div className="tax-boxrow" style={{ background: 'var(--ok-soft)', borderRadius: 6 }}>
        <span className="pmd-mono tax-boxn">s24</span><span className="tax-boxl">Finance-cost tax reducer (20% of box 44, capped at profit)</span>
        <span style={{ fontWeight: 700, color: 'var(--ok)' }}>−{money(fig.financeReducer)} off the bill</span>
      </div>
    </div>;
  }

  // ── submit modal — the four hard locks, then send ────────────────────────
  function SubmitModal({ sb, sub, fig, bankRows, role, onClose, onDone, toast }) {
    const [phase, setPhase] = useState('review');
    const unmatched = bankRows.filter(r => r.status === 'unmatched' && r.date >= sub.period_start && r.date <= sub.period_end);
    const openQ = (sub.queries || []).filter(q => q.status === 'open');
    const locks = [
      { ok: unmatched.length === 0, label: 'Bank lines in this period reconciled', detail: unmatched.length ? unmatched.length + ' unmatched — clear them in Bank' : 'all matched' },
      { ok: openQ.length === 0, label: 'No open accountant queries', detail: openQ.length ? openQ.length + ' open' : 'none open' },
      { ok: !!sub.accountant_approved, label: 'Accountant approved', detail: sub.accountant_approved ? 'by ' + (sub.accountant_by || 'accountant') : 'awaiting' },
      { ok: !!sub.owner_approved, label: 'Owner approved', detail: sub.owner_approved ? 'by ' + (sub.owner_by || 'owner') : 'awaiting' },
    ];
    const ready = locks.every(l => l.ok);
    const send = async () => {
      setPhase('sending');
      const { error } = await sb.rpc('submit_tax', { submission_id: sub.id, receipt: makeReceipt(sub) });
      if (error) { setPhase('review'); return toast(error.message); }
      setPhase('done');
    };
    return <div className="modal-scrim" onClick={phase === 'sending' ? undefined : onClose}><div className="modal" style={{ maxWidth: 560 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>SUBMIT TO HMRC · {sub.period_label || sub.period_key} · {tyShort(sub.tax_year)}</div><h2>{phase === 'done' ? 'Accepted ✓' : 'Ready to send?'}</h2></div>{phase !== 'sending' && <button className="x" onClick={onClose}>✕</button>}</div>
      {phase === 'review' && <React.Fragment>
        <div style={{ padding: '0 24px' }}>
          <div className="rr-autofill" style={{ padding: '0 0 12px' }}>
            <div><span className="pmd-mono">INCOME</span><strong>{money(fig.rentIncome)}</strong></div>
            <div><span className="pmd-mono">ALLOWABLE</span><strong>{money(fig.allowableTotal)}</strong></div>
            <div><span className="pmd-mono">FINANCE COSTS</span><strong>{money(fig.financeCosts)}</strong></div>
            <div><span className="pmd-mono">NET</span><strong>{money(fig.netProfit)}</strong></div>
          </div>
          {locks.map((l, i) => <div key={i} className={'gate-row' + (l.ok ? ' on' : '')} style={{ cursor: 'default', marginBottom: 8 }}>
            <span style={{ fontSize: 15, marginTop: 1 }}>{l.ok ? '✓' : '🔒'}</span>
            <div><strong>{l.label}</strong><div className="s">{l.detail}</div></div>
          </div>)}
          {!ready && <p className="modal-sub" style={{ marginTop: 4 }}>Every lock must be green — they're also enforced by the database on submit.</p>}
        </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={!ready || role !== 'owner'} title={role !== 'owner' ? 'The owner (or admin) sends the submission' : ''} onClick={send}>Send to HMRC →</button>
        </div>
      </React.Fragment>}
      {phase === 'sending' && <div style={{ textAlign: 'center', padding: '26px 0 36px' }}><Spin /><div style={{ marginTop: 12, fontWeight: 600 }}>Transmitting via MTD-ITSA…</div></div>}
      {phase === 'done' && <React.Fragment>
        <div style={{ padding: '0 24px' }}>
          <div className="alert alert-ok"><span className="ic">✓</span><div>HMRC accepted the submission. The receipt is filed against this obligation.</div></div>
        </div>
        <div className="modal-foot" style={{ justifyContent: 'flex-end' }}><button className="btn btn-primary" style={{ width: 'auto' }} onClick={() => { onDone(); onClose(); }}>Done</button></div>
      </React.Fragment>}
    </div></div>;
  }

  // ── owner workspace ──────────────────────────────────────────────────────
  function OwnerWorkspace({ sb, account, owner, data, role, onBack, reload, toast }) {
    const regime = regimeOf(owner);
    const curTY = currentTaxYear();
    const [ty, setTy] = useState(priorTaxYear(curTY));
    const [submitFor, setSubmitFor] = useState(null);
    const [handoff, setHandoff] = useState(false);
    const [busy, setBusy] = useState('');
    const subs = data.subs.filter(s => s.owner_id === owner.id && s.tax_year === ty);
    const periods = regime === 'mtd' ? [...quartersFor(ty), finalDeclFor(ty)] : regime === 'annual' ? [sa105For(ty)] : [];
    const bounds = yearBounds(ty);
    const yearFig = taxFigures(data, owner.id, bounds.start, bounds.end);

    const subFor = (p) => subs.find(s => s.period_key === p.key);
    const ensureSub = async (p) => {
      const existing = subFor(p);
      if (existing) return existing;
      const { data: row, error } = await sb.from('tax_submissions').insert({
        owner_id: owner.id, tax_year: ty, kind: p.kind, period_key: p.key, period_label: p.label,
        period_start: p.start, period_end: p.end, deadline: p.deadline, stage: 'draft', queries: [],
      }).select('*').single();
      if (error) { toast(/tax|feature/.test(error.message) ? 'Tax needs the Portfolio plan.' : error.message); return null; }
      return row;
    };
    const act = async (p, fn) => { setBusy(p.key); try { const s = await ensureSub(p); if (s) await fn(s); } finally { setBusy(''); reload(); } };
    const accountantApprove = (s) => sb.rpc('tax_accountant_approve', { submission_id: s.id }).then(({ error }) => { if (error) toast(error.message); });
    const ownerApprove = (s) => sb.rpc('tax_owner_approve', { submission_id: s.id }).then(({ error }) => { if (error) toast(error.message); });
    const flagQuery = async (s) => {
      const note = prompt('Query for the owner (e.g. "Is the £450 refurb capital, not revenue?"):', '');
      if (!note) return;
      const { error } = await sb.rpc('flag_tax_row', { submission_id: s.id, query: { note, by: 'accountant' } });
      if (error) toast(error.message);
    };
    const resolveQuery = async (s, q) => {
      const { error } = await sb.rpc('resolve_tax_row', { submission_id: s.id, query_id: q.id });
      if (error) toast(error.message); else reload();
    };

    return <React.Fragment>
      <button className="backlink" onClick={onBack}>← Back to tax overview</button>
      <div className="pagehead" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, flexWrap: 'wrap' }}>
        <div><h1>{owner.name}</h1><p>{regime === 'mtd' ? 'MTD-ITSA — quarterly updates + final declaration (calendar-quarter election).' : regime === 'annual' ? 'Annual self-assessment — SA105 UK property pages.' : 'No filing obligation on record.'}{owner.utr ? ' · UTR ' + owner.utr : ''}</p></div>
        <div className="tabs" style={{ marginBottom: 0 }}>
          {[priorTaxYear(curTY), curTY].map(t => <button key={t} className={ty === t ? 'on' : ''} onClick={() => setTy(t)}>{tyShort(t)}</button>)}
        </div>
      </div>

      {periods.length > 0 && <div className="card" style={{ marginBottom: 18 }}>
        <div className="card-head"><div><h3>Obligations · {tyShort(ty)}</h3><div className="sub">{regime === 'mtd' ? '4 quarterly updates + final declaration' : 'one annual return'} · filing deadline {fmt(bounds.filingDeadline)}</div></div></div>
        {periods.map(p => {
          const s = subFor(p);
          const t = timing(p);
          const [tone, tlabel] = TIMING[t];
          const st = s ? stageMeta(s.stage) : (t === 'open' || t === 'upcoming' ? STAGES.open : STAGES.draft);
          const fig = taxFigures(data, owner.id, p.start, p.end);
          const openQ = s ? (s.queries || []).filter(q => q.status === 'open') : [];
          const dleft = daysUntil(p.deadline);
          return <div className="row" key={p.key} style={{ alignItems: 'flex-start', flexWrap: 'wrap' }}>
            <div className="main" style={{ minWidth: 180 }}>
              <div className="t">{p.label} <span className="pmd-mono" style={{ fontSize: 10.5, color: 'var(--ink-faint)' }}>{fmt(p.start)} – {fmt(p.end)}</span></div>
              <div className="s">deadline {fmt(p.deadline)}{dleft != null && dleft >= 0 && t !== 'upcoming' ? ' · ' + dleft + 'd' : ''} · income {money(fig.rentIncome)} · expenses {money(fig.allowableTotal)}{fig.financeCosts ? ' · finance ' + money(fig.financeCosts) : ''}</div>
              {openQ.length > 0 && <div className="s" style={{ color: 'var(--red)' }}>{openQ.length} open quer{openQ.length === 1 ? 'y' : 'ies'}: {openQ.map(q => q.note).join(' · ')}</div>}
              {s && s.stage === 'submitted' && s.receipt && <div className="s" style={{ color: 'var(--ok)' }}>✓ {s.receipt.channel || 'HMRC'} · ref {s.receipt.correlationId} · {fmt(s.submitted_at)}</div>}
            </div>
            <span className={'badge ' + (s && s.stage === 'submitted' ? 'ok' : tone)}>{s && s.stage === 'submitted' ? 'Submitted' : tlabel}</span>
            <span className={'badge ' + st.tone}>{st.label}</span>
            {s && s.stage !== 'submitted' && <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {(role === 'accountant' || role === 'owner') && openQ.length > 0 && openQ.map(q => <button key={q.id} className="btn btn-ghost btn-sm" onClick={() => resolveQuery(s, q)}>Resolve query</button>)}
              {role === 'accountant' && <React.Fragment>
                <button className="btn btn-ghost btn-sm" onClick={() => flagQuery(s)}>Flag a query</button>
                {!s.accountant_approved && <button className="btn btn-primary btn-sm" disabled={openQ.length > 0} onClick={() => act(p, accountantApprove)}>Accountant approve ✓</button>}
              </React.Fragment>}
              {role === 'owner' && <React.Fragment>
                {!s.accountant_approved && <button className="btn btn-ghost btn-sm" onClick={() => act(p, accountantApprove)} title="Acting as accountant (admin)">Accountant approve</button>}
                {s.accountant_approved && !s.owner_approved && <button className="btn btn-primary btn-sm" onClick={() => act(p, ownerApprove)}>Owner approve ✓</button>}
                {s.accountant_approved && s.owner_approved && <button className="btn btn-primary btn-sm" onClick={() => setSubmitFor({ sub: s, fig })}>Submit to HMRC →</button>}
              </React.Fragment>}
            </div>}
            {!s && t !== 'upcoming' && <button className="btn btn-ghost btn-sm" disabled={busy === p.key} onClick={() => act(p, async () => {})}>{busy === p.key ? <Spin /> : t === 'open' ? 'Open draft' : 'Start ' + (p.kind === 'sa105' ? 'SA105' : p.key) + ' →'}</button>}
          </div>;
        })}
      </div>}

      <div className="grid2">
        <div className="card">
          <div className="card-head"><div><h3>SA105 · {tyShort(ty)}</h3><div className="sub">cash basis · joint splits applied · s24 finance-cost split</div></div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={() => setHandoff(true)}>⇄ MTD handoff</button>
              <button className="btn btn-ghost btn-sm" onClick={() => openForm(owner, ty, yearFig)}>Official form · Print/PDF</button>
              <button className="btn btn-ghost btn-sm" onClick={() => downloadJson(owner, ty, yearFig)}>↓ JSON</button>
            </div></div>
          <div className="card-pad"><SA105Summary fig={yearFig} /></div>
        </div>
        <div className="card">
          <div className="card-head"><div><h3>By property</h3><div className="sub">{owner.name}'s share</div></div></div>
          {yearFig.propRows.length === 0 ? <div className="card-pad" style={{ color: 'var(--ink-faint)', fontSize: 13.5 }}>No properties linked to this owner.</div>
            : yearFig.propRows.map((r, i) => <div className="row" key={i}>
              <div className="main"><div className="t">{r.property.address}</div><div className="s">share {Math.round(r.frac * 100)}% · rent {money(r.rent)} · allowable {money(r.allowable)}{r.finance ? ' · finance ' + money(r.finance) : ''}</div></div>
              <strong>{money(r.rent - r.allowable)}</strong>
            </div>)}
        </div>
      </div>

      {submitFor && <SubmitModal sb={sb} sub={submitFor.sub} fig={submitFor.fig} bankRows={data.bankRows} role={role} onClose={() => setSubmitFor(null)} onDone={reload} toast={toast} />}
      {handoff && <MtdHandoffModal data={data} owner={owner} ty={ty} onClose={() => setHandoff(false)} />}
    </React.Fragment>;
  }

  // ── tax hub (§12a) ───────────────────────────────────────────────────────
  function TaxHub({ sb, account, toast, openBilling, membership }) {
    const entitled = account.features && account.features.tax === true;
    const [data, setData] = useState(null);
    const [openOwner, setOpenOwner] = useState(null);
    const role = membership && membership.role === 'accountant' ? 'accountant' : 'owner';

    const load = useCallback(async () => {
      if (!entitled) return;
      const [o, p, po, ty, pay, exp, br, ts] = await Promise.all([
        sb.from('owners').select('*').order('created_at', { ascending: true }),
        sb.from('properties').select('id,address,primary_owner_id'),
        sb.from('property_owners').select('property_id,owner_id,split_pct'),
        sb.from('tenancies').select('id,property_id'),
        sb.from('payments').select('tenancy_id,amount,date'),
        sb.from('expenses').select('id,property_id,date,category,sa105_box,amount,vendor,description'),
        sb.from('bank_rows').select('id,status,date'),
        sb.from('tax_submissions').select('*').order('created_at', { ascending: true }),
      ]);
      const poByProp = {}; (po.data || []).forEach(r => { (poByProp[r.property_id] = poByProp[r.property_id] || []).push(r); });
      setData({ owners: o.data || [], properties: p.data || [], poByProp, tenancies: ty.data || [], payments: pay.data || [], expenses: exp.data || [], bankRows: br.data || [], subs: ts.data || [] });
    }, [sb, entitled]);
    useEffect(() => { load(); }, [load]);

    if (!entitled) return <React.Fragment>
      <div className="pagehead"><h1>Tax</h1><p>Making Tax Digital + SA105, per owner.</p></div>
      <div className="card card-pad"><div className="locked">
        <div className="lock-ico">🧾</div><h3>Tax is a Portfolio feature</h3>
        <p>Per-owner MTD quarterly updates, SA105 with the s24 finance-cost split, accountant review and the locked approval chain to HMRC.</p>
        <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={openBilling}>Upgrade to Portfolio</button>
        <div className="lock-foot">Your data stays exactly as it is — upgrading just unlocks the module.</div>
      </div></div>
    </React.Fragment>;
    if (data === null) return <div className="card card-pad"><Spin /></div>;

    if (openOwner) {
      const owner = data.owners.find(o => o.id === openOwner);
      if (owner) return <OwnerWorkspace sb={sb} account={account} owner={owner} data={data} role={role} onBack={() => { setOpenOwner(null); load(); }} reload={load} toast={toast} />;
    }

    const curTY = currentTaxYear(); const focusTY = priorTaxYear(curTY);
    return <React.Fragment>
      <div className="pagehead"><h1>Tax</h1><p>Every owner's position — MTD quarterly updates, SA105 and the approval chain. Focus year {tyShort(focusTY)} (filing deadline {fmt(yearBounds(focusTY).filingDeadline)}).</p></div>
      {data.owners.length === 0 ? <div className="card"><div className="empty"><div className="ico">👤</div><h3>No owners yet</h3><p>Add owners (with UTR / MTD status) under People → Owners — each gets a tax workspace here.</p></div></div>
        : <div className="tn-cards">
          {data.owners.map(o => {
            const regime = regimeOf(o);
            const fig = taxFigures(data, o.id, yearBounds(focusTY).start, yearBounds(focusTY).end);
            const subs = data.subs.filter(s => s.owner_id === o.id && s.tax_year === focusTY);
            const openQ = subs.reduce((n, s) => n + ((s.queries || []).filter(q => q.status === 'open').length), 0);
            const periods = regime === 'mtd' ? [...quartersFor(focusTY), finalDeclFor(focusTY)] : regime === 'annual' ? [sa105For(focusTY)] : [];
            const next = periods.map(p => ({ p, s: subs.find(x => x.period_key === p.key), t: timing(p) })).find(x => !(x.s && x.s.stage === 'submitted') && x.t !== 'upcoming');
            return <div className="card tn-card" key={o.id}>
              <div className="tn-card-top">
                <span className="avatar" style={{ background: (o.color || '#385a8a') + '22', color: o.color || '#385a8a' }}>{(o.name || '?').split(/\s+/).map(w => w[0]).slice(0, 2).join('').toUpperCase()}</span>
                <div style={{ flex: 1, minWidth: 0 }}><div className="t" style={{ fontWeight: 700 }}>{o.name}</div><div className="s" style={{ fontSize: 12, color: 'var(--ink-faint)' }}>{o.utr ? 'UTR ' + o.utr : 'No UTR on file'}</div></div>
                <RegimeBadge r={regime} />
              </div>
              <div className="tn-card-facts">
                <span><span className="pmd-mono">INCOME {tyShort(focusTY)}</span> {money(fig.rentIncome)}</span>
                <span><span className="pmd-mono">NET</span> {money(fig.netProfit)}</span>
                {openQ > 0 && <span style={{ color: 'var(--red)' }}><span className="pmd-mono">QUERIES</span> {openQ} open</span>}
              </div>
              <div className="tn-card-hh" style={{ alignItems: 'center' }}>
                {regime === 'none' ? <span style={{ fontSize: 12.5, color: 'var(--ink-faint)' }}>No assessable property income on record.</span>
                  : next ? <span style={{ fontSize: 12.5 }}><b>{next.p.label}</b> · {next.s ? stageMeta(next.s.stage).label : TIMING[next.t][1]} · deadline {fmt(next.p.deadline)}</span>
                  : <span style={{ fontSize: 12.5, color: 'var(--ok)' }}>✓ All {tyShort(focusTY)} obligations submitted</span>}
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
                <button className="btn btn-primary btn-sm" onClick={() => setOpenOwner(o.id)}>Open workspace</button>
              </div>
            </div>;
          })}
        </div>}
    </React.Fragment>;
  }

  window.PMTaxHub = TaxHub;
})();
