// ════════════════════════════════════════════════════════════════════════
// PropMystro · G8 · pm-d-settings.jsx  (mirror of pm-settings.jsx PMSettingsHub)
// Settings hub — five surfaces under one roof: Notifications, Data retention,
// Integrations, Document filing, Account & data. Settings persist to the
// account_settings table (one jsonb blob per account, admin-write via RLS) —
// the direct replacement for the Apps Script PM_SETTINGS Script-Properties
// blob. Non-admins see everything read-only. → window.PMSettingsHub
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useCallback } = React;

  const SECTIONS = [
    { key: 'notifications', label: 'Notifications', icon: '✉' },
    { key: 'retention',     label: 'Data retention', icon: '⧗' },
    { key: 'integrations',  label: 'Integrations', icon: '⚡' },
    { key: 'documents',     label: 'Document filing', icon: '▤' },
    { key: 'permissions',   label: 'Team & roles', icon: '⊞' },
    { key: 'workspace',     label: 'Account & data', icon: '⟳' },
  ];

  const SUPER_ADMINS = ['hemal@sonisha.com', 'hemal@propmystro.com', 'alpahemal@yahoo.co.uk'];
  const COMP_TIERS = [
    { id: 'portfolio', label: 'Portfolio', blurb: 'Everything — tax, inspections, API' },
    { id: 'professional', label: 'Professional', blurb: 'Money + maintenance tools' },
    { id: 'starter', label: 'Starter', blurb: 'Core compliance only' },
  ];

  function Spin() { return <span className="spin dark" />; }

  function SettingsHub({ sb, account, isAdmin, toast, go, myEmail, myId }) {
    const isSuper = SUPER_ADMINS.includes(String(myEmail || '').toLowerCase());
    const [section, setSection] = useState('notifications');
    const [data, setData] = useState(null);          // the settings blob
    const [owners, setOwners] = useState([]);

    const load = useCallback(async () => {
      const [s, o] = await Promise.all([
        sb.from('account_settings').select('data').eq('account_id', account.id).maybeSingle(),
        sb.from('owners').select('id,name,email,color').order('name', { ascending: true }),
      ]);
      setData((s.data && s.data.data) || {});
      setOwners(o.data || []);
    }, [sb, account.id]);
    useEffect(() => { load(); }, [load]);

    const save = async (patch) => {
      const prev = data;
      const next = { ...(data || {}), ...patch };
      setData(next);   // optimistic
      const { error } = await sb.from('account_settings').upsert({ account_id: account.id, data: next });
      if (error) {
        setData(prev);
        toast(isAdmin ? error.message : 'Only an account admin can change settings');
      }
    };

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

    return <React.Fragment>
      <SettingsStyles />
      <div className="pagehead">
        <h1>Settings</h1>
        <p>Configure once — who gets notified, how long data is kept, what's connected, and how documents are filed.{!isAdmin ? ' You have view-only access; an account admin can make changes.' : ''}</p>
      </div>

      <div className="set-layout">
        <nav className="set-rail">
          {SECTIONS.concat(isSuper ? [{ key: 'comps', label: 'Friends & comps', icon: '♡' }] : []).map(s => <button key={s.key} className={'set-tab' + (section === s.key ? ' on' : '')} onClick={() => setSection(s.key)}>
            <span className="set-tab-ico">{s.icon}</span>{s.label}
          </button>)}
        </nav>

        <div className="card set-pane">
          {section === 'notifications' && <Notifications s={data} save={save} owners={owners} isAdmin={isAdmin} sb={sb} toast={toast} />}
          {section === 'retention' && <Retention s={data} save={save} sb={sb} isAdmin={isAdmin} />}
          {section === 'integrations' && <Integrations s={data} save={save} isAdmin={isAdmin} isSuper={isSuper} sb={sb} go={go} />}
          {section === 'documents' && <DocumentFiling s={data} save={save} isAdmin={isAdmin} />}
          {section === 'permissions' && <TeamRoles sb={sb} account={account} data={data} save={save} isAdmin={isAdmin} myId={myId} toast={toast} />}
          {section === 'comps' && isSuper && <FriendsComps sb={sb} toast={toast} myEmail={myEmail} />}
          {section === 'workspace' && <Workspace sb={sb} account={account} go={go} isAdmin={isAdmin} isSuper={isSuper} toast={toast} />}
        </div>
      </div>
    </React.Fragment>;
  }

  // shared row
  function Row({ title, desc, children }) {
    return <div className="set-row">
      <div className="set-row-l"><strong>{title}</strong>{desc && <span className="set-row-d">{desc}</span>}</div>
      <div className="set-row-c">{children}</div>
    </div>;
  }
  function Pill({ tone, children }) {
    return <span className={'badge ' + (tone || 'none')}>{children}</span>;
  }
  function Stepper({ value, unit, min, max, step, onChange, disabled }) {
    return <div className="set-stepper">
      <button className="btn btn-ghost btn-sm" disabled={disabled} onClick={() => onChange(Math.max(min, value - step))}>−</button>
      <span className="pmd-mono set-stepper-v">{value} {unit}</span>
      <button className="btn btn-ghost btn-sm" disabled={disabled} onClick={() => onChange(Math.min(max, value + step))}>+</button>
    </div>;
  }

  // ── Notifications ────────────────────────────────────────────────────
  function Notifications({ s, save, owners, isAdmin, sb, toast }) {
    const digest = s.notifyDigest || 'weekly';
    const recipients = s.notifyRecipients || {};
    const withEmail = owners.filter(o => o.email);
    const [testState, setTestState] = useState('idle'); // idle | sending | sent | err
    const [testMsg, setTestMsg] = useState('');
    const sendTest = async () => {
      setTestState('sending'); setTestMsg('');
      try {
        const { data, error } = await sb.functions.invoke('send-digest', { body: {} });
        if (error) throw new Error(error.message || 'Function call failed');
        if (data && data.error) throw new Error(data.error);
        const line = data && data.results && data.results[0] ? data.results[0] : 'Sent.';
        if (/SEND FAILED|skipped/i.test(line)) { setTestState('err'); setTestMsg(line); }
        else { setTestState('sent'); setTestMsg(line); }
      } catch (e) {
        setTestState('err');
        setTestMsg(/Failed to send a request|not found|404/i.test(e.message) ? 'The send-digest function isn\u2019t deployed yet — see supabase/digest-setup-guide.md.' : e.message);
      }
    };
    return <div className="card-pad set-section">
      <h3>Notifications</h3>
      <p className="set-blurb">An email digest of everything needing attention — compliance expiries, arrears, escalated tickets, tax deadlines — sent to each owner on the schedule you pick. Weekly digests go out on Monday mornings.</p>
      <Row title="Email digest" desc="How often the summary goes out">
        <div className="set-seg">
          {['off', 'daily', 'weekly'].map(opt => <button key={opt} className={'set-seg-btn' + (digest === opt ? ' on' : '')} disabled={!isAdmin} onClick={() => save({ notifyDigest: opt })}>{opt[0].toUpperCase() + opt.slice(1)}</button>)}
        </div>
      </Row>
      <Row title="Recipients" desc="Owners who receive the digest">
        {withEmail.length === 0 ? <span className="set-muted pmd-mono">No owner emails on file — add them under Owners</span>
          : <div className="set-checks">
            {withEmail.map(o => <label key={o.id} className="set-check">
              <input type="checkbox" checked={recipients[o.id] !== false} disabled={!isAdmin} onChange={e => save({ notifyRecipients: { ...recipients, [o.id]: e.target.checked } })} />
              <span className="owner-mini" style={{ background: o.color || '#385a8a', marginRight: 2 }}>{(o.name || '?')[0]}</span>
              <span>{o.name}</span>
            </label>)}
          </div>}
      </Row>
      <Row title="Warning threshold" desc="Days before a certificate expiry first flags amber">
        <Stepper value={s.notifyWarnDays || 90} unit="days" min={30} max={180} step={30} disabled={!isAdmin} onChange={v => save({ notifyWarnDays: v })} />
      </Row>
      <Row title="Critical threshold" desc="Days before expiry it escalates to red">
        <Stepper value={s.notifyCritDays || 30} unit="days" min={7} max={60} step={7} disabled={!isAdmin} onChange={v => save({ notifyCritDays: v })} />
      </Row>
      <Row title="Test the digest" desc="Sends today's digest to the recipients above, right now, ignoring the schedule">
        <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} disabled={!isAdmin || testState === 'sending'} onClick={sendTest}>{testState === 'sending' ? 'Sending…' : 'Send test digest ✉'}</button>
      </Row>
      {testState === 'sent' && <div className="set-note"><span className="hdot ok" />{testMsg}</div>}
      {testState === 'err' && <div className="set-note crit"><span className="hdot crit" />{testMsg}</div>}
    </div>;
  }

  // ── Data retention ───────────────────────────────────────────────────
  function Retention({ s, save, sb, isAdmin }) {
    const years = s.tenantArchive || 10;
    const [archived, setArchived] = useState(null);
    useEffect(() => {
      sb.from('tenants').select('id', { count: 'exact', head: true }).eq('status', 'archived')
        .then(r => setArchived(r.count || 0));
    }, [sb]);
    return <div className="card-pad set-section">
      <h3>Data retention</h3>
      <p className="set-blurb">GDPR Article 5(1)(e) — personal data kept no longer than necessary. Tenant records auto-purge this many years after their last tenancy ends; deletion is blocked before the window closes.</p>
      <Row title="Tenant data retention" desc="Years to keep a past tenant after their last tenancy ends">
        <Stepper value={years} unit="years" min={1} max={15} step={1} disabled={!isAdmin} onChange={v => save({ tenantArchive: v })} />
      </Row>
      <Row title="Document originals" desc="Uploaded PDFs and scans in secure storage">
        <span className="set-muted pmd-mono">Kept indefinitely (compliance evidence)</span>
      </Row>
      <Row title="Audit log" desc="Activity + access trail retention">
        <span className="set-muted pmd-mono">Append-only · never purged</span>
      </Row>
      <div className="set-note">
        <span className="hdot ok" />
        {archived === null ? 'Counting archived tenants…' : archived + ' archived tenant' + (archived === 1 ? '' : 's') + ' on record · the purge check runs against the ' + years + '-year window before any deletion is allowed.'}
      </div>
    </div>;
  }

  // ── Integrations ─────────────────────────────────────────────────────
  function Integrations({ s, save, isAdmin, isSuper, sb, go }) {
    const [bank, setBank] = useState(undefined);   // undefined loading · null none · row
    useEffect(() => {
      sb.from('bank_connections').select('institution_name,status,last_synced_at').neq('status', 'revoked').order('created_at', { ascending: false }).limit(1)
        .then(r => setBank(r.error ? null : ((r.data || [])[0] || null)));
    }, [sb]);
    const bankLinked = bank && bank.status === 'linked';
    return <div className="card-pad set-section">
      <h3>Integrations</h3>
      <p className="set-blurb">Connections you control for this account. Link your bank feed and tax submission here — the platform-run services below are managed centrally.</p>

      <div className="int-group-l pmd-mono">YOUR CONNECTIONS</div>
      <Row title="Open Banking feed" desc="Enable Banking — transactions auto-sync into Bank rec daily">
        {bank === undefined ? <Pill tone="none">…</Pill>
          : bankLinked ? <Pill tone="ok">{(bank.institution_name || 'Bank') + ' linked'}</Pill>
          : bank && bank.status === 'expired' ? <Pill tone="warn">Consent expired</Pill>
          : <Pill tone="none">Not connected</Pill>}
        <button className="btn btn-ghost btn-sm" onClick={() => go && go('bank')}>{bankLinked ? 'Manage in Bank rec →' : 'Connect in Bank rec →'}</button>
      </Row>
      <Row title="HMRC MTD-ITSA" desc="Quarterly updates + final declaration submission endpoint">
        <Pill tone={s.hmrcConnected ? 'warn' : 'none'}>{s.hmrcConnected ? 'Sandbox linked' : 'Simulated'}</Pill>
        <button className="btn btn-ghost btn-sm" disabled={!isAdmin} onClick={() => save({ hmrcConnected: !s.hmrcConnected })}>{s.hmrcConnected ? 'Unlink' : 'Link HMRC…'}</button>
      </Row>
      <Row title="Email digests" desc="Automated attention digests sent to owners">
        <Pill tone={(s.notifyDigest || 'weekly') === 'off' ? 'none' : 'ok'}>{(s.notifyDigest || 'weekly') === 'off' ? 'Off' : 'On (' + (s.notifyDigest || 'weekly') + ')'}</Pill>
        <span className="set-muted pmd-mono">set under Notifications</span>
      </Row>

      {isSuper ? <React.Fragment>
        <div className="int-group-l pmd-mono" style={{ marginTop: 18 }}><span className="dev-badge">PLATFORM</span> CENTRALLY MANAGED · SUPER-ADMIN</div>
        <Row title="Claude document extraction" desc="Vision OCR for certificates, invoices and bank statements">
          <Pill tone="ok">Active</Pill>
        </Row>
        <Row title="Secure document storage" desc="Originals filed per-account in private buckets">
          <Pill tone="ok">Active</Pill>
        </Row>
        <Row title="Stripe billing" desc="Plans, trials and seat / property caps">
          <Pill tone="ok">Active</Pill>
        </Row>
        <Row title="Email delivery (Resend)" desc="Transactional + digest email sending">
          <Pill tone="ok">Active</Pill>
        </Row>
      </React.Fragment>
      : <div className="set-note" style={{ marginTop: 16 }}><span className="hdot ok" />Document AI, secure storage, billing and email delivery are managed for you — nothing to configure.</div>}
    </div>;
  }

  // ── Document filing ──────────────────────────────────────────────────
  function DocumentFiling({ s, save, isAdmin }) {
    const naming = s.fileNaming || 'date-type';
    return <div className="card-pad set-section">
      <h3>Document filing</h3>
      <p className="set-blurb">How uploaded originals are organised in secure storage. Every certificate, invoice and ID scan is filed automatically on save — isolated to this account.</p>
      <Row title="Storage location" desc="Private per-account bucket">
        <span className="set-muted pmd-mono">documents / {'{account}'} / {'{property}'} / {'{type}'} /</span>
      </Row>
      <Row title="File naming" desc="Pattern applied to each upload">
        <select className="pm-input" style={{ padding: '7px 10px', fontSize: 13 }} value={naming} disabled={!isAdmin} onChange={e => save({ fileNaming: e.target.value })}>
          <option value="date-type">date__doctype__ref.pdf</option>
          <option value="date-only">date__ref.pdf</option>
          <option value="original">keep original filename</option>
        </select>
      </Row>
      <div className="set-chips-block">
        <span className="pmd-mono set-chips-l">FILED UNDER</span>
        <div className="set-chips">
          {['Gas Safety', 'EICR', 'EPC', 'Insurance', 'Deposit', 'Right to Rent', 'HMO Licence', 'Inspections', 'Invoices', 'Tenant ID', 'Letters'].map(t => <span key={t} className="role-tag">{t}</span>)}
        </div>
      </div>
    </div>;
  }

  // tier entitlements — mirrors supabase/functions/_shared/tiers.ts so the dev
  // plan-switch writes EXACTLY what the Stripe webhook would.
  const TIER_PATCH = {
    starter: { plan: 'starter', property_cap: 3, seat_cap: 1, owner_cap: 1, ai_credits_monthly: 10,
      features: { compliance: true, documents: true, dashboard: 'basic' } },
    professional: { plan: 'professional', property_cap: 15, seat_cap: 3, owner_cap: 2, ai_credits_monthly: 100,
      features: { compliance: true, documents: true, dashboard: 'full', rent_review: true, ledger: true, expenses: true, bank: true, maintenance: true, inspect_mobile: true, exports: 'csv' } },
    portfolio: { plan: 'portfolio', property_cap: 100000, seat_cap: 100000, owner_cap: 100000, ai_credits_monthly: 100000,
      features: { compliance: true, documents: true, dashboard: 'full', rent_review: true, ledger: true, expenses: true, bank: true, maintenance: true, inspect_mobile: true, tax: true, inspection_runner: true, exports: 'csv+api' } },
  };

  // ── Account & data ───────────────────────────────────────────────────
  function Workspace({ sb, account, go, isAdmin, isSuper, toast }) {
    const [checking, setChecking] = useState(false);
    const [health, setHealth] = useState(null);
    const [switching, setSwitching] = useState('');
    const [del, setDel] = useState(false);
    const isDeployer = isSuper === true;

    const switchPlan = async (tier) => {
      if (tier === account.plan || switching) return;
      setSwitching(tier);
      const patch = TIER_PATCH[tier];
      const { error } = await sb.from('accounts').update({ ...patch, status: account.status === 'trialing' ? 'trialing' : 'active' }).eq('id', account.id);
      setSwitching('');
      if (error) { toast && toast(error.message); return; }
      try { await sb.from('activity_log').insert({ account_id: account.id, who: 'Admin', action: 'Dev: plan switched to ' + tier, entity_type: 'billing', details: { tier, dev: true } }); } catch (e) {}
      toast && toast('Switched to ' + tier.charAt(0).toUpperCase() + tier.slice(1) + ' — reloading…');
      setTimeout(() => location.reload(), 700);
    };
    const TABLES = ['properties', 'owners', 'tenants', 'tenancies', 'certificates', 'documents', 'payments', 'expenses', 'bank_rows', 'tickets', 'inspections', 'notices', 'tax_submissions'];
    const host = (() => { try { return new URL(localStorage.getItem('pm_url') || '').host; } catch { return '—'; } })();

    const runCheck = async () => {
      setChecking(true);
      try {
        const counts = await Promise.all(TABLES.map(t => sb.from(t).select('id', { count: 'exact', head: true }).then(r => ({ t, n: r.error ? null : (r.count || 0), err: r.error }))));
        const bad = counts.filter(c => c.err);
        setHealth({ ok: bad.length === 0, counts, detail: bad.length === 0 ? 'All ' + TABLES.length + ' tables reachable · row-level security scoping to this account' : bad.length + ' table(s) unreachable: ' + bad.map(b => b.t).join(', ') });
      } catch (e) { setHealth({ ok: false, counts: [], detail: e.message }); }
      setChecking(false);
    };

    const cap = (n) => n >= 100000 ? '∞' : n;
    return <div className="card-pad set-section">
      <h3>Account &amp; data</h3>
      <p className="set-blurb">Where your data lives and how to get it out. Every read and write is scoped to this account at the database — not just in the interface.</p>
      {isSuper && <Row title="Backend" desc="Live database, row-level security enforced">
        <span className="set-muted pmd-mono">{host}</span>
      </Row>}
      <Row title="Plan limits" desc="What your current plan allows">
        <span className="set-muted pmd-mono">{cap(account.property_cap)} properties · {cap(account.seat_cap)} seats · {cap(account.owner_cap)} owners</span>
      </Row>
      <Row title="Data health" desc="Verify every table is reachable for this account">
        <button className="btn btn-primary btn-sm" style={{ width: 'auto' }} disabled={checking} onClick={runCheck}>{checking ? 'Checking…' : 'Run check'}</button>
      </Row>
      {health && <React.Fragment>
        <div className={'set-note' + (health.ok ? '' : ' crit')}>
          <span className={'hdot ' + (health.ok ? 'ok' : 'crit')} />{health.detail}
        </div>
        {health.counts.length > 0 && <div className="set-chips" style={{ marginTop: 10 }}>
          {health.counts.map(c => <span key={c.t} className="role-tag">{c.t} · {c.n === null ? '✕' : c.n}</span>)}
        </div>}
      </React.Fragment>}
      <Row title="Take your data with you" desc="CSV / JSON / PDF exports — available on every plan status, even read-only">
        <button className="btn btn-ghost btn-sm" onClick={() => go && go('exports')}>Open Export centre →</button>
      </Row>

      {isAdmin && !account.is_comp && <div className="danger-zone">
        <div className="danger-head"><span className="danger-badge">DANGER ZONE</span></div>
        <div className="danger-row">
          <div><strong>Delete this account</strong><p className="set-blurb" style={{ margin: '2px 0 0' }}>Permanently erase this account and every property, tenant, document and record in it. This can't be undone — export your data first if you might need it.</p></div>
          <button className="btn btn-danger btn-sm" style={{ flex: '0 0 auto' }} onClick={() => setDel(true)}>Delete account…</button>
        </div>
      </div>}
      {del && <DeleteAccountModal sb={sb} account={account} toast={toast} onClose={() => setDel(false)} onExport={() => { setDel(false); go && go('exports'); }} />}

      {isDeployer && <div className="dev-zone">
        <div className="dev-zone-head"><span className="dev-badge">SUPER-ADMIN</span><strong>Switch plan</strong><span className="set-muted pmd-mono">testing only</span></div>
        <p className="set-blurb" style={{ margin: '0 0 10px' }}>Flip your own account between tiers to preview what each unlocks — writes the same entitlements Stripe would, then reloads. Real customers change plans through Plans &amp; billing.</p>
        <div className="dev-plans">
          {['starter', 'professional', 'portfolio'].map(t => {
            const cur = account.plan === t;
            return <button key={t} className={'dev-plan' + (cur ? ' on' : '')} disabled={cur || !!switching} onClick={() => switchPlan(t)}>
              <span className="dev-plan-n">{t.charAt(0).toUpperCase() + t.slice(1)}</span>
              <span className="dev-plan-c">{t === 'starter' ? '3 props · core' : t === 'professional' ? '15 props · money + maint' : '∞ · tax + API'}</span>
              <span className="dev-plan-s">{cur ? '● current' : switching === t ? 'switching…' : 'switch →'}</span>
            </button>;
          })}
        </div>
      </div>}
    </div>;
  }

  // ── Delete account — gated 3-step confirmation ────────────────────────
  function DeleteAccountModal({ sb, account, toast, onClose, onExport }) {
    const [step, setStep] = useState(1);
    const [ack, setAck] = useState({ irreversible: false, data: false, team: false });
    const [phrase, setPhrase] = useState('');
    const [name, setName] = useState('');
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const allAck = ack.irreversible && ack.data && ack.team;
    const nameOk = name.trim().toLowerCase() === (account.name || '').trim().toLowerCase();
    const phraseOk = phrase === 'DELETE';

    const doDelete = async () => {
      setBusy(true); setErr('');
      try {
        const { data, error } = await sb.functions.invoke('delete-account', { body: { accountId: account.id, confirm: 'DELETE' } });
        if (error) { let m = error.message; try { const c = await error.context.json(); if (c && c.error) m = c.error; } catch (e) {} throw new Error(/Failed to send|not found|404/i.test(m) ? 'The delete-account function isn\u2019t deployed yet — see the setup note.' : m); }
        if (data && data.error) throw new Error(data.error);
        // wipe complete — sign out and reload to the signed-out screen
        setStep(4);
        setTimeout(async () => { try { await sb.auth.signOut(); } catch (e) {} location.href = location.origin + location.pathname; }, 2600);
      } catch (e) { setErr(e.message); setBusy(false); }
    };

    return <div className="modal-scrim" onClick={busy ? undefined : onClose}><div className="modal" style={{ maxWidth: 500 }} onClick={e => e.stopPropagation()}>
      {step === 4 ? <div style={{ padding: '38px 26px', textAlign: 'center' }}>
        <div className="empty" style={{ padding: 0 }}><div className="ico">✓</div><h3>Account deleted</h3><p>Everything has been permanently removed. Signing you out…</p></div>
      </div> : <React.Fragment>
        <div className="modal-head"><div>
          <div className="pmd-mono" style={{ fontSize: 11, color: 'var(--red)' }}>DELETE ACCOUNT · STEP {step} OF 3</div>
          <h2>{step === 1 ? 'This is permanent' : step === 2 ? 'What will be deleted' : 'Final confirmation'}</h2>
        </div><button className="x" onClick={onClose} disabled={busy}>✕</button></div>

        <div className="modal-form" style={{ display: 'block' }}>
          {err && <div className="set-note crit"><span className="hdot crit" />{err}</div>}

          {step === 1 && <React.Fragment>
            <div className="del-warn">⚠</div>
            <p>You're about to permanently delete <strong>{account.name}</strong>. This removes the entire account for <strong>you and everyone on it</strong> — there is no undo, and PropMystro cannot recover it afterwards.</p>
            <p className="set-blurb">If you only want to stop being billed, cancel your plan in <strong>Plans &amp; billing</strong> instead — that keeps your data in read-only.</p>
            <div className="del-export"><span>Want a copy first?</span><button className="btn btn-ghost btn-sm" onClick={onExport}>Export my data →</button></div>
          </React.Fragment>}

          {step === 2 && <React.Fragment>
            <p className="set-blurb" style={{ marginTop: 0 }}>Tick each box to confirm you understand. All of the following are erased immediately:</p>
            <div className="del-list">
              <span>· Every property, tenancy, tenant &amp; owner record</span>
              <span>· All certificates, documents &amp; uploaded files</span>
              <span>· The full financial ledger, expenses, bank &amp; tax data</span>
              <span>· Maintenance tickets, inspections &amp; activity history</span>
            </div>
            <label className="del-ack"><input type="checkbox" checked={ack.irreversible} onChange={e => setAck(a => ({ ...a, irreversible: e.target.checked }))} /><span>I understand this is permanent and cannot be undone.</span></label>
            <label className="del-ack"><input type="checkbox" checked={ack.data} onChange={e => setAck(a => ({ ...a, data: e.target.checked }))} /><span>I understand all my data will be erased, and I've exported anything I need.</span></label>
            <label className="del-ack"><input type="checkbox" checked={ack.team} onChange={e => setAck(a => ({ ...a, team: e.target.checked }))} /><span>I understand everyone on this account loses access immediately.</span></label>
          </React.Fragment>}

          {step === 3 && <React.Fragment>
            <p className="set-blurb" style={{ marginTop: 0 }}>Last step. To confirm, type your account name and the word DELETE.</p>
            <div className="field"><label>Account name — <span className="pmd-mono">{account.name}</span></label>
              <input value={name} placeholder="Type the account name" onChange={e => setName(e.target.value)} /></div>
            <div className="field"><label>Type <span className="pmd-mono">DELETE</span> to confirm</label>
              <input value={phrase} placeholder="DELETE" onChange={e => setPhrase(e.target.value)} /></div>
            {!nameOk && name && <div className="pmd-mono" style={{ fontSize: 11, color: 'var(--red)' }}>Name doesn't match.</div>}
          </React.Fragment>}
        </div>

        <div className="modal-foot" style={{ justifyContent: 'space-between' }}>
          <button className="btn btn-ghost" onClick={step === 1 ? onClose : () => setStep(step - 1)} disabled={busy}>{step === 1 ? 'Cancel' : '← Back'}</button>
          {step < 3
            ? <button className="btn btn-danger" style={{ width: 'auto' }} disabled={step === 2 && !allAck} onClick={() => setStep(step + 1)}>Continue →</button>
            : <button className="btn btn-danger" style={{ width: 'auto' }} disabled={!nameOk || !phraseOk || busy} onClick={doDelete}>{busy ? 'Deleting…' : 'Permanently delete account'}</button>}
        </div>
      </React.Fragment>}
    </div></div>;
  }

  // ── Roles & permissions ──────────────────────────────────────────────
  // The access model enforced by row-level security, shown as a reference
  // matrix (areas × roles). Admins can sketch extra custom-permission
  // columns to plan a bespoke role before inviting someone into it.
  const PERM_ROLES = [
    { key: 'family_admin', label: 'Admin', color: '#1a1815', blurb: 'Cross-portfolio. Invites users, changes settings.' },
    { key: 'family_owner', label: 'Co-owner', color: '#385a8a', blurb: 'Full access to their own + joint properties.' },
    { key: 'accountant', label: 'Accountant', color: '#c98a1f', blurb: 'Read-only across all; full on expenses & tax prep.' },
    { key: 'inspector', label: 'Inspector', color: '#2f6f6a', blurb: 'Runs inspections, raises issues. No finance.' },
    { key: 'contractor', label: 'Contractor', color: '#5a6b3a', blurb: 'Only the tickets assigned to them.' },
    { key: 'tenant', label: 'Tenant', color: '#8a5a7a', blurb: 'Their own tenancy: ledger, tickets, documents.' },
  ];
  const PERM_ROWS = [
    { area: 'Properties',              family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'read', contractor: 'none', tenant: 'none' },
    { area: 'Compliance certificates', family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'read', contractor: 'none', tenant: 'read' },
    { area: 'Tenants & tenancies',     family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'read', contractor: 'none', tenant: 'own'  },
    { area: 'Financial ledger',        family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'none', contractor: 'none', tenant: 'own'  },
    { area: 'Expenses',                family_admin: 'full', family_owner: 'own',  accountant: 'full', inspector: 'none', contractor: 'none', tenant: 'none' },
    { area: 'Maintenance tickets',     family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'own',  contractor: 'own',  tenant: 'own'  },
    { area: 'Inspections',             family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'full', contractor: 'none', tenant: 'none' },
    { area: 'Tax submissions',         family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'none', contractor: 'none', tenant: 'none' },
    { area: 'Documents library',       family_admin: 'full', family_owner: 'own',  accountant: 'read', inspector: 'read', contractor: 'none', tenant: 'own'  },
    { area: 'User management',         family_admin: 'full', family_owner: 'none', accountant: 'none', inspector: 'none', contractor: 'none', tenant: 'none' },
    { area: 'Global settings',         family_admin: 'full', family_owner: 'none', accountant: 'none', inspector: 'none', contractor: 'none', tenant: 'none' },
  ];
  const PERM_CYCLE = ['none', 'read', 'own', 'full'];
  const cellLabel = (v) => ({ full: 'Full', read: 'Read', own: 'Own', none: '—' }[v] || v);
  const ROLE_LABEL = { family_admin: 'Admin', family_owner: 'Co-owner', accountant: 'Accountant', inspector: 'Inspector', contractor: 'Contractor', tenant: 'Tenant' };
  const INVITE_ROLES = [['family_owner', 'Co-owner'], ['accountant', 'Accountant'], ['inspector', 'Inspector'], ['contractor', 'Contractor']];

  // ── Team & roles (T4 + editable permission matrix S2) ──────────────────
  function TeamRoles({ sb, account, data, save, isAdmin, myId, toast }) {
    // ---- team members + invite ----
    const [members, setMembers] = useState(null);
    const [email, setEmail] = useState('');
    const [role, setRole] = useState('accountant');
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const [invite, setInvite] = useState(null);
    const loadMembers = useCallback(() => {
      sb.from('memberships').select('*').order('created_at', { ascending: true }).then(r => setMembers(r.data || []));
    }, [sb]);
    useEffect(() => { loadMembers(); }, [loadMembers]);
    const sendInvite = async () => {
      setErr(''); setInvite(null);
      if (!email.trim()) { setErr('Enter an email to invite.'); return; }
      setBusy(true);
      const { data: tok, error } = await sb.rpc('invite_member', { acct: account.id, member_email: email.trim(), member_role: role });
      setBusy(false);
      if (error) { setErr(/seat_limit/.test(error.message) ? 'You\u2019ve reached your plan\u2019s team-seat limit.' : error.message); return; }
      setInvite(location.origin + location.pathname + '?invite=' + tok); setEmail(''); toast && toast('Invitation created'); loadMembers();
    };
    const seatCap = account.seat_cap >= 100000 ? '∞' : account.seat_cap;

    // ---- editable permission matrix (persisted overrides; PERM_ROWS = default) ----
    const overrides = (data && data.rolePermissions) || {};       // { roleKey: { area: level } }
    const customRoles = (data && data.customRoles) || [];          // [{ id, label, perms:{area:level} }]
    const cellOf = (roleKey, area) => (overrides[roleKey] && overrides[roleKey][area] != null)
      ? overrides[roleKey][area]
      : (PERM_ROWS.find(r => r.area === area) || {})[roleKey] || 'none';
    const [editMode, setEditMode] = useState(false);

    const cycleBuiltin = (roleKey, area) => {
      if (!isAdmin) return;
      const cur = cellOf(roleKey, area);
      const next = PERM_CYCLE[(PERM_CYCLE.indexOf(cur) + 1) % PERM_CYCLE.length];
      const ro = { ...(overrides[roleKey] || {}), [area]: next };
      save({ rolePermissions: { ...overrides, [roleKey]: ro } });
    };
    const resetMatrix = () => save({ rolePermissions: {} });
    const isOverridden = (roleKey, area) => overrides[roleKey] && overrides[roleKey][area] != null;

    const addCustom = () => {
      const blank = {}; PERM_ROWS.forEach(r => { blank[r.area] = 'none'; });
      save({ customRoles: [...customRoles, { id: 'c' + Date.now(), label: 'Custom ' + (customRoles.length + 1), perms: blank }] });
    };
    const removeCustom = (id) => save({ customRoles: customRoles.filter(c => c.id !== id) });
    const renameCustom = (id, label) => save({ customRoles: customRoles.map(c => c.id === id ? { ...c, label } : c) });
    const cycleCustom = (id, area) => save({ customRoles: customRoles.map(c => {
      if (c.id !== id) return c;
      const cur = c.perms[area] || 'none';
      return { ...c, perms: { ...c.perms, [area]: PERM_CYCLE[(PERM_CYCLE.indexOf(cur) + 1) % PERM_CYCLE.length] } };
    }) });

    const dirty = Object.keys(overrides).length > 0 || customRoles.length > 0;

    return <div className="card-pad set-section">
      <h3>Team &amp; roles</h3>
      <p className="set-blurb">Who has access to this account, and exactly what each role can do. Access levels are enforced at the database by row-level security — not just hidden in the interface.</p>

      {/* ---- members ---- */}
      <div className="int-group-l pmd-mono">PEOPLE · {members ? members.length : '–'} OF {seatCap} SEAT{seatCap === 1 ? '' : 'S'}</div>
      <div className="tr-members">
        {members === null ? <Spin />
          : members.map(m => <div className="tr-member" key={m.id}>
            <span className="avatar" style={{ background: 'var(--surface-2)', color: 'var(--ink-soft)' }}>{(m.user_id === myId ? 'You' : (m.owner_key || 'M')).slice(0, 2).toUpperCase()}</span>
            <div className="tr-member-l">
              <strong>{m.user_id === myId ? 'You' : (m.owner_key || 'Team member')}{m.status !== 'active' ? ' · ' + m.status : ''}</strong>
              <span className="set-muted pmd-mono">{m.user_id === myId ? 'this is you' : 'member'}</span>
            </div>
            <span className="role-tag">{ROLE_LABEL[m.role] || m.role}</span>
          </div>)}
      </div>
      {isAdmin ? <div className="tr-invite">
        {err && <div className="set-note crit"><span className="hdot crit" />{err}</div>}
        {invite && <div className="set-note"><span className="hdot ok" />Invitation link (emailed automatically in production): <code style={{ fontSize: 11, wordBreak: 'break-all', marginLeft: 4 }}>{invite}</code></div>}
        <div className="tr-invite-row">
          <label className="comp-field"><span className="pmd-mono">INVITE BY EMAIL</span><input type="email" value={email} placeholder="accountant@firm.com" onChange={e => setEmail(e.target.value)} /></label>
          <label className="comp-field comp-field-tier"><span className="pmd-mono">ROLE</span><select value={role} onChange={e => setRole(e.target.value)}>{INVITE_ROLES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}</select></label>
          <button className="btn btn-primary" style={{ width: 'auto', alignSelf: 'flex-end' }} disabled={busy} onClick={sendInvite}>{busy ? 'Sending…' : 'Send invite'}</button>
        </div>
      </div> : <div className="set-muted" style={{ fontSize: 13, marginTop: 8 }}>Only an account admin can invite teammates.</div>}

      {/* ---- editable matrix ---- */}
      <div className="tr-matrix-head">
        <div className="int-group-l pmd-mono" style={{ margin: 0 }}>ROLE PERMISSIONS</div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          {dirty && isAdmin && <button className="linkbtn" onClick={resetMatrix}>Reset to defaults</button>}
          {isAdmin && <button className={'btn btn-sm ' + (editMode ? 'btn-primary' : 'btn-ghost')} style={{ width: 'auto' }} onClick={() => setEditMode(v => !v)}>{editMode ? '✓ Done editing' : 'Edit permissions'}</button>}
        </div>
      </div>
      <p className="set-blurb" style={{ margin: '0 0 10px' }}>{isAdmin ? (editMode ? 'Click any cell to cycle None → Read → Own → Full. Changes save instantly. Edited cells are underlined; built-in defaults stay as the starting point.' : 'The current access model. Turn on Edit to tailor any role, or add a bespoke custom role.') : 'The access model for this account (view only — an admin can edit).'}</p>

      <div className="perm-scroll">
        <table className="perm-matrix">
          <thead>
            <tr>
              <th className="perm-area-h">Access area</th>
              {PERM_ROLES.map(r => <th key={r.key}><span className="perm-role-pill" style={{ background: r.color + '1c', borderColor: r.color, color: r.color }}>{r.label}</span></th>)}
              {customRoles.map(c => <th key={c.id}><div className="perm-custom-h">
                {editMode ? <input value={c.label} onChange={e => renameCustom(c.id, e.target.value)} /> : <span className="perm-role-pill" style={{ background: 'var(--surface-2)', borderColor: 'var(--line2)', color: 'var(--ink-soft)' }}>{c.label}</span>}
                {editMode && <button onClick={() => removeCustom(c.id)} title="Remove">×</button>}
              </div></th>)}
            </tr>
          </thead>
          <tbody>
            {PERM_ROWS.map(row => <tr key={row.area}>
              <td className="perm-area">{row.area}</td>
              {PERM_ROLES.map(r => { const v = cellOf(r.key, row.area); const canEdit = editMode && isAdmin && r.key !== 'family_admin';
                return <td key={r.key} className={'perm-cell perm-' + v + (canEdit ? ' perm-toggle' : '') + (isOverridden(r.key, row.area) ? ' perm-edited' : '')} onClick={canEdit ? () => cycleBuiltin(r.key, row.area) : undefined} title={canEdit ? 'Click to cycle' : (r.key === 'family_admin' ? 'Admin always has full access' : '')}>{cellLabel(v)}</td>; })}
              {customRoles.map(c => { const v = c.perms[row.area] || 'none'; const canEdit = editMode && isAdmin;
                return <td key={c.id} className={'perm-cell perm-' + v + (canEdit ? ' perm-toggle' : '')} onClick={canEdit ? () => cycleCustom(c.id, row.area) : undefined}>{cellLabel(v)}</td>; })}
            </tr>)}
          </tbody>
        </table>
      </div>

      <div className="perm-legend">
        <span><b className="perm-key perm-full">Full</b> read &amp; write</span>
        <span><b className="perm-key perm-read">Read</b> read only</span>
        <span><b className="perm-key perm-own">Own</b> only their own records</span>
        <span><b className="perm-key perm-none">—</b> no access</span>
        {editMode && isAdmin && <button className="linkbtn" onClick={addCustom}>+ Add custom role</button>}
      </div>

      <div className="perm-roles">
        {PERM_ROLES.map(r => <div key={r.key} className="perm-role-card">
          <span className="perm-dot" style={{ background: r.color }} />
          <div><strong>{r.label}</strong><span>{r.blurb}</span></div>
        </div>)}
      </div>
    </div>;
  }

  // ── Friends & comps (super-admin only) ─────────────────────────────────
  // Grant / change / revoke complimentary access to OTHER landlords' accounts
  // via the service-role grant-comp Edge Function (itself allowlist-gated).
  function FriendsComps({ sb, toast, myEmail }) {
    const [comps, setComps] = useState(null);
    const [email, setEmail] = useState('');
    const [tier, setTier] = useState('portfolio');
    const [busy, setBusy] = useState(false);
    const [lookup, setLookup] = useState(null);
    const [err, setErr] = useState('');

    const call = async (body) => {
      const { data, error } = await sb.functions.invoke('grant-comp', { body });
      if (error) {
        let msg = error.message || 'Request failed';
        try { const ctx = await error.context.json(); if (ctx && ctx.error) msg = ctx.error; } catch (e) {}
        if (/Failed to send a request|not found|404/i.test(msg)) msg = 'The grant-comp function isn’t deployed yet — see comp-access-setup-guide.md.';
        throw new Error(msg);
      }
      if (data && data.error) throw new Error(data.error);
      return data;
    };

    const load = useCallback(async () => {
      setErr('');
      try { const d = await call({ action: 'list' }); setComps(d.comps || []); }
      catch (e) { setComps([]); setErr(e.message); }
    }, [sb]);
    useEffect(() => { load(); }, [load]);

    const grant = async () => {
      if (!email.trim() || busy) return;
      setBusy(true); setErr('');
      try {
        await call({ action: 'grant', email: email.trim(), tier });
        toast && toast('Granted ' + tier + ' to ' + email.trim());
        setEmail(''); setLookup(null); load();
      } catch (e) { setErr(e.message); }
      setBusy(false);
    };
    const changeTier = async (c, newTier) => {
      if (newTier === c.plan) return;
      setBusy(true); setErr('');
      try { await call({ action: 'grant', email: c.email, tier: newTier }); toast && toast(c.email + ' → ' + newTier); load(); }
      catch (e) { setErr(e.message); }
      setBusy(false);
    };
    const revoke = async (c) => {
      if (!confirm('Revoke complimentary access for ' + (c.email || c.name) + '? Their data is kept; the account drops to a 14-day starter trial.')) return;
      setBusy(true); setErr('');
      try { await call({ action: 'revoke', accountId: c.id }); toast && toast('Revoked — ' + (c.email || c.name) + ' moved to starter trial'); load(); }
      catch (e) { setErr(e.message); }
      setBusy(false);
    };
    const checkEmail = async () => {
      if (!email.trim()) return;
      setErr('');
      try { const d = await call({ action: 'list', email: email.trim() }); setLookup(d.lookup); }
      catch (e) { setErr(e.message); }
    };

    const fmt = (d) => d ? new Date(d).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }) : '—';

    return <div className="card-pad set-section">
      <div className="comp-super-tag"><span className="dev-badge">SUPER-ADMIN</span><span className="set-muted pmd-mono">visible only to {SUPER_ADMINS.length} allowlisted accounts</span></div>
      <h3>Friends &amp; comps</h3>
      <p className="set-blurb">Give family &amp; friends who are landlords free access to PropMystro — each gets their own private portfolio, billed to no one. They sign up normally first; then comp them here by the email they used. Comped accounts never expire and never see a payment prompt.</p>

      <div className="comp-grant">
        <div className="comp-grant-row">
          <label className="comp-field"><span className="pmd-mono">THEIR SIGN-UP EMAIL</span>
            <input type="email" value={email} placeholder="friend@email.com" onChange={e => { setEmail(e.target.value); setLookup(null); }} onBlur={checkEmail} /></label>
          <label className="comp-field comp-field-tier"><span className="pmd-mono">TIER</span>
            <select value={tier} onChange={e => setTier(e.target.value)}>{COMP_TIERS.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}</select></label>
          <button className="btn btn-primary" style={{ width: 'auto', alignSelf: 'flex-end' }} disabled={!email.trim() || busy} onClick={grant}>{busy ? 'Working…' : 'Grant free access'}</button>
        </div>
        {lookup && (lookup.found
          ? <div className="comp-look ok"><span className="hdot ok" />Found <b>{lookup.email}</b> — {lookup.account.is_comp ? 'already comped (' + lookup.account.plan + '). Granting again updates the tier.' : 'currently ' + lookup.account.plan + ' / ' + lookup.account.status + '. Ready to comp.'}</div>
          : <div className="comp-look warn"><span className="hdot warn" />No account for <b>{lookup.email}</b> yet — ask them to sign up first, then comp them.</div>)}
        {COMP_TIERS.find(t => t.id === tier) && <div className="set-muted pmd-mono" style={{ marginTop: 6 }}>{COMP_TIERS.find(t => t.id === tier).blurb}</div>}
      </div>

      {err && <div className="set-note crit"><span className="hdot crit" />{err}</div>}

      <div className="comp-list-head">
        <span className="pmd-mono">COMPED ACCOUNTS</span>
        <button className="linkbtn" onClick={load}>↻ Refresh</button>
      </div>
      {comps === null ? <Spin />
        : comps.length === 0 ? <div className="comp-empty">No comped accounts yet. Grant your first one above.</div>
        : <div className="comp-list">
          {comps.map(c => <div className="comp-row" key={c.id}>
            <div className="comp-row-l">
              <strong>{c.email || c.name || 'Account'}</strong>
              <span className="set-muted pmd-mono">{c.name} · since {fmt(c.comped_at)}{c.comped_by ? ' · by ' + c.comped_by : ''}</span>
            </div>
            <div className="comp-row-r">
              <select className="pm-input comp-tier-sel" value={c.plan} disabled={busy} onChange={e => changeTier(c, e.target.value)}>{COMP_TIERS.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}</select>
              <button className="btn btn-ghost btn-sm" disabled={busy} onClick={() => revoke(c)}>Revoke</button>
            </div>
          </div>)}
        </div>}
    </div>;
  }

  function SettingsStyles() {
    return <style>{`
      .set-layout { display: grid; grid-template-columns: 196px 1fr; gap: 18px; align-items: start; }
      .set-rail { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 76px; }
      .set-tab { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border: 0; background: transparent; border-radius: 8px; text-align: left; font-size: 13.5px; font-weight: 500; color: var(--ink-soft); cursor: pointer; }
      .set-tab:hover { background: var(--surface-2); color: var(--ink); }
      .set-tab.on { background: var(--ink); color: #fff; font-weight: 600; }
      .set-tab-ico { width: 18px; text-align: center; opacity: .75; }
      .set-section h3 { font-size: 17px; margin-bottom: 4px; }
      .set-blurb { color: var(--ink-faint); font-size: 13.5px; margin: 0 0 14px; max-width: 560px; line-height: 1.55; }
      .set-row { display: flex; justify-content: space-between; align-items: center; gap: 18px; padding: 14px 0; border-top: 1px solid var(--line-soft); flex-wrap: wrap; }
      .set-row-l { display: flex; flex-direction: column; gap: 2px; min-width: 200px; flex: 1; }
      .set-row-l strong { font-size: 14px; }
      .set-row-d { font-size: 12.5px; color: var(--ink-faint); }
      .set-row-c { display: flex; align-items: center; gap: 10px; flex-shrink: 0; flex-wrap: wrap; }
      .set-seg { display: inline-flex; background: var(--surface-2); border: 1px solid var(--line); border-radius: 9px; padding: 3px; gap: 2px; }
      .set-seg-btn { background: transparent; border: 0; padding: 6px 13px; border-radius: 6px; font-size: 13px; font-weight: 600; color: var(--ink-faint); cursor: pointer; }
      .set-seg-btn.on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
      .set-seg-btn:disabled { cursor: default; opacity: .6; }
      .set-checks { display: flex; flex-direction: column; gap: 6px; }
      .set-check { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
      .set-check input { width: 15px; height: 15px; accent-color: var(--ink); }
      .set-stepper { display: inline-flex; align-items: center; gap: 8px; }
      .set-stepper-v { min-width: 76px; text-align: center; font-size: 13px; }
      .set-muted { font-size: 12.5px; color: var(--ink-faint); }
      .set-note { display: flex; align-items: center; gap: 9px; margin-top: 14px; background: var(--ok-soft); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 13px; }
      .set-note.crit { background: var(--red-soft); }
      .set-chips-block { margin-top: 16px; }
      .set-chips-l { font-size: 10px; letter-spacing: .07em; color: var(--ink-faint); }
      .set-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
      .perm-scroll { overflow-x: auto; margin-top: 16px; border: 1px solid var(--line); border-radius: var(--radius-sm); -webkit-overflow-scrolling: touch; }
      .perm-matrix { border-collapse: collapse; width: 100%; min-width: 640px; font-size: 12.5px; }
      .perm-matrix th, .perm-matrix td { padding: 9px 10px; text-align: center; border-bottom: 1px solid var(--line-soft); border-right: 1px solid var(--line-soft); white-space: nowrap; }
      .perm-matrix th:last-child, .perm-matrix td:last-child { border-right: 0; }
      .perm-matrix thead th { background: var(--surface-2); position: sticky; top: 0; }
      .perm-area-h, .perm-area { text-align: left !important; font-weight: 600; position: sticky; left: 0; background: var(--surface); z-index: 1; }
      .perm-matrix thead .perm-area-h { background: var(--surface-2); }
      .perm-role-pill { display: inline-block; padding: 3px 9px; border-radius: 999px; border: 1px solid; font-size: 11.5px; font-weight: 700; }
      .perm-custom-h { display: inline-flex; align-items: center; gap: 4px; }
      .perm-custom-h input { width: 78px; font-size: 11.5px; padding: 3px 6px; border: 1px solid var(--line2); border-radius: 6px; font-weight: 600; }
      .perm-custom-h button { border: 0; background: var(--surface-2); border-radius: 5px; width: 20px; height: 20px; cursor: pointer; color: var(--ink-faint); font-size: 14px; line-height: 1; }
      .perm-cell { font-weight: 600; }
      .perm-full { background: rgba(31,138,91,.14); color: #1f6b48; }
      .perm-read { background: rgba(42,111,219,.12); color: #2a5fb0; }
      .perm-own  { background: rgba(201,138,31,.15); color: #8a5d12; }
      .perm-none { color: var(--ink-faint); }
      .perm-toggle { cursor: pointer; }
      .perm-toggle:hover { outline: 2px solid var(--ink); outline-offset: -2px; }
      .perm-legend { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 12px; font-size: 12.5px; color: var(--ink-soft); align-items: center; }
      .perm-key { display: inline-block; padding: 1px 7px; border-radius: 5px; margin-right: 5px; }
      .perm-roles { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 10px; margin-top: 18px; }
      .perm-role-card { display: flex; gap: 9px; align-items: flex-start; padding: 11px 13px; background: var(--surface-2); border-radius: var(--radius-sm); }
      .perm-role-card .perm-dot { width: 10px; height: 10px; border-radius: 50%; margin-top: 4px; flex: 0 0 auto; }
      .perm-role-card strong { display: block; font-size: 13px; }
      .perm-role-card span { font-size: 12px; color: var(--ink-faint); line-height: 1.45; }
      .dev-zone { margin-top: 20px; border: 1px dashed var(--line2); border-radius: var(--radius-sm); padding: 16px 18px; background: repeating-linear-gradient(45deg, transparent, transparent 9px, rgba(0,0,0,.012) 9px, rgba(0,0,0,.012) 18px); }
      .dev-zone-head { display: flex; align-items: center; gap: 9px; margin-bottom: 6px; }
      .dev-badge { font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700; letter-spacing: .08em; background: var(--ink); color: #fff; padding: 2px 7px; border-radius: 5px; }
      .dev-plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
      .dev-plan { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; text-align: left; padding: 12px 13px; border: 1.5px solid var(--line); border-radius: 10px; background: var(--surface); cursor: pointer; transition: border-color .12s, transform .1s; }
      .dev-plan:hover:not(:disabled) { border-color: var(--ink-faint); transform: translateY(-1px); }
      .dev-plan.on { border-color: var(--brand-deep); background: var(--brand-soft); cursor: default; }
      .dev-plan:disabled:not(.on) { opacity: .6; cursor: default; }
      .dev-plan-n { font-size: 14px; font-weight: 700; }
      .dev-plan-c { font-size: 11px; color: var(--ink-faint); }
      .dev-plan-s { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--brand-deep); margin-top: 4px; }
      .perm-edited { box-shadow: inset 0 -2px 0 var(--ink); }
      .int-group-l { font-size: 10px; letter-spacing: .08em; color: var(--ink-faint); margin: 4px 0 8px; display: flex; align-items: center; gap: 7px; }
      .tr-members { display: flex; flex-direction: column; gap: 7px; margin-bottom: 14px; }
      .tr-member { display: flex; align-items: center; gap: 11px; padding: 9px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
      .tr-member-l { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
      .tr-member-l strong { font-size: 13.5px; }
      .tr-member-l .pmd-mono { font-size: 10.5px; }
      .tr-invite { background: var(--surface-2); border-radius: var(--radius-sm); padding: 14px; margin-bottom: 8px; }
      .tr-invite-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
      .tr-matrix-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 20px; flex-wrap: wrap; }
      .danger-zone { margin-top: 22px; border: 1px solid var(--red); border-radius: var(--radius); overflow: hidden; }
      .danger-head { background: var(--red-soft); padding: 7px 14px; }
      .danger-badge { font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .08em; font-weight: 700; color: var(--red); }
      .danger-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 14px 16px; flex-wrap: wrap; }
      .danger-row strong { font-size: 14px; }
      .btn-danger { background: var(--red); color: #fff; border: 0; }
      .btn-danger:hover:not(:disabled) { filter: brightness(.93); }
      .btn-danger:disabled { background: var(--ink-faint); cursor: default; opacity: .6; }
      .del-warn { font-size: 34px; text-align: center; margin: 4px 0 10px; }
      .del-export { display: flex; align-items: center; justify-content: space-between; gap: 12px; background: var(--surface-2); border-radius: var(--radius-sm); padding: 11px 14px; margin-top: 12px; font-size: 13px; }
      .del-list { display: flex; flex-direction: column; gap: 5px; background: var(--red-soft); border-radius: var(--radius-sm); padding: 12px 14px; font-size: 13px; color: var(--ink); margin-bottom: 14px; }
      .del-ack { display: flex; gap: 9px; align-items: flex-start; padding: 8px 0; font-size: 13.5px; cursor: pointer; line-height: 1.4; }
      .del-ack input { width: 16px; height: 16px; margin-top: 1px; accent-color: var(--red); flex: 0 0 auto; }
      .dev-plan.on .dev-plan-s { color: var(--brand-deep); font-weight: 700; }
      .comp-super-tag { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
      .comp-grant { background: var(--surface-2); border-radius: var(--radius-sm); padding: 16px; margin: 6px 0 16px; }
      .comp-grant-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
      .comp-field { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 180px; }
      .comp-field .pmd-mono { font-size: 9.5px; color: var(--ink-faint); letter-spacing: .06em; }
      .comp-field input, .comp-field select { width: 100%; }
      .comp-field-tier { flex: 0 0 160px; min-width: 140px; }
      .comp-look { display: flex; align-items: center; gap: 9px; margin-top: 11px; font-size: 12.5px; padding: 8px 12px; border-radius: var(--radius-sm); background: var(--surface); }
      .comp-look.warn { color: #8a5418; }
      .comp-list-head { display: flex; justify-content: space-between; align-items: center; margin: 4px 0 8px; }
      .comp-list-head .pmd-mono { font-size: 10px; letter-spacing: .07em; color: var(--ink-faint); }
      .comp-empty { font-size: 13px; color: var(--ink-faint); padding: 18px; text-align: center; background: var(--surface-2); border-radius: var(--radius-sm); }
      .comp-list { display: flex; flex-direction: column; gap: 8px; }
      .comp-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-sm); flex-wrap: wrap; }
      .comp-row-l { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
      .comp-row-l strong { font-size: 13.5px; }
      .comp-row-l .pmd-mono { font-size: 11px; }
      .comp-row-r { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
      .comp-tier-sel { padding: 6px 9px !important; font-size: 12.5px !important; }
      @media (max-width: 540px) { .comp-grant-row { flex-direction: column; align-items: stretch; } .comp-field-tier { flex: 1; } .comp-grant-row .btn { align-self: stretch !important; } .comp-row-r { width: 100%; } .comp-tier-sel { flex: 1; } }
      @media (max-width: 540px) { .dev-plans { grid-template-columns: 1fr; } }
      .set-pane { min-width: 0; }
      @media (max-width: 860px) {
        .set-layout { grid-template-columns: 1fr; }
        .set-rail { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; position: static; gap: 4px; padding-bottom: 6px; -webkit-overflow-scrolling: touch; }
        .set-tab { flex: 0 0 auto; white-space: nowrap; }
        .perm-roles { grid-template-columns: 1fr; }
        .perm-legend { gap: 10px 14px; }
      }
    `}</style>;
  }

  window.PMSettingsHub = SettingsHub;
})();
