// ════════════════════════════════════════════════════════════════════════
// PropMystro · M5 · pm-d-rentreview.jsx  (faithful port of pm-rent-review*.jsx)
// Section 13 / Form 4A rent reviews: statutory date math (2-month notice,
// rental-period starts, 12-month gap), the 4-step wizard (Complete → Review &
// approve → Sign → Serve & track), the official-form viewer, and the Tenancy-
// tab tracker (cycle, live notice clocks, tenant-response tracking, apply).
// Table: notices (type section_13, RLS-gated by the rent_review feature);
// serve/apply go through the serve_rent_review / apply_rent_review RPCs so
// the statutory locks are enforced by the database. → window.PMRentReview
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useCallback } = React;

  // ── statutory constants + date math (verbatim) ──────────────────────────
  const NOTICE_MONTHS = 2;        // minimum notice period (monthly periodic)
  const INITIATE_MONTHS = 2;      // window opens 2mo before anniversary (aligned to the 2-month notice)
  const MIN_GAP_MONTHS = 12;      // rent may rise at most once per 12 months
  const METHODS = [
    { key: 'email', label: 'Email', offsetDays: 0, blurb: 'deemed served same day' },
    { key: 'post', label: 'First-class post', offsetDays: 2, blurb: 'deemed served +2 working days' },
    { key: 'hand', label: 'By hand', offsetDays: 0, blurb: 'deemed served same day' },
    { key: 'portal', label: 'Tenant portal', offsetDays: 0, blurb: 'deemed served same day' },
  ];
  const method = (k) => METHODS.find(m => m.key === k) || METHODS[0];
  const STATUS = {
    drafted: { tone: 'warn', label: 'Draft' },
    approved: { tone: 'none', label: 'Approved — ready to serve' },
    served: { tone: 'warn', label: 'Served — notice running' },
    acknowledged: { tone: 'ok', label: 'Acknowledged by tenant' },
    challenged: { tone: 'bad', label: 'Challenged · Tribunal' },
    determined: { tone: 'none', label: 'Tribunal determined' },
    effective: { tone: 'ok', label: 'In effect' },
    withdrawn: { tone: 'none', label: 'Withdrawn' },
  };
  const statusMeta = (s) => STATUS[s] || STATUS.drafted;

  const todayISO = () => new Date().toISOString().slice(0, 10);
  const iso = (d) => d.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 ordinal = (n) => { const s = ['th', 'st', 'nd', 'rd'], v = n % 100; return n + (s[(v - 20) % 10] || s[v] || s[0]); };
  const daysBetween = (a, b) => (!a || !b) ? null : Math.round((new Date(b + 'T00:00:00Z') - new Date(a + 'T00:00:00Z')) / 86400000);
  const addMonths = (i, n) => { const d = new Date(i + 'T00:00:00Z'); d.setUTCMonth(d.getUTCMonth() + n); return iso(d); };
  const addDays = (i, n) => { const d = new Date(i + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() + n); return iso(d); };

  function periodStartOnOrAfter(isoDate, payDay) {
    const ref = new Date(isoDate + 'T00:00:00Z');
    const day = Math.min(Number(payDay) || 1, 28);
    const cand = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), day));
    if (cand < ref) cand.setUTCMonth(cand.getUTCMonth() + 1);
    return iso(cand);
  }
  const isPeriodStart = (i, payDay) => { if (!i) return false; const day = Math.min(Number(payDay) || 1, 28); return new Date(i + 'T00:00:00Z').getUTCDate() === day; };

  function minEffectiveDate(tenancy, servedIso, m) {
    const deemed = addDays(servedIso, method(m).offsetDays);
    let floor = periodStartOnOrAfter(addMonths(deemed, NOTICE_MONTHS), tenancy.pay_day);
    const lastChange = tenancy.last_rent_review_at || tenancy.start_date;
    if (lastChange) {
      const gapFloor = periodStartOnOrAfter(addMonths(lastChange, MIN_GAP_MONTHS), tenancy.pay_day);
      if (gapFloor > floor) floor = gapFloor;
    }
    return floor;
  }
  function effectiveDateIssues(tenancy, servedIso, m, effectiveIso) {
    const issues = [];
    if (!effectiveIso) { issues.push('Set the date the new rent takes effect.'); return issues; }
    const minEff = minEffectiveDate(tenancy, servedIso, m);
    if (effectiveIso < minEff) issues.push('Too early — at least ' + NOTICE_MONTHS + ' months\u2019 notice after deemed service, and 12 months since the last change. Earliest: ' + fmt(minEff) + '.');
    if (!isPeriodStart(effectiveIso, tenancy.pay_day)) issues.push('Must fall on the first day of a rental period (the ' + ordinal(Math.min(Number(tenancy.pay_day) || 1, 28)) + ').');
    return issues;
  }
  function cycleOf(tenancy, today) {
    today = today || todayISO();
    if (!tenancy || !tenancy.start_date) return null;
    const anchor = tenancy.rent_review_anchor || tenancy.last_rent_review_at || tenancy.start_date;
    let anniversary = addMonths(anchor, 12);
    while (addMonths(anniversary, 6) < today) anniversary = addMonths(anniversary, 12);
    const initiateFrom = addMonths(anniversary, -INITIATE_MONTHS);
    const status = today >= anniversary ? 'overdue' : today >= initiateFrom ? 'due' : 'ontrack';
    return { anniversary, initiateFrom, status };
  }
  const F4A_CHARGES = [
    { key: 'councilTax', label: 'Council tax' }, { key: 'water', label: 'Water charges' },
    { key: 'hotWater', label: 'Heating / hot water' }, { key: 'furniture', label: 'Furniture' },
    { key: 'service', label: 'Fixed service charges' },
  ];
  const emptyCharges = () => { const c = {}; F4A_CHARGES.forEach(r => { c[r.key] = { existing: 'nil', proposed: 'nil' }; }); return c; };

  function Spin() { return <span className="spin dark" />; }
  const SigMark = ({ mark, small }) => mark ? <span className="ag-signature" style={{ fontSize: small ? 19 : 28, borderBottomWidth: 1, marginTop: 0, padding: '0 8px 0 2px' }}>{mark.name}</span> : null;

  // ── inline sign modal (typed signature) ─────────────────────────────────
  function SignModal({ who, role, onDone, onClose }) {
    const [name, setName] = useState('');
    return <div className="modal-scrim" style={{ zIndex: 70 }} onClick={onClose}><div className="modal" style={{ maxWidth: 430 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>{(role || 'SIGNATURE').toUpperCase()}</div><h2>Sign as {who}</h2><p className="modal-sub">Type the full name to apply an e-signature to the notice.</p></div><button className="x" onClick={onClose}>✕</button></div>
      <div style={{ padding: '0 24px' }}>
        <div className="field"><label>Full name</label><input value={name} autoFocus placeholder={who} onChange={e => setName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && name.trim()) onDone({ name: name.trim(), at: todayISO() }); }} /></div>
        {name.trim() && <div className="ag-signature">{name}</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={!name.trim()} onClick={() => onDone({ name: name.trim(), at: todayISO() })}>Apply signature ✍</button>
      </div>
    </div></div>;
  }

  // ── Form 4A viewer — the official notice, populated ─────────────────────
  function Form4AViewer({ review, tenancy, property, owner, joint, tenantNames, onClose }) {
    const charges = review.charges || emptyCharges();
    const Box = ({ label, children }) => <div className="f4a-box"><div className="f4a-box-l">{label}</div><div className="f4a-box-v">{children || '—'}</div></div>;
    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 760 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>OFFICIAL NOTICE · HOUSING ACT 1988 s.13(2)</div><h2>Form 4A (05.26)</h2><p className="modal-sub">Landlord's notice proposing a new rent for assured tenancies in the private rented sector.</p></div><button className="x" onClick={onClose}>✕</button></div>
      <div className="f4a-doc">
        <div className="f4a-head">
          <div className="f4a-formno">Form 4A</div>
          <h3>Landlord's notice proposing a new rent</h3>
          <div className="f4a-sub">Housing Act 1988 section 13(2), as amended by the Renters' Rights Act 2026 · This notice proposes a new rent for your assured tenancy. The proposed rent cannot take effect earlier than the date in section 4.6.</div>
        </div>
        <div className="f4a-sec"><span className="f4a-n">1</span><div><strong>Tenant(s)</strong><Box label="Name(s)">{tenantNames.join(', ')}</Box></div></div>
        <div className="f4a-sec"><span className="f4a-n">2</span><div><strong>Property</strong><Box label="Address">{property.address}{property.postcode ? ', ' + property.postcode : ''}</Box></div></div>
        <div className="f4a-sec"><span className="f4a-n">3</span><div><strong>Landlord</strong>
          <Box label="Name">{owner ? owner.name : '—'}{joint.length ? ' (and ' + joint.map(o => o.name).join(', ') + ' — joint landlords)' : ''}</Box>
          <Box label="Address">{owner && owner.address ? owner.address : '—'}</Box>
          <div className="f4a-row"><Box label="Phone">{owner && owner.phone}</Box><Box label="Email">{owner && owner.email}</Box></div>
        </div></div>
        <div className="f4a-sec"><span className="f4a-n">4</span><div><strong>The rent</strong>
          <div className="f4a-row">
            <Box label="4.1 · Tenancy started">{fmt(tenancy.start_date)}</Box>
            <Box label="4.2 · Existing rent (per month)">{money(review.current_rent)}</Box>
          </div>
          <div className="f4a-row">
            <Box label="4.3 · Rent payable on">the {ordinal(Math.min(Number(tenancy.pay_day) || 1, 28))} of the month</Box>
            <Box label="4.4 · First increase after 11 Feb 2003">{review.first_increase_2003 ? fmt(review.first_increase_2003) : 'No increase'}</Box>
          </div>
          <div className="f4a-row">
            <Box label="4.5 · Proposed new rent (per month)"><b>{money(review.proposed_rent)}</b></Box>
            <Box label="4.6 · New rent starts on"><b>{fmt(review.effective_date)}</b></Box>
          </div>
          <div className="f4a-box-l" style={{ marginTop: 10 }}>4.7 · Charges included in the rent</div>
          <table className="f4a-charges"><thead><tr><th></th><th>Existing</th><th>Proposed</th></tr></thead><tbody>
            {F4A_CHARGES.map(r => { const c = charges[r.key] || { existing: 'nil', proposed: 'nil' }; return <tr key={r.key}><td>{r.label}</td><td>{c.existing}</td><td>{c.proposed}</td></tr>; })}
          </tbody></table>
          {review.comparables_note && <Box label="How the proposed rent was decided">{review.comparables_note}</Box>}
        </div></div>
        <div className="f4a-sec"><span className="f4a-n">5</span><div><strong>Signature</strong>
          <div className="f4a-row" style={{ alignItems: 'flex-end' }}>
            <div className="f4a-box" style={{ flex: 1 }}><div className="f4a-box-l">Signed ({review.signed_capacity === 'agent' ? "landlord's agent" : 'landlord'})</div><div className="f4a-signarea">{review.signature ? <SigMark mark={review.signature} /> : null}</div></div>
            <Box label="Name">{review.signed_name || (owner && owner.name)}</Box>
            <Box label="Date">{review.signed_at ? fmt(review.signed_at) : '—'}</Box>
          </div>
          {(review.joint_signatures || []).length > 0 && <React.Fragment>
            <div className="f4a-box-l" style={{ marginTop: 8 }}>Additional signatures — joint landlords</div>
            {(review.joint_signatures || []).map((j, i) => <div className="f4a-row" key={i} style={{ alignItems: 'flex-end' }}>
              <div className="f4a-box" style={{ flex: 1 }}><div className="f4a-signarea"><SigMark mark={j.mark} small /></div></div>
              <Box label="Name">{j.name}</Box><Box label="Date">{fmt(j.at)}</Box>
            </div>)}
          </React.Fragment>}
        </div></div>
        <div className="f4a-notes"><strong>Notes for the tenant.</strong> If you accept the proposed rent, you need do nothing — it takes effect on the date in 4.6. If you do not accept it, you may refer this notice to the First-tier Tribunal (Property Chamber) <b>before</b> that date. The Tribunal will determine a market rent, which may be lower or higher than the proposal but not above it where the notice was served under section 13(2).</div>
      </div>
      <div className="modal-foot" style={{ justifyContent: 'flex-end' }}><button className="btn btn-ghost" onClick={onClose}>Close</button></div>
    </div></div>;
  }

  // ── the 4-step wizard ────────────────────────────────────────────────────
  function Wizard({ sb, account, tenancy, property, owner, joint, tenantNames, existing, onClose, onDone, toast }) {
    const blank = () => ({
      tenancy_id: tenancy.id, property_id: property.id, type: 'section_13', status: 'drafted', form_version: '4A (05.26)',
      current_rent: Number(tenancy.rent_pcm) || 0, proposed_rent: '', frequency: 'monthly',
      effective_date: minEffectiveDate(tenancy, todayISO(), 'email'), tenancy_started: tenancy.start_date,
      last_increase_on: tenancy.last_rent_review_at || null, first_increase_2003: null,
      charges: emptyCharges(), comparables_note: '', method: 'email',
      approved: false, signature: null, joint_signatures: [], signed_capacity: 'landlord',
    });
    const [d, setD] = useState(() => existing ? { ...existing, charges: existing.charges || emptyCharges(), joint_signatures: existing.joint_signatures || [] } : blank());
    const [step, setStep] = useState(existing && existing.signature ? 4 : existing && existing.approved ? 3 : 1);
    const [viewer, setViewer] = useState(false);
    const [showCharges, setShowCharges] = useState(false);
    const [signWho, setSignWho] = useState(null);
    const [busy, setBusy] = useState(false);
    const set = (patch) => setD(s => ({ ...s, ...patch }));

    const rise = (Number(d.proposed_rent) || 0) - (Number(d.current_rent) || 0);
    const risePct = d.current_rent ? Math.round((rise / d.current_rent) * 1000) / 10 : 0;
    const served0 = todayISO();
    const minEff = minEffectiveDate(tenancy, served0, d.method);
    const effIssues = effectiveDateIssues(tenancy, served0, d.method, d.effective_date);
    const canReview = Number(d.proposed_rent) > 0 && effIssues.length === 0;
    const STEPS = ['Complete Form 4A', 'Review & approve', 'Sign', 'Serve & track'];
    const setMethodK = (m) => { const nm = minEffectiveDate(tenancy, served0, m); set({ method: m, effective_date: (!d.effective_date || d.effective_date < nm) ? nm : d.effective_date }); };
    const setCharge = (key, col, val) => setD(s => ({ ...s, charges: { ...s.charges, [key]: { ...s.charges[key], [col]: val } } }));

    const persist = async (patch) => {
      const row = { ...d, ...patch };
      const cols = {
        tenancy_id: row.tenancy_id, property_id: row.property_id, type: 'section_13', status: row.status,
        form_version: row.form_version, current_rent: Number(row.current_rent) || 0, proposed_rent: Number(row.proposed_rent) || null,
        frequency: row.frequency, effective_date: row.effective_date || null, tenancy_started: row.tenancy_started || null,
        last_increase_on: row.last_increase_on || null, first_increase_2003: row.first_increase_2003 || null,
        charges: row.charges, comparables_note: row.comparables_note || null, method: row.method,
        approved: !!row.approved, approved_at: row.approved_at || null,
        signature: row.signature, joint_signatures: row.joint_signatures,
        signed_name: row.signed_name || null, signed_at: row.signed_at || null, signed_capacity: row.signed_capacity || 'landlord',
      };
      if (row.id) { const { error } = await sb.from('notices').update(cols).eq('id', row.id); if (error) throw error; return row.id; }
      const { data, error } = await sb.from('notices').insert(cols).select('id').single();
      if (error) throw error;
      set({ id: data.id }); return data.id;
    };
    const saveDraft = async () => {
      setBusy(true);
      try { await persist({ status: d.approved ? 'approved' : 'drafted' }); toast('Draft saved'); onDone(); }
      catch (e) { toast(/rent_review|feature/.test(e.message) ? 'Rent reviews need the Professional plan.' : e.message); }
      finally { setBusy(false); }
    };
    const serve = async () => {
      setBusy(true);
      try {
        const id = await persist({ status: d.approved ? 'approved' : 'drafted' });
        const { error } = await sb.rpc('serve_rent_review', { notice_id: id, signatures: { primary: d.signature, joint: d.joint_signatures } });
        if (error) throw error;
        toast('Form 4A served — notice period running');
        onDone();
      } catch (e) { toast(e.message); }
      finally { setBusy(false); }
    };

    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 760 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div>
        <div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>RENT REVIEW · OFFICIAL FORM 4A (05.26) · SECTION 13 · STEP {step} OF 4</div>
        <h2>{property.address}</h2>
        <p className="modal-sub">{tenantNames.join(', ')} · assured periodic tenancy since {fmt(tenancy.start_date)}. The form auto-populates from records — you set the new rent, effective date and any charges, then {joint.length ? 'the primary owner signs' : 'sign'} and serve.</p>
      </div><button className="x" onClick={onClose}>✕</button></div>

      <div className="rr-steps">{STEPS.map((s, i) => <div key={i} className={'rr-step' + (step === i + 1 ? ' on' : '') + (step > i + 1 ? ' done' : '')}><span className="rr-step-n">{step > i + 1 ? '✓' : i + 1}</span>{s}</div>)}</div>

      {step === 1 && <React.Fragment>
        <div className="rr-autofill">
          <div><span className="pmd-mono">LANDLORD</span><strong>{owner ? owner.name : '—'}{joint.length ? ' +' + joint.length : ''}</strong></div>
          <div><span className="pmd-mono">TENANT(S)</span><strong>{tenantNames.join(', ') || '—'}</strong></div>
          <div><span className="pmd-mono">EXISTING RENT</span><strong>{money(d.current_rent)}/mo</strong></div>
          <div><span className="pmd-mono">RENT DUE</span><strong>{ordinal(Math.min(Number(tenancy.pay_day) || 1, 28))} of the month</strong></div>
        </div>
        <div className="modal-form">
          <div className="field"><label>Proposed new rent · £/month (Q4.5)</label>
            <input value={d.proposed_rent} placeholder={String(d.current_rent)} onChange={e => set({ proposed_rent: e.target.value.replace(/[^\d.]/g, '') })} />
            {Number(d.proposed_rent) > 0 && <div className={'hint'} style={{ color: rise > 0 ? 'var(--ok)' : rise < 0 ? 'var(--red)' : undefined, fontWeight: 600 }}>{rise >= 0 ? '+' : ''}{money(rise)}/mo · {rise >= 0 ? '+' : ''}{risePct}% {rise > 0 ? '↑' : rise < 0 ? '↓' : ''}</div>}
          </div>
          <div className="field"><label>Service method</label>
            <select value={d.method} onChange={e => setMethodK(e.target.value)}>{METHODS.map(m => <option key={m.key} value={m.key}>{m.label} — {m.blurb}</option>)}</select>
          </div>
          <div className="field"><label>New rent starts on (Q4.6)</label>
            <input type="date" value={d.effective_date || ''} min={minEff} onChange={e => set({ effective_date: e.target.value })} />
            <div className="hint">Earliest lawful date: {fmt(minEff)} · must start a rental period</div>
            {effIssues.map((x, i) => <div key={i} className="hint" style={{ color: 'var(--red)' }}>⚠ {x}</div>)}
          </div>
          <div className="field"><label>First increase after 11 Feb 2003 (Q4.4 · optional)</label>
            <input type="date" value={d.first_increase_2003 || ''} onChange={e => set({ first_increase_2003: e.target.value || null })} />
            <div className="hint">Leave blank if there has been no increase since 11 Feb 2003.</div>
          </div>
          <div className="field full"><label>How the new rent was decided (optional · prints on the notice)</label>
            <textarea rows={2} placeholder="e.g. In line with comparable 2-bed flats letting at £2,600–£2,750; last reviewed Sep 2025." value={d.comparables_note || ''} onChange={e => set({ comparables_note: e.target.value })}></textarea>
          </div>
        </div>
        <div style={{ padding: '0 24px' }}>
          <button className="linkbtn" onClick={() => setShowCharges(v => !v)}>{showCharges ? '▾' : '▸'} Charges included in the rent (Q4.7) — default nil</button>
          {showCharges && <table className="prop-table" style={{ marginTop: 8 }}><thead><tr><th>Charge</th><th>Existing (£)</th><th>Proposed (£)</th></tr></thead><tbody>
            {F4A_CHARGES.map(r => { const c = d.charges[r.key] || { existing: 'nil', proposed: 'nil' }; return <tr key={r.key}><td>{r.label}</td>
              <td><input className="pm-input" style={{ width: 90 }} value={c.existing} onChange={e => setCharge(r.key, 'existing', e.target.value)} /></td>
              <td><input className="pm-input" style={{ width: 90 }} value={c.proposed} onChange={e => setCharge(r.key, 'proposed', e.target.value)} /></td></tr>; })}
          </tbody></table>}
          <div className="rr-clocks">
            <div><span className="pmd-mono">NOTICE PERIOD</span><strong>{NOTICE_MONTHS} months</strong></div>
            <div><span className="pmd-mono">TENANT MAY CHALLENGE UNTIL</span><strong>{d.effective_date ? fmt(addDays(d.effective_date, -1)) : '—'}</strong></div>
          </div>
        </div>
        <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
          <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!canReview} onClick={() => setStep(2)}>{canReview ? 'Review Form 4A →' : 'Complete the rent & date first'}</button>
        </div>
      </React.Fragment>}

      {step === 2 && <React.Fragment>
        <div style={{ padding: '0 24px' }}>
          <div className="alert alert-info"><span className="ic">📄</span><div>Proposing <b>{money(d.proposed_rent)}/mo</b> from <b>{fmt(d.effective_date)}</b> ({rise >= 0 ? '+' : ''}{risePct}%). Every box of the official Form 4A is populated from your records — open the notice, check it, then approve for issue.</div></div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <button className="btn btn-ghost" onClick={() => setViewer(true)}>⤢ Open the full notice</button>
            <button className={'btn ' + (d.approved ? 'btn-ghost' : 'btn-primary')} style={{ width: 'auto' }} onClick={() => set({ approved: !d.approved, approved_at: !d.approved ? todayISO() : null })}>{d.approved ? '✓ Approved for issue — undo' : 'Approve for issue'}</button>
          </div>
        </div>
        <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
          <button className="btn btn-ghost" onClick={() => setStep(1)}>← Edit details</button>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-ghost" disabled={busy} onClick={saveDraft}>{busy ? <Spin /> : 'Save draft & close'}</button>
            <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!d.approved} onClick={() => setStep(3)}>{d.approved ? 'Sign the notice →' : '🔒 Approve first'}</button>
          </div>
        </div>
      </React.Fragment>}

      {step === 3 && <React.Fragment>
        <div style={{ padding: '0 24px' }}>
          <div className="field" style={{ maxWidth: 360 }}><label>Signing capacity</label>
            <div className="tabs" style={{ margin: 0 }}>
              <button className={d.signed_capacity !== 'agent' ? 'on' : ''} onClick={() => set({ signed_capacity: 'landlord' })}>Landlord</button>
              <button className={d.signed_capacity === 'agent' ? 'on' : ''} onClick={() => set({ signed_capacity: 'agent' })}>Landlord's agent</button>
            </div>
          </div>
          <div className="rr-signrow">
            <div><strong>{owner ? owner.name : 'Landlord'}</strong><div className="pmd-mono" style={{ fontSize: 10, color: 'var(--ink-faint)' }}>{joint.length ? 'PRIMARY OWNER' : 'OWNER'} · {d.signed_capacity === 'agent' ? 'agent' : 'landlord'}</div></div>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              {d.signature ? <React.Fragment><SigMark mark={d.signature} small /><span className="badge ok">Signed {fmt(d.signed_at)}</span><button className="btn btn-ghost btn-sm" onClick={() => set({ signature: null, signed_at: null, joint_signatures: [] })}>Clear</button></React.Fragment>
                : <button className="btn btn-primary btn-sm" onClick={() => setSignWho({ primary: true })}>✍ Sign as {owner ? owner.name.split(' ')[0] : 'landlord'}</button>}
            </div>
          </div>
          {joint.length > 0 && <React.Fragment>
            <p className="modal-sub" style={{ margin: '10px 0 6px' }}>Joint-owned property — the primary owner may sign on behalf of all owners, <b>or</b> each joint owner adds their own signature (the official "Additional signatures for joint landlords").</p>
            {joint.map(o => { const signed = (d.joint_signatures || []).find(j => j.owner_id === o.id); return <div className="rr-signrow" key={o.id}>
              <div><strong>{o.name}</strong><div className="pmd-mono" style={{ fontSize: 10, color: 'var(--ink-faint)' }}>JOINT OWNER</div></div>
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                {signed ? <React.Fragment><SigMark mark={signed.mark} small /><span className="badge ok">Signed</span><button className="btn btn-ghost btn-sm" onClick={() => set({ joint_signatures: d.joint_signatures.filter(j => j.owner_id !== o.id) })}>Remove</button></React.Fragment>
                  : <button className="btn btn-ghost btn-sm" disabled={!d.signature} title={!d.signature ? 'The primary owner signs first' : ''} onClick={() => setSignWho({ jointId: o.id })}>+ Add signature</button>}
              </div>
            </div>; })}
          </React.Fragment>}
        </div>
        <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
          <button className="btn btn-ghost" onClick={() => setStep(2)}>← Back to review</button>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-ghost" onClick={() => setViewer(true)}>⤢ View signed form</button>
            <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!d.signature} onClick={() => setStep(4)}>{d.signature ? 'Continue to serve →' : '🔒 Sign first'}</button>
          </div>
        </div>
      </React.Fragment>}

      {step === 4 && <React.Fragment>
        <div style={{ padding: '0 24px' }}>
          <div className="rr-autofill">
            <div><span className="pmd-mono">SERVE TO</span><strong>{tenantNames.join(', ')}</strong></div>
            <div><span className="pmd-mono">METHOD</span><strong>{method(d.method).label}</strong></div>
            <div><span className="pmd-mono">NEW RENT</span><strong>{money(d.proposed_rent)}/mo</strong></div>
            <div><span className="pmd-mono">EFFECTIVE</span><strong>{fmt(d.effective_date)}</strong></div>
            <div><span className="pmd-mono">SIGNED BY</span><strong>{d.signature ? d.signature.name : '—'}{(d.joint_signatures || []).length ? ' +' + d.joint_signatures.length : ''}</strong></div>
            <div><span className="pmd-mono">CAPACITY</span><strong>{d.signed_capacity === 'agent' ? "Landlord's agent" : 'Landlord'}</strong></div>
          </div>
          <p className="modal-sub" style={{ marginTop: 12 }}>Serving files the signed Form 4A against this tenancy and starts two clocks: the <b>{NOTICE_MONTHS}-month notice period</b> to {fmt(d.effective_date)}, and the <b>tenant's window</b> to challenge at the First-tier Tribunal, which closes on the same date. The statutory checks are re-verified by the database on serve.</p>
        </div>
        <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
          <button className="btn btn-ghost" onClick={() => setStep(3)}>← Back to sign</button>
          <button className="btn btn-primary" style={{ width: 'auto' }} disabled={busy} onClick={serve}>{busy ? <Spin /> : 'Serve Form 4A & file →'}</button>
        </div>
      </React.Fragment>}

      {viewer && <Form4AViewer review={{ ...d, signed_name: d.signature ? d.signature.name : null }} tenancy={tenancy} property={property} owner={owner} joint={joint} tenantNames={tenantNames} onClose={() => setViewer(false)} />}
      {signWho && <SignModal who={signWho.primary ? (owner ? owner.name : 'Landlord') : (joint.find(o => o.id === signWho.jointId) || {}).name} role={signWho.primary ? (d.signed_capacity === 'agent' ? "Landlord's agent · Form 4A" : 'Landlord · Form 4A') : 'Joint landlord · Form 4A'}
        onDone={(mark) => { if (signWho.primary) set({ signature: mark, signed_name: owner ? owner.name : mark.name, signed_at: todayISO() }); else set({ joint_signatures: [...(d.joint_signatures || []).filter(j => j.owner_id !== signWho.jointId), { owner_id: signWho.jointId, name: (joint.find(o => o.id === signWho.jointId) || {}).name, mark, at: todayISO() }] }); setSignWho(null); }}
        onClose={() => setSignWho(null)} />}
    </div></div>;
  }

  // ── tracker card (Tenancy tab) ───────────────────────────────────────────
  function RentReviewTracker({ sb, account, tenancy, property, toast }) {
    const entitled = account.features && account.features.rent_review === true;
    const [reviews, setReviews] = useState(null);
    const [owner, setOwner] = useState(null);
    const [joint, setJoint] = useState([]);
    const [tenantNames, setTenantNames] = useState([]);
    const [wizard, setWizard] = useState(null);
    const [viewer, setViewer] = useState(null);
    const today = todayISO();

    const load = useCallback(async () => {
      if (!entitled) return;
      const [n, po, hm] = await Promise.all([
        sb.from('notices').select('*').eq('tenancy_id', tenancy.id).eq('type', 'section_13').order('created_at', { ascending: false }),
        sb.from('property_owners').select('owner_id').eq('property_id', property.id),
        sb.from('tenancy_members').select('tenant_id,position').eq('tenancy_id', tenancy.id),
      ]);
      setReviews(n.data || []);
      const ownerIds = []; if (property.primary_owner_id) ownerIds.push(property.primary_owner_id);
      (po.data || []).forEach(r => { if (!ownerIds.includes(r.owner_id)) ownerIds.push(r.owner_id); });
      if (ownerIds.length) {
        const { data: os } = await sb.from('owners').select('*').in('id', ownerIds);
        const byId = Object.fromEntries((os || []).map(o => [o.id, o]));
        setOwner(byId[ownerIds[0]] || null);
        setJoint(ownerIds.slice(1).map(id => byId[id]).filter(Boolean));
      }
      const tids = (hm.data || []).sort((a, b) => (a.position || 0) - (b.position || 0)).map(m => m.tenant_id);
      if (tids.length) { const { data: ts } = await sb.from('tenants').select('id,full_name,dob').in('id', tids); const byId = Object.fromEntries((ts || []).map(t => [t.id, t]));
        setTenantNames(tids.map(id => byId[id]).filter(t => t && (!t.dob || (Date.now() - new Date(t.dob)) / 31557600000 >= 18)).map(t => t.full_name)); }
    }, [sb, tenancy.id, property.id, entitled]);
    useEffect(() => { load(); }, [load]);

    if (!entitled) return <React.Fragment>
      <div className="card-head"><div><h3>Rent review · Section 13</h3></div><span className="badge none">Professional</span></div>
      <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}><p style={{ margin: 0, color: 'var(--ink-faint)', fontSize: 13.5 }}>Statutory rent reviews — the official Form 4A, auto-populated, e-signed and tracked — are a Professional feature.</p></div>
    </React.Fragment>;
    if (reviews === null) return <div className="card-pad"><Spin /></div>;

    const live = reviews.find(n => !['effective', 'withdrawn'].includes(n.status));
    const cycle = cycleOf(tenancy, today);
    const meta = live ? statusMeta(live.status) : null;
    const finalRent = live ? (live.determined_rent || live.proposed_rent) : null;
    const daysToEff = live ? daysBetween(today, live.effective_date) : null;
    const effPassed = live && daysToEff != null && daysToEff <= 0;
    const progress = live && live.served_at ? Math.max(0, Math.min(100, Math.round((daysBetween(live.served_at, today) / Math.max(daysBetween(live.served_at, live.effective_date) || 1, 1)) * 100))) : 0;

    const patch = async (p) => { const { error } = await sb.from('notices').update(p).eq('id', live.id); if (error) toast(error.message); else load(); };
    const markAck = () => patch({ status: 'acknowledged', acknowledged_at: today });
    const markChallenge = () => { const ref = prompt('First-tier Tribunal reference (optional):', ''); patch({ status: 'challenged', challenged_at: today, tribunal_ref: ref || null }); };
    const markDetermined = () => { const v = prompt('Rent determined by the Tribunal (£ per month):', String(live.proposed_rent)); if (v == null) return; patch({ status: 'determined', determined_at: today, determined_rent: Number(v) || live.proposed_rent }); };
    const withdraw = () => { if (confirm('Withdraw this rent review notice? The rent stays unchanged.')) patch({ status: 'withdrawn', withdrawn_at: today }); };
    const apply = async () => {
      const { error } = await sb.rpc('apply_rent_review', { notice_id: live.id });
      if (error) return toast(error.message);
      toast('New rent applied — ledger now expects ' + money(finalRent) + '/mo'); load();
    };

    const facts = (rows) => <div className="rr-autofill" style={{ margin: '0 0 10px' }}>{rows.map(([l, v], i) => <div key={i}><span className="pmd-mono">{l}</span><strong>{v}</strong></div>)}</div>;

    return <React.Fragment>
      <div className="card-head"><div><h3>Rent review · Section 13</h3><div className="sub">official Form 4A (05.26)</div></div>
        {live ? <span className={'badge ' + meta.tone}>{meta.label}</span>
          : cycle && cycle.status !== 'ontrack' ? <span className={'badge ' + (cycle.status === 'overdue' ? 'bad' : 'warn')}>{cycle.status === 'overdue' ? 'Overdue' : 'Due now'}</span>
          : <span className="badge ok">On track</span>}
      </div>
      <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}>
        {!live && <React.Fragment>
          {facts([['CURRENT RENT', money(tenancy.rent_pcm) + '/mo'], ['ANNIVERSARY', fmt(cycle && cycle.anniversary)], ['WINDOW OPENS', fmt(cycle && cycle.initiateFrom)], ['STATUS', cycle ? (cycle.status === 'overdue' ? 'Overdue' : cycle.status === 'due' ? 'Due now' : 'On track') : '—']])}
          {cycle && cycle.status !== 'ontrack'
            ? <div className={'banner ' + (cycle.status === 'overdue' ? 'pastdue' : 'trial')} style={{ marginBottom: 0 }}>
              <span>{cycle.status === 'overdue' ? 'The anniversary has passed without a review — you can still propose a new rent.' : 'You can now propose a new rent for the coming year.'}</span>
              <span className="spacer" /><button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={() => setWizard({ existing: null })}>Initiate rent review →</button>
            </div>
            : <div className="rr-startearly">
                <p style={{ margin: 0, color: 'var(--ink-faint)', fontSize: 13 }}>The next review opens on <b>{fmt(cycle && cycle.initiateFrom)}</b> — two months before the anniversary, matching the {NOTICE_MONTHS}-month notice the tenant must receive.</p>
                <button className="btn btn-primary btn-sm" style={{ width: 'auto', flex: '0 0 auto' }} onClick={() => setWizard({ existing: null })}>Start early →</button>
              </div>}
        </React.Fragment>}

        {live && (live.status === 'drafted' || live.status === 'approved') && <React.Fragment>
          {facts([['PROPOSED', money(live.proposed_rent) + '/mo'], ['FROM', fmt(live.effective_date)], ['CURRENT', money(live.current_rent) + '/mo'], ['APPROVAL', live.signature ? 'Signed ' + fmt(live.signed_at) : live.approved ? 'Approved ' + fmt(live.approved_at) : 'Draft']])}
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <button className="btn btn-ghost btn-sm" onClick={() => setViewer(live)}>View Form 4A</button>
            <button className="btn btn-primary btn-sm" onClick={() => setWizard({ existing: live })}>{live.signature ? 'Serve to tenant →' : live.approved ? 'Sign & serve →' : 'Continue → review & approve'}</button>
            <button className="btn btn-ghost btn-sm" style={{ color: 'var(--red)' }} onClick={withdraw}>Discard</button>
          </div>
        </React.Fragment>}

        {live && ['served', 'acknowledged', 'challenged', 'determined'].includes(live.status) && <React.Fragment>
          {facts([['NEW RENT', money(finalRent) + '/mo'], ['SERVED', fmt(live.served_at) + ' · ' + method(live.method).label], ['EFFECTIVE', fmt(live.effective_date)], [effPassed ? 'NOTICE PERIOD' : 'COUNTDOWN', effPassed ? 'elapsed' : daysToEff + ' days']])}
          <div className="rr-keydates">
            <div className="rr-keydate done"><span className="rr-kd-dot" /><div><span className="pmd-mono">NOTICE ISSUED</span><strong>{fmt(live.served_at)}</strong><span className="rr-kd-sub">Form 4A served · {method(live.method).label}</span></div></div>
            <div className={'rr-keydate' + (effPassed ? ' done' : ' active')}><span className="rr-kd-dot" /><div><span className="pmd-mono">CHALLENGE PERIOD ENDS</span><strong>{fmt(addDays(live.effective_date, -1))}</strong><span className="rr-kd-sub">tenant may refer to the Tribunal up to this day</span></div></div>
            <div className={'rr-keydate' + (effPassed ? ' active' : '')}><span className="rr-kd-dot" /><div><span className="pmd-mono">RENT INCREASE EFFECTIVE</span><strong>{fmt(live.effective_date)}</strong><span className="rr-kd-sub">new rent of {money(finalRent)}/mo begins</span></div></div>
          </div>
          <div className="rr-progress"><span style={{ width: progress + '%' }}></span></div>
          <div className="rr-progress-labels"><span>Served {fmt(live.served_at)}</span><span>Challenge window closes {fmt(addDays(live.effective_date, -1))}</span></div>
          {live.status === 'challenged' && <div className="banner pastdue" style={{ margin: '10px 0 0' }}>
            <span>Tenant referred the notice to the First-tier Tribunal{live.tribunal_ref ? ' (' + live.tribunal_ref + ')' : ''} on {fmt(live.challenged_at)}. The Tribunal cannot set a rent above the {money(live.proposed_rent)} proposed.</span>
            <span className="spacer" /><button className="btn btn-primary btn-sm" style={{ width: 'auto' }} onClick={markDetermined}>Record determination</button>
          </div>}
          {live.status === 'determined' && <div className="banner trial" style={{ margin: '10px 0 0' }}><span>Tribunal determined <b>{money(live.determined_rent)}/mo</b> on {fmt(live.determined_at)} — applies from {fmt(live.effective_date)}.</span></div>}
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 10 }}>
            {live.status === 'served' && <button className="btn btn-ghost btn-sm" onClick={markAck}>Mark acknowledged</button>}
            {['served', 'acknowledged'].includes(live.status) && <button className="btn btn-ghost btn-sm" style={{ color: 'var(--red)' }} onClick={markChallenge}>Tenant challenged at Tribunal</button>}
            <button className="btn btn-ghost btn-sm" onClick={() => setViewer(live)}>View Form 4A</button>
            <button className="btn btn-ghost btn-sm" onClick={withdraw}>Withdraw notice</button>
            <button className={'btn btn-sm ' + (effPassed && live.status !== 'challenged' ? 'btn-primary' : 'btn-ghost')} disabled={!effPassed || live.status === 'challenged'} title={live.status === 'challenged' ? 'Resolve the Tribunal challenge first' : effPassed ? '' : 'Available from the effective date'} onClick={apply}>{effPassed ? 'Apply new rent ✓' : 'Apply on ' + fmt(live.effective_date)}</button>
          </div>
        </React.Fragment>}

        {reviews.some(h => ['effective', 'withdrawn'].includes(h.status)) && <div style={{ marginTop: 12 }}>
          <span className="pmd-mono" style={{ fontSize: 10, color: 'var(--ink-faint)' }}>PAST REVIEWS</span>
          {reviews.filter(h => ['effective', 'withdrawn'].includes(h.status)).map(h => <div key={h.id} style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 13, padding: '5px 0' }}>
            <span className={'hdot ' + (h.status === 'effective' ? 'ok' : 'warn')} style={h.status !== 'effective' ? { background: 'var(--ink-faint)' } : null}></span>
            <span>{h.status === 'effective' ? money(h.determined_rent || h.proposed_rent) + '/mo from ' + fmt(h.effective_date) : 'Withdrawn ' + fmt(h.withdrawn_at)}</span>
            <button className="linkbtn" onClick={() => setViewer(h)}>view</button>
          </div>)}
        </div>}
      </div>

      {wizard && <Wizard sb={sb} account={account} tenancy={tenancy} property={property} owner={owner} joint={joint} tenantNames={tenantNames.length ? tenantNames : ['Tenant']} existing={wizard.existing} onClose={() => setWizard(null)} onDone={() => { setWizard(null); load(); }} toast={toast} />}
      {viewer && <Form4AViewer review={viewer} tenancy={tenancy} property={property} owner={owner} joint={joint} tenantNames={tenantNames.length ? tenantNames : ['Tenant']} onClose={() => setViewer(null)} />}
    </React.Fragment>;
  }

  window.PMRentReview = RentReviewTracker;

  // tracker-only styles (key-dates milestones + start-early row)
  if (!document.getElementById('rr-tracker-css')) {
    const st = document.createElement('style'); st.id = 'rr-tracker-css';
    st.textContent = `
    .rr-startearly { display: flex; align-items: center; gap: 14px; justify-content: space-between; flex-wrap: wrap; }
    .rr-startearly p { flex: 1; min-width: 220px; }
    .rr-keydates { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 4px 0 14px; }
    @media (max-width: 640px) { .rr-keydates { grid-template-columns: 1fr; } }
    .rr-keydate { display: flex; gap: 9px; align-items: flex-start; background: var(--surface-2); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: 10px 12px; }
    .rr-keydate .rr-kd-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-faint); margin-top: 4px; flex: 0 0 auto; }
    .rr-keydate.done { background: var(--ok-soft); border-color: transparent; }
    .rr-keydate.done .rr-kd-dot { background: var(--ok); }
    .rr-keydate.active { border-color: var(--brand-deep); box-shadow: var(--shadow-sm); }
    .rr-keydate.active .rr-kd-dot { background: var(--brand-deep); }
    .rr-keydate .pmd-mono { display: block; font-size: 9px; letter-spacing: .05em; color: var(--ink-faint); }
    .rr-keydate strong { display: block; font-size: 14px; margin: 1px 0; }
    .rr-kd-sub { display: block; font-size: 11px; color: var(--ink-faint); line-height: 1.35; }
    `;
    document.head.appendChild(st);
  }
})();
