// ════════════════════════════════════════════════════════════════════════
// PropMystro · M8+M10 · pm-d-tenants.jsx
// Faithful port of pm-tenants.jsx + pm-property-tenancy.jsx + pm-tenancy-data
// helpers onto Supabase:
//   • PMTenantsHub — directory: Current/Past/All/Archived tabs, Households &
//     People views, tenant profile (contact + inline email, RTR, tenancy
//     history, GDPR retention panel, archive / request-deletion via the
//     approve_tenant_deletion RPC)
//   • PMTenancy (property Tenancy tab) — current-tenancy summary, agreement
//     card, household with relationships + RTR + turning-18 signals, quick
//     actions, tenancy history, Record-existing-tenancy (migrate) and the
//     gated onboarding wizard → commit_tenancy RPC (server re-checks gates).
// → window.PMTenantsHub, window.PMTenancy
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useCallback } = React;

  const TYPES = [
    { key: 'ast_periodic', label: 'AST · periodic' }, { key: 'ast_fixed', label: 'AST · fixed term' },
    { key: 'company_let', label: 'Company let' }, { key: 'lodger', label: 'Lodger' },
  ];
  const typeLabel = (k) => (TYPES.find(t => t.key === k) || {}).label || k || '—';
  const SCHEMES = ['TDS', 'DPS', 'mydeposits'];
  const RELATIONS = [
    { v: 'lead', l: 'Lead tenant' }, { v: 'partner', l: 'Partner / spouse' }, { v: 'joint', l: 'Joint tenant' },
    { v: 'child', l: 'Child' }, { v: 'other', l: 'Other occupant' },
  ];
  const relLabel = (v) => (RELATIONS.find(r => r.v === v) || {}).l || v || 'Occupant';
  const RTR = [
    { v: 'valid', l: 'Valid' }, { v: 'time-limited', l: 'Time-limited' },
    { v: 'expired', l: 'Expired' }, { v: 'unchecked', l: 'Not checked' },
  ];
  const RETENTION_YEARS = 10;

  const today = () => 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');
  const initials = (s) => (s || '?').trim().split(/\s+/).map(w => w[0]).slice(0, 2).join('').toUpperCase();

  // ── age / minor / turning-18 (verbatim logic from pm-tenancy-data) ──────
  function tenantAge(t) {
    if (!t || !t.dob) return null;
    const d = new Date(t.dob); if (isNaN(d)) return null;
    const n = new Date(); let a = n.getFullYear() - d.getFullYear();
    if (n.getMonth() < d.getMonth() || (n.getMonth() === d.getMonth() && n.getDate() < d.getDate())) a--;
    return a;
  }
  const isMinor = (t) => { const a = tenantAge(t); return a != null && a < 18; };
  function eighteenth(t) {
    if (!t || !t.dob) return null;
    const d = new Date(t.dob); if (isNaN(d)) return null;
    d.setFullYear(d.getFullYear() + 18);
    return d.toISOString().slice(0, 10);
  }
  // bounded window: heads-up 90d before the 18th, escalates after; never flags established adults
  function child18Signal(t) {
    if (!t || !t.dob) return null;
    const iso = eighteenth(t); if (!iso) return null;
    const days = Math.ceil((new Date(iso) - new Date()) / 864e5);
    if (days > 90 || days < -180) return null;
    if (t.rtr_adult_checked_at || t.rtr_status === 'valid' && days < -30) return null;
    if (days > 0) return { stage: 'soon', days, dot: 'warn', label: 'Turns 18 on ' + fmt(iso) + ' — Right to Rent check will be required' };
    return { stage: 'due', days, dot: 'crit', label: 'Turned 18 on ' + fmt(iso) + ' — adult Right to Rent check required' };
  }
  function retentionOf(tenant, tenancies) {
    const mine = tenancies.filter(ty => ty.lead_tenant_id === tenant.id);
    const lastEnd = mine.map(t => t.end_date).filter(Boolean).sort().slice(-1)[0];
    const base = tenant.retention_until || (lastEnd ? (Number(lastEnd.slice(0, 4)) + RETENTION_YEARS) + lastEnd.slice(4) : null);
    if (!base) return null;
    const days = Math.ceil((new Date(base) - new Date()) / 864e5);
    return { until: base, days, expired: days < 0 };
  }

  function Spin() { return <span className="spin dark" />; }
  const rtrPill = (t, c18) => {
    if (c18) return <span className={'badge ' + (c18.dot === 'crit' ? 'bad' : 'warn')} title={c18.label}>{c18.stage === 'due' ? 'RTR due · 18' : 'Turns 18 · ' + c18.days + 'd'}</span>;
    const s = t && t.rtr_status;
    const ok = s === 'valid' || s === 'time-limited';
    return <span className={'badge ' + (ok ? 'ok' : 'warn')}>{s === 'time-limited' ? 'RTR · time-limited' : ok ? 'RTR verified' : 'RTR ' + (s || 'unchecked')}</span>;
  };

  // ════════════════════════════════════════════════════════════════════════
  // M8 · TENANTS HUB
  // ════════════════════════════════════════════════════════════════════════
  function TenantsHub({ sb, account, toast, onOpenProperty }) {
    const [tenants, setTenants] = useState(null);
    const [tenancies, setTenancies] = useState([]);
    const [members, setMembers] = useState([]);
    const [props, setProps] = useState([]);
    const [tab, setTab] = useState('current');      // current | past | all | archived
    const [view, setView] = useState('households'); // households | people
    const [profile, setProfile] = useState(null);   // tenant id

    const load = useCallback(async () => {
      const [tn, ty, tm, p] = await Promise.all([
        sb.from('tenants').select('*').order('full_name', { ascending: true }),
        sb.from('tenancies').select('*').order('start_date', { ascending: false }),
        sb.from('tenancy_members').select('*'),
        sb.from('properties').select('id,address,postcode'),
      ]);
      setTenants(tn.data || []); setTenancies(ty.data || []); setMembers(tm.data || []); setProps(p.data || []);
    }, [sb]);
    useEffect(() => { load(); }, [load]);

    if (tenants === null) return <div className="card card-pad"><Spin /></div>;

    const propById = Object.fromEntries(props.map(p => [p.id, p]));
    const tenantById = Object.fromEntries(tenants.map(t => [t.id, t]));
    const activeTys = tenancies.filter(t => t.status === 'active');

    if (profile) {
      const t = tenantById[profile];
      if (t) return <TenantProfile sb={sb} tenant={t} tenancies={tenancies} members={members} propById={propById} onBack={() => { setProfile(null); load(); }} reload={load} toast={toast} onOpenProperty={onOpenProperty} />;
    }

    const filterTenant = (t) => {
      if (tab === 'archived') return t.status === 'archived';
      if (tab === 'past') return t.status === 'past';
      if (tab === 'current') return t.status === 'current' || !t.status;
      return t.status !== 'archived';
    };
    const shown = tenants.filter(filterTenant);

    // households view: one card per active tenancy (current) / ended (past)
    const hhTys = tab === 'past' ? tenancies.filter(t => t.status === 'ended')
      : tab === 'archived' ? [] : activeTys;
    const householdOf = (ty) => members.filter(m => m.tenancy_id === ty.id).sort((a, b) => (a.position || 0) - (b.position || 0));

    const counts = {
      current: tenants.filter(t => t.status === 'current' || !t.status).length,
      past: tenants.filter(t => t.status === 'past').length,
      all: tenants.filter(t => t.status !== 'archived').length,
      archived: tenants.filter(t => t.status === 'archived').length,
    };

    return <React.Fragment>
      <div className="pagehead" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 14, flexWrap: 'wrap' }}>
        <div><h1>Tenants</h1><p>Everyone who lives in your properties — households, Right to Rent, and the GDPR retention clock.</p></div>
        <div className="tabs" style={{ marginBottom: 0 }}>
          <button className={view === 'households' ? 'on' : ''} onClick={() => setView('households')}>Households</button>
          <button className={view === 'people' ? 'on' : ''} onClick={() => setView('people')}>People</button>
        </div>
      </div>

      <div className="pm-toolbar">
        {['current', 'past', 'all', 'archived'].map(k => <button key={k} className={'btn btn-sm ' + (tab === k ? 'btn-primary' : 'btn-ghost')} onClick={() => setTab(k)}>{k[0].toUpperCase() + k.slice(1)} <span className="pmd-mono" style={{ fontSize: 10, opacity: .75 }}>{counts[k]}</span></button>)}
      </div>

      {view === 'households' && tab !== 'archived' && <div className="tn-cards">
        {hhTys.length === 0 && <div className="card"><div className="empty"><div className="ico">🔑</div><h3>No {tab === 'past' ? 'past' : 'active'} tenancies</h3><p>{tab === 'past' ? 'Ended tenancies appear here.' : 'Create a tenancy from a property\u2019s Tenancy tab.'}</p></div></div>}
        {hhTys.map(ty => {
          const lead = tenantById[ty.lead_tenant_id]; const p = propById[ty.property_id];
          const hh = householdOf(ty);
          return <div className="card tn-card" key={ty.id}>
            <div className="tn-card-top">
              <span className="avatar">{initials(lead ? lead.full_name : '?')}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="t" style={{ fontWeight: 700 }}>{lead ? <button className="linkbtn" onClick={() => setProfile(lead.id)}>{lead.full_name}</button> : 'Household'}</div>
                <div className="s" style={{ fontSize: 12, color: 'var(--ink-faint)' }}>{p ? p.address : '—'} · {typeLabel(ty.type)}</div>
              </div>
              <span className={'badge ' + (ty.status === 'active' ? 'ok' : 'none')}>{ty.status}</span>
            </div>
            <div className="tn-card-facts">
              <span><span className="pmd-mono">RENT</span> {gbp(ty.rent_pcm)}/mo</span>
              <span><span className="pmd-mono">SINCE</span> {fmt(ty.start_date)}</span>
              <span><span className="pmd-mono">HOUSEHOLD</span> {hh.length || 1}</span>
            </div>
            <div className="tn-card-hh">
              {hh.map((m, i) => { const t = tenantById[m.tenant_id]; if (!t) return null; const c18 = child18Signal(t); return <span key={m.tenant_id} className="tn-hh-chip" onClick={() => setProfile(t.id)}>
                {t.full_name}{tenantAge(t) != null ? ' · ' + tenantAge(t) : ''}{isMinor(t) ? ' (minor)' : ''}{c18 ? ' ⚠' : ''}
              </span>; })}
            </div>
            <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
              {lead && <button className="btn btn-ghost btn-sm" onClick={() => setProfile(lead.id)}>Open profile</button>}
              {p && onOpenProperty && <button className="btn btn-ghost btn-sm" onClick={() => onOpenProperty(p)}>Open property</button>}
            </div>
          </div>;
        })}
      </div>}

      {(view === 'people' || tab === 'archived') && <div className="card" style={{ overflow: 'hidden' }}>
        {shown.length === 0 ? <div className="empty"><div className="ico">👤</div><h3>No tenants here</h3><p>{tab === 'archived' ? 'Archived tenants (past retention) appear here.' : 'Tenants are created when you set up a tenancy.'}</p></div>
        : <div style={{ overflowX: 'auto' }}><table className="prop-table">
          <thead><tr><th>Name</th><th>Role / household</th><th>Age</th><th>Right to Rent</th><th>Status</th><th>{tab === 'archived' ? 'Retention' : 'Email'}</th><th></th></tr></thead>
          <tbody>{shown.map(t => {
            const mem = members.find(m => m.tenant_id === t.id);
            const ty = mem && tenancies.find(x => x.id === mem.tenancy_id);
            const p = ty && propById[ty.property_id];
            const c18 = child18Signal(t);
            const ret = retentionOf(t, tenancies);
            return <tr key={t.id} className="clickable" onClick={() => setProfile(t.id)}>
              <td><div className="prop-addr">{t.full_name}</div>{p && <div className="prop-meta">{p.address}</div>}</td>
              <td>{mem ? relLabel(mem.relation) : '—'}</td>
              <td className="pmd-mono" style={{ fontSize: 12 }}>{tenantAge(t) != null ? tenantAge(t) + (isMinor(t) ? ' · minor' : '') : '—'}</td>
              <td>{rtrPill(t, c18)}</td>
              <td><span className={'badge ' + (t.status === 'current' || !t.status ? 'ok' : 'none')}>{t.status || 'current'}</span></td>
              <td className="pmd-mono" style={{ fontSize: 11.5 }}>{tab === 'archived' ? (ret ? 'until ' + fmt(ret.until) : '—') : (t.email || <span style={{ color: 'var(--amber)' }}>no email</span>)}</td>
              <td><span style={{ color: 'var(--ink-faint)' }}>→</span></td>
            </tr>;
          })}</tbody>
        </table></div>}
      </div>}
    </React.Fragment>;
  }

  // ── tenant profile ───────────────────────────────────────────────────────
  function TenantProfile({ sb, tenant, tenancies, members, propById, onBack, reload, toast, onOpenProperty }) {
    const [editEmail, setEditEmail] = useState(false);
    const [email, setEmail] = useState(tenant.email || '');
    const [busy, setBusy] = useState(false);
    const myMems = members.filter(m => m.tenant_id === tenant.id);
    const myTys = tenancies.filter(ty => myMems.some(m => m.tenancy_id === ty.id) || ty.lead_tenant_id === tenant.id)
      .sort((a, b) => (b.start_date || '').localeCompare(a.start_date || ''));
    const active = myTys.find(t => t.status === 'active');
    const ret = retentionOf(tenant, tenancies);
    const c18 = child18Signal(tenant);

    const saveEmail = async () => {
      setBusy(true);
      const { error } = await sb.from('tenants').update({ email: email.trim() || null }).eq('id', tenant.id);
      setBusy(false);
      if (error) return toast(error.message);
      tenant.email = email.trim(); setEditEmail(false); toast('Email saved');
    };
    const setRtr = async (v) => {
      const { error } = await sb.from('tenants').update({ rtr_status: v }).eq('id', tenant.id);
      if (error) return toast(error.message);
      tenant.rtr_status = v; toast('Right to Rent updated'); reload();
    };
    const archive = async () => {
      if (!confirm('Archive ' + tenant.full_name + '? They move to the Archived tab; records are kept until the retention date.')) return;
      const { error } = await sb.from('tenants').update({ status: 'archived' }).eq('id', tenant.id);
      if (error) return toast(error.message);
      toast('Tenant archived'); onBack();
    };
    const gdprDelete = async () => {
      if (!confirm('Permanently erase ' + tenant.full_name + '\u2019s record? This is the GDPR hard delete — admin-only, audited, and refused if still inside the retention window.')) return;
      const { error } = await sb.rpc('approve_tenant_deletion', { tenant_id: tenant.id });
      if (error) return toast(/retention/.test(error.message) ? 'Still inside the retention window — ' + error.message : error.message);
      toast('Tenant record erased (audited)'); onBack();
    };

    return <React.Fragment>
      <button className="backlink" onClick={onBack}>← Back to tenants</button>
      <div className="prop-hero">
        <div className="prop-hero-l">
          <div className="pmd-mono" style={{ fontSize: 11, color: 'var(--ink-faint)' }}>TENANT</div>
          <h1 style={{ fontSize: 26, margin: '2px 0 8px' }}>{tenant.full_name}</h1>
          <div className="prop-hero-meta">
            <span>{tenantAge(tenant) != null ? tenantAge(tenant) + ' years old' + (isMinor(tenant) ? ' · minor' : '') : 'Age unknown'}</span>
            {tenant.phone && <span>{tenant.phone}</span>}
            <span className={'badge ' + (tenant.status === 'current' || !tenant.status ? 'ok' : 'none')}>{tenant.status || 'current'}</span>
            {rtrPill(tenant, c18)}
          </div>
          <div className="prop-hero-meta" style={{ marginBottom: 0 }}>
            {editEmail ? <span style={{ display: 'inline-flex', gap: 6 }}>
              <input className="pm-input" style={{ width: 230 }} value={email} autoFocus onChange={e => setEmail(e.target.value)} placeholder="tenant@email.com" />
              <button className="btn btn-primary btn-sm" disabled={busy} onClick={saveEmail}>{busy ? <Spin /> : 'Save'}</button>
              <button className="btn btn-ghost btn-sm" onClick={() => setEditEmail(false)}>Cancel</button>
            </span> : <span>{tenant.email || <span style={{ color: 'var(--amber)' }}>No email — needed for Welcome Pack &amp; compliance emails</span>} <button className="linkbtn" onClick={() => setEditEmail(true)}>edit</button></span>}
          </div>
        </div>
        <div className="prop-hero-r">
          <div className="field" style={{ margin: 0 }}><label>Right to Rent</label>
            <select className="pm-input" value={tenant.rtr_status || 'unchecked'} onChange={e => setRtr(e.target.value)}>{RTR.map(r => <option key={r.v} value={r.v}>{r.l}</option>)}</select>
          </div>
        </div>
      </div>

      {c18 && <div className="mt-awaab" style={{ margin: '0 0 16px' }}><strong>Turning 18.</strong><span> {c18.label}. Once verified, set Right to Rent to Valid above.</span></div>}

      <div className="card" style={{ marginBottom: 16 }}>
        <div className="card-head"><div><h3>Tenancies</h3><div className="sub">{myTys.length} on record</div></div></div>
        {myTys.length === 0 && <div className="card-pad" style={{ color: 'var(--ink-faint)' }}>No tenancies linked yet.</div>}
        {myTys.map(ty => { const p = propById[ty.property_id]; const mem = myMems.find(m => m.tenancy_id === ty.id); return <div className="row" key={ty.id}>
          <span className="avatar">{(p ? p.address : '?')[0]}</span>
          <div className="main">
            <div className="t">{p ? <button className="linkbtn" onClick={() => onOpenProperty && onOpenProperty(p)}>{p.address}</button> : '—'}{ty.lead_tenant_id === tenant.id ? ' · lead' : mem ? ' · ' + relLabel(mem.relation) : ''}</div>
            <div className="s">{typeLabel(ty.type)} · {fmt(ty.start_date)} → {ty.end_date ? fmt(ty.end_date) : 'ongoing'} · {gbp(ty.rent_pcm)}/mo</div>
          </div>
          <span className={'badge ' + (ty.status === 'active' ? 'ok' : 'none')}>{ty.status}</span>
        </div>; })}
      </div>

      <div className="card">
        <div className="card-head"><div><h3>GDPR &amp; retention</h3><div className="sub">Personal data is kept {RETENTION_YEARS} years after the last tenancy ends</div></div></div>
        <div className="card-pad">
          <div className="kv"><span className="k" style={{ width: 170 }}>Status</span><span className="v">{tenant.status || 'current'}</span></div>
          <div className="kv"><span className="k" style={{ width: 170 }}>Retention until</span><span className="v">{ret ? fmt(ret.until) + (ret.expired ? ' · window passed' : ' · ' + ret.days + ' days left') : (active ? 'Tenancy active — clock starts at departure' : 'No departure recorded')}</span></div>
          <div className="kv"><span className="k" style={{ width: 170 }}>What we hold</span><span className="v" style={{ fontWeight: 400 }}>Identity &amp; contact, Right to Rent evidence, tenancy &amp; payment history</span></div>
          <div style={{ display: 'flex', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
            {tenant.status !== 'archived' && !active && <button className="btn btn-ghost btn-sm" onClick={archive}>Archive tenant</button>}
            <button className="btn btn-ghost btn-sm" style={{ color: 'var(--red)' }} onClick={gdprDelete}>Erase record (GDPR)</button>
          </div>
          <div className="pmd-mono" style={{ fontSize: 10.5, color: 'var(--ink-faint)', marginTop: 8 }}>Erase is admin-only, audited, and refused while inside the retention window — enforced by the database.</div>
        </div>
      </div>
    </React.Fragment>;
  }

  // ════════════════════════════════════════════════════════════════════════
  // M10 · PROPERTY TENANCY TAB (faithful)
  // ════════════════════════════════════════════════════════════════════════
  function TenancyTab({ sb, property, account, toast }) {
    const [tenancies, setTenancies] = useState(null);
    const [members, setMembers] = useState([]);
    const [tmap, setTmap] = useState({});
    const [certs, setCerts] = useState([]);
    const [migrate, setMigrate] = useState(false);
    const [onboard, setOnboard] = useState(false);
    const [building, setBuilding] = useState(false);
    const [agreementDoc, setAgreementDoc] = useState(null);
    const [addingMember, setAddingMember] = useState(false);
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const [m, setM] = useState({ name: '', email: '', dob: '', relation: 'partner', rtr: 'valid' });

    const load = useCallback(async () => {
      const [ty, mem, tn, c] = await Promise.all([
        sb.from('tenancies').select('*').eq('property_id', property.id).order('start_date', { ascending: false }),
        sb.from('tenancy_members').select('*'),
        sb.from('tenants').select('*'),
        sb.from('certificates').select('*').eq('property_id', property.id).eq('type', 'tenancy_agreement'),
      ]);
      const list = ty.data || []; setTenancies(list);
      const ids = new Set(list.map(t => t.id));
      setMembers((mem.data || []).filter(x => ids.has(x.tenancy_id)));
      setTmap(Object.fromEntries((tn.data || []).map(t => [t.id, t])));
      setCerts(c.data || []);
      const act = list.find(t => t.status === 'active');
      if (act && act.agreement_doc_id) {
        const { data: doc } = await sb.from('documents').select('id,filename,storage_path,created_at').eq('id', act.agreement_doc_id).maybeSingle();
        setAgreementDoc(doc || null);
      } else setAgreementDoc(null);
    }, [sb, property.id]);
    useEffect(() => { load(); }, [load]);

    if (tenancies === null) return <div className="card-pad"><Spin /></div>;

    const active = tenancies.find(t => t.status === 'active');
    const past = tenancies.filter(t => t.status === 'ended');
    const household = active ? members.filter(x => x.tenancy_id === active.id).sort((a, b) => (a.position || 0) - (b.position || 0)) : [];
    const lead = active && tmap[active.lead_tenant_id];
    const agreementCert = certs[0];

    const addMember = async () => {
      setErr('');
      if (!m.name.trim()) return setErr('Enter the occupant\u2019s name.');
      setBusy(true);
      const t1 = await sb.from('tenants').insert({ full_name: m.name.trim(), email: m.email.trim() || null, dob: m.dob || null, status: 'current', rtr_status: m.rtr }).select().single();
      if (t1.error) { setBusy(false); return setErr(t1.error.message); }
      const r = await sb.from('tenancy_members').insert({ tenancy_id: active.id, tenant_id: t1.data.id, relation: m.relation, position: household.length });
      setBusy(false);
      if (r.error) return setErr(r.error.message);
      setAddingMember(false); setM({ name: '', email: '', dob: '', relation: 'partner', rtr: 'valid' }); toast('Occupant added'); load();
    };
    const endTenancy = async () => {
      if (!confirm('End this tenancy? It is marked ended today and the household moves to Past — the GDPR retention clock starts.')) return;
      await sb.from('tenancies').update({ status: 'ended', end_date: today() }).eq('id', active.id);
      const ids = household.map(h => h.tenant_id);
      if (ids.length) await sb.from('tenants').update({ status: 'past' }).in('id', ids);
      toast('Tenancy ended'); load();
    };

    // ── vacant state ──────────────────────────────────────────────────────
    if (!active) return <React.Fragment>
      <div className="card-pad">
        <div className="empty" style={{ padding: '24px 16px' }}>
          <div className="ico">🔑</div>
          <h3>No active tenancy</h3>
          <p>{property.address} is currently vacant. Start onboarding to let it (with the statutory gates), or record an existing tenancy if you're migrating a current tenant.</p>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
            <button className="btn btn-ghost btn-sm" onClick={() => setMigrate(true)}>Record existing tenancy</button>
            <button className="btn btn-primary btn-sm" onClick={() => setOnboard(true)}>Start onboarding →</button>
          </div>
        </div>
      </div>
      {past.length > 0 && <History past={past} tmap={tmap} />}
      {migrate && <MigrateModal sb={sb} property={property} onClose={() => setMigrate(false)} onDone={() => { setMigrate(false); load(); }} toast={toast} />}
      {onboard && (() => { const Wiz = window.PMOnboardWizard || OnboardWizard; return <Wiz sb={sb} property={property} account={account} onClose={() => setOnboard(false)} onDone={async (next) => { setOnboard(false); await load(); if (next === 'agreement') setBuilding(true); }} toast={toast} />; })()}
    </React.Fragment>;

    // ── active tenancy ────────────────────────────────────────────────────
    return <React.Fragment>
      {/* summary */}
      <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}>
        <div className="tn-summary">
          <div><span className="pmd-mono">LEAD TENANT</span><strong>{lead ? lead.full_name : '—'}</strong></div>
          <div><span className="pmd-mono">HOUSEHOLD</span><strong>{household.length || 1} {household.length === 1 ? 'person' : 'people'}</strong></div>
          <div><span className="pmd-mono">TYPE</span><strong>{typeLabel(active.type)}</strong></div>
          <div><span className="pmd-mono">RENT</span><strong>{gbp(active.rent_pcm)}/mo{active.pay_day ? ' · day ' + active.pay_day : ''}</strong></div>
          <div><span className="pmd-mono">START</span><strong>{fmt(active.start_date)}</strong></div>
          <div><span className="pmd-mono">{active.end_date ? 'ENDS' : 'BASIS'}</span><strong>{active.end_date ? fmt(active.end_date) : 'rolling / periodic'}</strong></div>
          <div><span className="pmd-mono">DEPOSIT</span><strong>{Number(active.deposit_amount) ? gbp(active.deposit_amount) + ' · ' + (active.deposit_scheme || '—') : 'none'}</strong></div>
          <div><span className="pmd-mono">RENT REVIEW</span><strong>{active.rent_review_anchor ? fmt(active.rent_review_anchor) : '—'}</strong></div>
        </div>
      </div>

      {/* agreement */}
      <div className="card-head"><div><h3>Tenancy agreement</h3></div>
        {agreementDoc || agreementCert ? <span className="badge ok">signed</span> : active.type === 'ast_fixed' ? <span className="badge none">legacy doc</span> : <span className="badge warn">not on file</span>}
      </div>
      <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}>
        {agreementDoc ? <div className="row" style={{ padding: 0, border: 0 }}>
          <span className="avatar" style={{ background: 'var(--ok-soft)' }}>📄</span>
          <div className="main"><div className="t">{agreementDoc.filename}</div><div className="s">e-signed{agreementDoc.created_at ? ' · filed ' + fmt(agreementDoc.created_at) : ''}</div></div>
          <button className="btn btn-ghost btn-sm" onClick={async () => { const { data } = await sb.storage.from('documents').createSignedUrl(agreementDoc.storage_path, 60); if (data) window.open(data.signedUrl, '_blank'); }}>View ↗</button>
        </div> : agreementCert ? <div className="row" style={{ padding: 0, border: 0 }}>
          <span className="avatar" style={{ background: 'var(--ok-soft)' }}>📄</span>
          <div className="main"><div className="t">{agreementCert.file_name || 'Tenancy agreement'}</div><div className="s">{agreementCert.cert_number || ''}{agreementCert.created_at ? ' · filed ' + fmt(agreementCert.created_at) : ''}</div></div>
          {agreementCert.storage_url && <a className="btn btn-ghost btn-sm" href={agreementCert.storage_url} target="_blank" rel="noopener">View ↗</a>}
        </div> : <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
          <p style={{ margin: 0, color: 'var(--ink-faint)', fontSize: 13.5 }}>{active.type === 'ast_fixed' ? 'This legacy tenancy predates the digital agreement flow — upload the signed agreement via Documents, or generate a fresh one.' : 'No signed agreement on file for this tenancy.'}</p>
          {window.PMAgreementBuilder && <button className="btn btn-primary btn-sm" onClick={() => setBuilding(true)}>Generate &amp; sign →</button>}
        </div>}
      </div>

      {/* household */}
      <div className="card-head"><div><h3>Household</h3><div className="sub">relationship · Right to Rent</div></div>
        {!addingMember && <button className="btn btn-ghost btn-sm" onClick={() => { setAddingMember(true); setErr(''); }}>+ Add occupant</button>}</div>
      {addingMember && <div className="card-pad" style={{ borderBottom: '1px solid var(--line-soft)' }}>
        {err && <div className="alert alert-err"><span className="ic">⚠</span><div>{err}</div></div>}
        <div className="inline-form">
          <div className="field grow"><label>Name</label><input value={m.name} onChange={e => setM({ ...m, name: e.target.value })} placeholder="Full name" /></div>
          <div className="field"><label>Date of birth</label><input type="date" value={m.dob} onChange={e => setM({ ...m, dob: e.target.value })} /></div>
          <div className="field"><label>Relation</label><select value={m.relation} onChange={e => setM({ ...m, relation: e.target.value })}>{RELATIONS.filter(r => r.v !== 'lead').map(r => <option key={r.v} value={r.v}>{r.l}</option>)}</select></div>
          <div className="field"><label>Right to Rent</label><select value={m.rtr} onChange={e => setM({ ...m, rtr: e.target.value })}>{RTR.map(r => <option key={r.v} value={r.v}>{r.l}</option>)}</select></div>
          <button className="btn btn-primary btn-sm" onClick={addMember} disabled={busy}>{busy ? <Spin /> : 'Add'}</button>
          <button className="btn btn-ghost btn-sm" onClick={() => setAddingMember(false)}>Cancel</button>
        </div>
      </div>}
      {household.map((h, i) => {
        const t = tmap[h.tenant_id] || {};
        const c18 = child18Signal(t); const age = tenantAge(t);
        return <div className="row" key={h.tenant_id}>
          <span className="avatar" style={i === 0 ? { background: 'var(--amber-soft)', color: '#8a5418' } : null}>{initials(t.full_name)}</span>
          <div className="main">
            <div className="t">{t.full_name || 'Occupant'}</div>
            <div className="s">{i === 0 ? 'Lead tenant' : relLabel(h.relation)}{age != null ? ' · ' + age + (isMinor(t) ? ' · minor' : '') : ''}{t.email ? ' · ' + t.email : ''}</div>
          </div>
          {rtrPill(t, c18)}
        </div>;
      })}

      {/* rent review tracker (Section 13 / Form 4A) */}
      {window.PMRentReview && <window.PMRentReview sb={sb} account={account} tenancy={active} property={property} toast={toast} />}

      {/* quick actions */}
      <div className="card-pad" style={{ borderTop: '1px solid var(--line-soft)', display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
        <button className="btn btn-ghost btn-sm" onClick={() => window.PMAgreementBuilder ? setBuilding(true) : toast('Renewal generates a fresh periodic AST.')}>Generate renewal</button>
        <button className="btn btn-ghost btn-sm" style={{ color: 'var(--red)' }} onClick={endTenancy}>End tenancy</button>
      </div>

      {past.length > 0 && <History past={past} tmap={tmap} />}
      {building && window.PMAgreementBuilder && <window.PMAgreementBuilder sb={sb} account={account} property={property} tenancy={active} onClose={() => setBuilding(false)} onDone={() => { setBuilding(false); load(); }} toast={toast} />}
    </React.Fragment>;
  }

  function History({ past, tmap }) {
    return <React.Fragment>
      <div className="card-head"><div><h3>Tenancy history</h3><div className="sub">{past.length} past</div></div></div>
      <div style={{ overflowX: 'auto' }}><table className="prop-table">
        <thead><tr><th>Lead tenant</th><th>Type</th><th>From</th><th>To</th><th>Rent</th></tr></thead>
        <tbody>{past.map(ty => { const lead = tmap[ty.lead_tenant_id]; return <tr key={ty.id}>
          <td>{lead ? lead.full_name : '—'}</td>
          <td className="pmd-mono" style={{ fontSize: 12 }}>{typeLabel(ty.type)}</td>
          <td className="pmd-mono" style={{ fontSize: 12 }}>{fmt(ty.start_date)}</td>
          <td className="pmd-mono" style={{ fontSize: 12 }}>{ty.end_date ? fmt(ty.end_date) : '—'}</td>
          <td>{gbp(ty.rent_pcm)}</td>
        </tr>; })}</tbody>
      </table></div>
    </React.Fragment>;
  }

  // ── migrate: record an existing tenancy (no gates — agreement already signed)
  function MigrateModal({ sb, property, onClose, onDone, toast }) {
    const [d, setD] = useState({ leadName: '', leadEmail: '', members: '', type: 'ast_fixed', start: '', end: '', rent: '', payDay: '1', deposit: '', scheme: 'TDS' });
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const set = (patch) => setD(s => ({ ...s, ...patch }));
    const valid = d.leadName.trim().length > 1 && Number(d.rent) > 0 && d.start;
    const save = async () => {
      setErr(''); setBusy(true);
      const t1 = await sb.from('tenants').insert({ full_name: d.leadName.trim(), email: d.leadEmail.trim() || null, status: 'current', rtr_status: 'valid' }).select().single();
      if (t1.error) { setBusy(false); return setErr(t1.error.message); }
      const t2 = await sb.from('tenancies').insert({
        property_id: property.id, lead_tenant_id: t1.data.id, type: d.type, status: 'active',
        start_date: d.start, end_date: d.end || null, rent_pcm: Number(d.rent), pay_day: Number(d.payDay) || null,
        deposit_amount: d.deposit ? Number(d.deposit) : null, deposit_scheme: d.deposit ? d.scheme : null,
        deposit_protected_at: d.deposit ? today() : null,
      }).select().single();
      if (t2.error) { setBusy(false); return setErr(t2.error.message); }
      const rows = [{ tenancy_id: t2.data.id, tenant_id: t1.data.id, relation: 'lead', position: 0 }];
      const names = d.members.split(',').map(s => s.trim()).filter(Boolean);
      for (let i = 0; i < names.length; i++) {
        const tx = await sb.from('tenants').insert({ full_name: names[i], status: 'current', rtr_status: 'unchecked' }).select().single();
        if (!tx.error && tx.data) rows.push({ tenancy_id: t2.data.id, tenant_id: tx.data.id, relation: 'other', position: i + 1 });
      }
      await sb.from('tenancy_members').insert(rows);
      setBusy(false); toast('Existing tenancy recorded'); onDone();
    };
    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 640 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>MIGRATE · EXISTING TENANCY</div><h2>Record an existing tenancy</h2><p className="modal-sub">For current tenants already in a signed agreement — writes the tenancy as active without the onboarding gates. New lets should use the onboarding wizard.</p></div><button className="x" onClick={onClose}>✕</button></div>
      {err && <div style={{ padding: '0 24px' }}><div className="alert alert-err"><span className="ic">⚠</span><div>{err}</div></div></div>}
      <div className="modal-form">
        <div className="field"><label>Lead tenant</label><input value={d.leadName} autoFocus onChange={e => set({ leadName: e.target.value })} /></div>
        <div className="field"><label>Lead email</label><input value={d.leadEmail} onChange={e => set({ leadEmail: e.target.value })} /></div>
        <div className="field full"><label>Other household (comma-separated)</label><input value={d.members} placeholder="e.g. Asha Patel, Dev Patel" onChange={e => set({ members: e.target.value })} /></div>
        <div className="field"><label>Tenancy type</label><select value={d.type} onChange={e => set({ type: e.target.value })}>{TYPES.map(t => <option key={t.key} value={t.key}>{t.label}</option>)}</select></div>
        <div className="field"><label>Start date</label><input type="date" value={d.start} onChange={e => set({ start: e.target.value })} /></div>
        <div className="field"><label>End date (fixed term)</label><input type="date" value={d.end} onChange={e => set({ end: e.target.value })} /></div>
        <div className="field"><label>Rent £ pcm</label><input value={d.rent} onChange={e => set({ rent: e.target.value.replace(/[^\d.]/g, '') })} /></div>
        <div className="field"><label>Deposit £</label><input value={d.deposit} onChange={e => set({ deposit: e.target.value.replace(/[^\d.]/g, '') })} /></div>
        <div className="field"><label>Deposit scheme</label><select value={d.scheme} onChange={e => set({ scheme: e.target.value })}>{SCHEMES.map(s => <option key={s} value={s}>{s}</option>)}</select></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={!valid || busy} onClick={save}>{busy ? <Spin /> : 'Record tenancy'}</button>
      </div>
    </div></div>;
  }

  // ── onboarding wizard: 3 steps + statutory gates → commit_tenancy RPC ────
  function OnboardWizard({ sb, property, account, onClose, onDone, toast }) {
    const [step, setStep] = useState(1);
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const [committed, setCommitted] = useState(false);
    const [d, setD] = useState({
      leadName: '', leadEmail: '', leadDob: '', members: '',
      type: 'ast_periodic', start: today(), rent: property.rent || '', payDay: '1', deposit: '', scheme: 'TDS',
      gateRtr: false, gateDeposit: false, gateInfo: false,
    });
    const set = (patch) => setD(s => ({ ...s, ...patch }));
    const step1ok = d.leadName.trim().length > 1;
    const step2ok = Number(d.rent) > 0 && d.start;
    const gatesOk = d.gateRtr && d.gateDeposit && d.gateInfo;

    const commit = async () => {
      setErr(''); setBusy(true);
      // create the people first (lead + others)
      const t1 = await sb.from('tenants').insert({ full_name: d.leadName.trim(), email: d.leadEmail.trim() || null, dob: d.leadDob || null, status: 'current', rtr_status: 'valid' }).select().single();
      if (t1.error) { setBusy(false); return setErr(t1.error.message); }
      const memberRows = [{ tenant_id: t1.data.id, relation: 'lead', position: 0 }];
      const names = d.members.split(',').map(s => s.trim()).filter(Boolean);
      for (let i = 0; i < names.length; i++) {
        const tx = await sb.from('tenants').insert({ full_name: names[i], status: 'current', rtr_status: 'valid' }).select().single();
        if (!tx.error && tx.data) memberRows.push({ tenant_id: tx.data.id, relation: 'other', position: i + 1 });
      }
      // the gated, atomic commit — the database re-checks the gates
      const { data, error } = await sb.rpc('commit_tenancy', { payload: {
        account_id: account.id, property_id: property.id, type: d.type,
        start_date: d.start, rent_pcm: Number(d.rent), pay_day: Number(d.payDay) || null,
        deposit_amount: d.deposit ? Number(d.deposit) : null, deposit_scheme: d.deposit ? d.scheme : null,
        deposit_protected_at: d.deposit ? today() : null,
        lead_tenant_id: t1.data.id, members: memberRows,
        gates: { rtr: 'valid', deposit: d.deposit ? (d.scheme + '-protected') : 'na-no-deposit', info_sheet: true },
        serve_info_sheet: true,
      } });
      setBusy(false);
      if (error) return setErr(error.message);
      toast('Tenancy created — gates verified server-side, info sheet served');
      setCommitted(true);
    };

    if (committed) return <div className="modal-scrim"><div className="modal" style={{ maxWidth: 520 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>ONBOARDING · COMPLETE</div><h2>Tenancy is live ✓</h2><p className="modal-sub">{property.address} · the statutory gates were re-verified by the database and the prescribed-information notice is on file.</p></div></div>
      <div style={{ padding: '0 24px' }}>
        <div className="alert alert-ok"><span className="ic">✓</span><div>One step left to a complete file: generate the tenancy agreement from your template and e-sign it.</div></div>
      </div>
      <div className="modal-foot" style={{ justifyContent: 'space-between', gap: 8 }}>
        <button className="btn btn-ghost" onClick={() => onDone()}>Finish</button>
        <button className="btn btn-primary" style={{ width: 'auto' }} onClick={() => onDone('agreement')}>Generate &amp; sign agreement →</button>
      </div>
    </div></div>;

    return <div className="modal-scrim" onClick={onClose}><div className="modal" style={{ maxWidth: 640 }} onClick={e => e.stopPropagation()}>
      <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>ONBOARDING · STEP {step} OF 3</div><h2>{step === 1 ? 'Who is moving in?' : step === 2 ? 'Tenancy terms' : 'Statutory gates'}</h2><p className="modal-sub">{step === 3 ? 'The tenancy can only go active when every gate is met — the database re-checks them on commit.' : property.address}</p></div><button className="x" onClick={onClose}>✕</button></div>
      {err && <div style={{ padding: '0 24px' }}><div className="alert alert-err"><span className="ic">⚠</span><div>{err}</div></div></div>}

      {step === 1 && <div className="modal-form">
        <div className="field"><label>Lead tenant</label><input value={d.leadName} autoFocus onChange={e => set({ leadName: e.target.value })} /></div>
        <div className="field"><label>Email</label><input value={d.leadEmail} onChange={e => set({ leadEmail: e.target.value })} /></div>
        <div className="field"><label>Date of birth</label><input type="date" value={d.leadDob} onChange={e => set({ leadDob: e.target.value })} /></div>
        <div className="field"><label>Other household (comma-separated)</label><input value={d.members} placeholder="optional" onChange={e => set({ members: e.target.value })} /></div>
      </div>}

      {step === 2 && <div className="modal-form">
        <div className="field"><label>Tenancy type</label><select value={d.type} onChange={e => set({ type: e.target.value })}>{TYPES.map(t => <option key={t.key} value={t.key}>{t.label}</option>)}</select></div>
        <div className="field"><label>Start date</label><input type="date" value={d.start} onChange={e => set({ start: e.target.value })} /></div>
        <div className="field"><label>Rent £ pcm</label><input value={d.rent} onChange={e => set({ rent: e.target.value.replace(/[^\d.]/g, '') })} /></div>
        <div className="field"><label>Pay day</label><input value={d.payDay} onChange={e => set({ payDay: e.target.value.replace(/\D/g, '').slice(0, 2) })} /></div>
        <div className="field"><label>Deposit £</label><input value={d.deposit} onChange={e => set({ deposit: e.target.value.replace(/[^\d.]/g, '') })} /></div>
        <div className="field"><label>Deposit scheme</label><select value={d.scheme} onChange={e => set({ scheme: e.target.value })}>{SCHEMES.map(s => <option key={s} value={s}>{s}</option>)}</select></div>
      </div>}

      {step === 3 && <div style={{ padding: '0 24px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        <label className={'gate-row' + (d.gateRtr ? ' on' : '')}><input type="checkbox" checked={d.gateRtr} onChange={e => set({ gateRtr: e.target.checked })} /><div><strong>Right to Rent verified</strong><div className="s">Every adult's RTR has been checked and is valid (original documents or share code).</div></div></label>
        <label className={'gate-row' + (d.gateDeposit ? ' on' : '')}><input type="checkbox" checked={d.gateDeposit} onChange={e => set({ gateDeposit: e.target.checked })} /><div><strong>Deposit protected{d.deposit ? ' · ' + d.scheme : ''}</strong><div className="s">{d.deposit ? 'The £' + d.deposit + ' deposit is registered with ' + d.scheme + ' within 30 days.' : 'No deposit taken — tick to confirm.'}</div></div></label>
        <label className={'gate-row' + (d.gateInfo ? ' on' : '')}><input type="checkbox" checked={d.gateInfo} onChange={e => set({ gateInfo: e.target.checked })} /><div><strong>Prescribed information &amp; How to Rent served</strong><div className="s">The info sheet is recorded as a served notice on commit, automatically.</div></div></label>
      </div>}

      <div className="modal-foot" style={{ justifyContent: 'space-between', gap: 8 }}>
        <button className="btn btn-ghost" onClick={() => step > 1 ? setStep(step - 1) : onClose()}>{step > 1 ? '← Back' : 'Cancel'}</button>
        {step < 3 ? <button className="btn btn-primary" style={{ width: 'auto' }} disabled={step === 1 ? !step1ok : !step2ok} onClick={() => setStep(step + 1)}>Continue →</button>
          : <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!gatesOk || busy} onClick={commit}>{busy ? <Spin /> : 'Create tenancy ✓'}</button>}
      </div>
    </div></div>;
  }

  window.PMTenantsHub = TenantsHub;
  window.PMTenancy = TenancyTab;
})();
