/* public/js/admin.js — compact, single-purpose superadmin panel
 *
 * Layout:
 *   1. 4 KPI cards (down from 8 — no verbose sub-text)
 *   2. Two panels side-by-side: Cases + Takedowns (resolution)
 *   3. Alerts — category pills on one line, click "show details" to expand
 *   4. Trusted devices — one line per device
 *   5. API keys — one line per key
 *   6. Users — one line per user, click for detail modal
 *   7. Audit log — collapsed by default, lazy-load on expand
 *
 * Removed from v1.5.x: production-pending panel (dead code), test-data
 * banner (data is now purged in prod), plan-mix + AI-cost panels
 * (verbose, moved to detail views).
 */
(function () {
  'use strict';
  function csrf() { const m = document.cookie.match(/(?:^|; )ncsrf=([^;]+)/); return m ? m[1] : ''; }
  async function api(p, o = {}) {
    const r = await fetch(p, {
      ...o,
      headers: { 'content-type': 'application/json', 'x-csrf-token': csrf(), ...(o.headers || {}) },
    });
    if (r.status === 401) {
      const body = await r.json().catch(() => ({}));
      if (body.error === 'UNAUTHENTICATED' || !body.error) {
        location.href = '/login?next=' + encodeURIComponent(location.pathname);
        return null;
      }
      if (body.error === 'TWO_FA_REQUIRED') {
        window.dispatchEvent(new CustomEvent('ncii:2fa_required', { detail: body }));
        return { error: 'TWO_FA_REQUIRED', message: body.message || '2FA required' };
      }
      return body;
    }
    if (!r.ok) { const t = await r.text().catch(() => ''); return { error: 'ERROR', message: t }; }
    return r.json();
  }
  function esc(s) { return String(s || '').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
  function flash(t, m) { document.getElementById('flash').innerHTML = `<div class="flash ${t}">${esc(m)}</div>`; setTimeout(() => document.getElementById('flash').innerHTML = '', 4000); }
  function bar(p) { return `<div class="bar ${p >= 90 ? 'danger' : p >= 70 ? 'warn' : ''}"><span style="width:${Math.min(100, Math.round(p))}%"></span></div>`; }
  function fmt(ms) { return ms ? new Date(ms).toLocaleDateString() : '—'; }
  function fmtDT(ms) { return ms ? new Date(ms).toLocaleString() : '—'; }
  function plural(n, w) { return n + ' ' + w + (n === 1 ? '' : 's'); }

  // ============================================================
  // KPI: 4 cards. No verbose sub-text. Single number, single label.
  // ============================================================
  async function loadKpis() {
    const a = await api('/admin/analytics');
    if (!a || a.error) return;
    const takedownTotal = (a.takedowns.by_status || []).reduce((s, x) => s + x.n, 0);
    const kpis = [
      { title: 'Users',    value: a.users.total,                    href: '#users' },
      { title: 'Open cases', value: (a.cases.by_status.find(s => s.status === 'open') || { n: 0 }).n, href: '#casesPanel' },
      { title: 'Takedowns',  value: takedownTotal,                  href: '#resoPanel' },
      { title: 'Resolution', value: a.takedowns.resolution_rate + '%', sub: `${a.takedowns.resolved}/${a.takedowns.total_sent} responded` },
    ];
    document.getElementById('kpis').innerHTML = kpis.map(k => `
      <a class="card" href="${k.href || '#'}" style="text-decoration:none;color:inherit">
        <h3>${k.title}</h3>
        <div class="v">${k.value}</div>
        ${k.sub ? `<div class="sub">${esc(k.sub)}</div>` : ''}
      </a>`).join('');
    // Topbar one-line summary
    document.getElementById('topbarSub').textContent =
      `${plural(a.users.total, 'user')} · ${plural(takedownTotal, 'takedown')} · ${a.takedowns.resolution_rate}% resolved · $${a.users.mrr_estimate.toFixed(0)}/mo`;
  }

  // ============================================================
  // Cases by status — one line per status, compact bars
  // ============================================================
  function renderBars(rows) {
    if (!rows.length) return '<div class="muted">no data</div>';
    const max = Math.max(1, ...rows.map(r => r[1]));
    return rows.map(([k, v]) => `<div class="kvrow"><span>${esc(k)}</span>${bar(v / max * 100)}<span class="mono right">${v}</span></div>`).join('');
  }
  async function loadCases() {
    const a = await api('/admin/analytics');
    if (!a) return;
    const el = document.getElementById('casesByStatus');
    if (el) el.innerHTML = renderBars(a.cases.by_status.map(s => [s.status, s.n]));
  }

  // ============================================================
  // Takedown resolution — compact: % + top 3 domains inline
  // ============================================================
  async function loadTakedownReso() {
    const a = await api('/admin/analytics');
    if (!a) return;
    const el = document.getElementById('takedownReso');
    const summary = document.getElementById('resoSummary');
    if (!el) return;
    if (summary) summary.textContent = `(${a.takedowns.resolution_rate}%)`;
    const top = (a.takedowns.top_domains || []).slice(0, 3);
    el.innerHTML = `
      <div class="v" style="font-size:2rem;line-height:1">${a.takedowns.resolution_rate}%</div>
      <div class="muted" style="font-size:.8rem;margin-bottom:.5rem">${a.takedowns.resolved} of ${a.takedowns.total_sent} responded</div>
      ${top.length ? `<div class="muted small-text">${top.map(d => `${esc(d.domain)} (${d.n})`).join(' · ')}</div>` : ''}`;
  }

  // ============================================================
  // API keys — compact: single line per key, no verbose columns
  // ============================================================
  async function loadKeys() {
    const d = await api('/admin/api-keys');
    if (!d) return;
    const el = document.getElementById('keyRows');
    if (!el) return;
    if (!d.keys.length) { el.innerHTML = '<div class="muted">No keys configured.</div>'; return; }
    el.innerHTML = d.keys.map(k => {
      const pct = k.quota_limit ? Math.round((k.calls_today / k.quota_limit) * 100) : 0;
      const status = !k.is_active ? 'disabled' : (k.exhausted_until && k.exhausted_until > Date.now()) ? 'exhausted' : 'active';
      const cls = status === 'exhausted' ? 'warn' : status === 'disabled' ? 'danger' : 'ok';
      return `<div class="kvrow">
        <span><b>${esc(k.service)}</b> · ${esc(k.label)} <span class="muted mono small-text">${esc(k.masked)}</span></span>
        <span>${bar(pct)}</span>
        <span class="mono right" style="min-width:120px">${k.calls_today}/${k.quota_limit || '∞'} <span class="pill ${cls}">${status}</span></span>
        <span><button class="secondary" data-act="edit" data-id="${k.id}">Edit</button> <button class="secondary" data-act="toggle" data-id="${k.id}">${k.is_active ? 'Off' : 'On'}</button> <button class="secondary danger" data-act="del" data-id="${k.id}">×</button></span>
      </div>`;
    }).join('');
    el.querySelectorAll('[data-act]').forEach(b => b.onclick = () => actKey(b.dataset.act, b.dataset.id, d));
  }
  async function actKey(act, id, data) {
    if (act === 'edit')  return openKeyModal(data.keys.find(k => k.id == id));
    if (act === 'toggle'){ await api(`/admin/api-keys/${id}/toggle`, { method: 'POST' }); return loadKeys(); }
    if (act === 'del')   { if (!confirm('Delete this key permanently?')) return; await api(`/admin/api-keys/${id}`, { method: 'DELETE' }); return loadKeys(); }
  }
  function openKeyModal(k) {
    const f = document.getElementById('keyForm');
    f.reset();
    if (k) {
      document.getElementById('keyModalTitle').textContent = `Edit ${k.service} key`;
      f.id.value = k.id; f.service.value = k.service; f.label.value = k.label; f.quota_limit.value = k.quota_limit;
      f.key_value.placeholder = 'leave blank to keep current';
    } else {
      document.getElementById('keyModalTitle').textContent = 'Add API key';
    }
    document.getElementById('keyModal').classList.remove('hidden');
  }
  document.getElementById('newKey').onclick = () => openKeyModal(null);
  document.getElementById('keyCancel').onclick = () => document.getElementById('keyModal').classList.add('hidden');
  document.getElementById('keyForm').onsubmit = async (e) => {
    e.preventDefault();
    const f = e.target;
    const body = Object.fromEntries(new FormData(f));
    const id = body.id; delete body.id;
    if (!body.key_value) delete body.key_value;
    let r;
    if (id) r = await api(`/admin/api-keys/${id}`, { method: 'PATCH', body: JSON.stringify(body) });
    else    r = await api('/admin/api-keys', { method: 'POST',  body: JSON.stringify(body) });
    if (r && r.ok) { flash('ok', 'Saved.'); document.getElementById('keyModal').classList.add('hidden'); loadKeys(); }
    else flash('err', (r && (r.message || r.error)) || 'Failed');
  };

  // ============================================================
  // Users — one line per user. Click for detail modal.
  // ============================================================
  async function loadUsers(opts = {}) {
    const q = new URLSearchParams();
    const search = document.getElementById('userSearch').value;
    if (search) q.set('search', search);
    if (opts.include_test) q.set('include_test', '1');
    q.set('limit', '20');
    const d = await api('/admin/users?' + q);
    if (!d) return;
    const el = document.getElementById('userRows');
    if (!el) return;
    if (!d.users.length) { el.innerHTML = '<div class="muted">No users match.</div>'; return; }
    el.innerHTML = d.users.map(u => {
      const status = u.suspended ? 'suspended' : (u.payment_failed ? 'payment_failed' : 'active');
      const cls = status === 'suspended' ? 'danger' : status === 'payment_failed' ? 'warn' : 'ok';
      const initial = (u.name || u.email || '?')[0].toUpperCase();
      // Each row is a link to the dedicated user detail page. No more
      // modal — the operator gets a real URL (/admin/user/123) they
      // can bookmark, share, or refresh.
      return `<a class="kvrow userrow" data-id="${u.id}" href="/admin/user/${u.id}" style="text-decoration:none;color:inherit">
        <span><span class="avatar" style="width:24px;height:24px;font-size:.75rem;display:inline-block;vertical-align:middle">${initial}</span> <b>${esc(u.name || '—')}</b> <span class="muted mono small-text">${esc(u.email)}</span></span>
        <span><span class="pill info">${esc(u.role)}</span> <span class="muted">${esc(u.plan)}</span></span>
        <span><span class="pill ${cls}">${status}</span> <span class="muted small-text">${fmt(u.last_login_at)}</span></span>
      </a>`;
    }).join('');
  }
  document.getElementById('userSearchBtn').onclick = () => loadUsers();
  document.getElementById('userSearch').onkeypress = (e) => { if (e.key === 'Enter') loadUsers(); };

  // ============================================================
  // Alerts — collapsed by default. Category summary line.
  // Click "show details" to expand.
  // ============================================================
  async function loadAlerts() {
    const ack = document.getElementById('alertAckFilter').value;
    const q = new URLSearchParams();
    if (ack) q.set('acknowledged', ack);
    q.set('limit', '50');
    const d = await api('/admin/alerts?' + q);
    if (!d) return;
    const c = d.counts || {};
    document.getElementById('alertCount').textContent = `(${c.unack || 0} unack · ${c.total || 0} total)`;
    const byCat = {};
    for (const a of d.alerts) {
      const k = a.category || 'other';
      if (!byCat[k]) byCat[k] = { count: 0, unack: 0, sample: a };
      byCat[k].count++;
      if (!a.acknowledged) byCat[k].unack++;
    }
    const cats = Object.entries(byCat).sort((a, b) => b[1].unack - a[1].unack);
    document.getElementById('alertsSummary').innerHTML = cats.length
      ? cats.map(([k, v]) => `<span class="pill ${v.unack ? 'warn' : 'info'}" style="margin-right:.3rem">${esc(k)}: ${v.unack}/${v.count} unack</span>`).join('')
      : '<span class="muted">No alerts.</span>';
    const block = document.getElementById('alertsBlock');
    if (!d.alerts.length) { block.innerHTML = '<div class="muted center" style="padding:.5rem">No alerts.</div>'; return; }
    block.innerHTML = d.alerts.slice(0, 10).map(a => {
      return `<div class="kvrow small-text" style="padding:.3rem 0">
        <span><span class="pill ${a.severity === 'critical' ? 'danger' : a.severity === 'error' ? 'danger' : a.severity === 'warn' ? 'warn' : 'info'}">${a.severity[0].toUpperCase()}</span> <b>${esc(a.title)}</b></span>
        <span class="muted">${esc(a.ref)} · ${fmtDT(a.created_at)}</span>
        <span>${!a.acknowledged ? `<button class="secondary" data-ack="${a.id}">Ack</button>` : '<span class="muted">ack</span>'}</span>
      </div>`;
    }).join('') + (d.alerts.length > 10 ? `<div class="muted small-text center" style="padding:.3rem">… ${d.alerts.length - 10} more, click row to filter</div>` : '');
    block.querySelectorAll('[data-ack]').forEach(b => b.onclick = async () => {
      await api(`/admin/alerts/${b.dataset.ack}/acknowledge`, { method: 'POST' });
      loadAlerts();
    });
  }
  document.getElementById('alertAckFilter').onchange = loadAlerts;
  document.getElementById('testAlertBtn').onclick = async () => {
    const r = await api('/admin/alerts/test-failure', { method: 'POST' });
    if (r && r.ok) { flash('ok', 'Test alert raised.'); loadAlerts(); }
  };
  document.getElementById('ackVisibleBtn').onclick = async () => {
    if (!confirm('Acknowledge every visible alert?')) return;
    const r = await api('/admin/alerts/bulk-acknowledge', { method: 'POST', body: JSON.stringify({ scope: 'all_visible' }) });
    if (r && r.ok) { flash('ok', `Acknowledged ${r.updated}.`); loadAlerts(); } else flash('err', 'Failed.');
  };

  // ============================================================
  // Trusted devices — single line per device.
  // ============================================================
  function browserLabel(ua) {
    if (!ua) return 'Unknown';
    if (/Edg\//.test(ua))    return 'Edge';
    if (/OPR\//.test(ua))    return 'Opera';
    if (/Chrome\//.test(ua)) return 'Chrome';
    if (/Firefox\//.test(ua))return 'Firefox';
    if (/Safari\//.test(ua)) return 'Safari';
    return ua.slice(0, 40);
  }
  async function loadTrustedDevices() {
    const d = await api('/admin/trusted-devices');
    if (!d) return;
    const rows = d.devices || [];
    document.getElementById('trustedCount').textContent = `(${rows.length})`;
    const el = document.getElementById('trustedRows');
    if (!el) return;
    if (!rows.length) { el.innerHTML = '<div class="muted">No trusted devices. Verify 2FA to issue one.</div>'; return; }
    el.innerHTML = rows.map((r, i) => {
      const expires = r.expires_at ? new Date(r.expires_at) : null;
      const daysLeft = expires ? Math.max(0, Math.round((expires - Date.now()) / 86400000)) : 0;
      return `<div class="kvrow">
        <span><b>${browserLabel(r.user_agent)}</b> <span class="muted small-text">${esc(r.ip_first_seen || '—')}</span> ${i === 0 ? '<span class="pill ok">this device</span>' : ''}</span>
        <span class="muted small-text">last ${r.last_used_at ? fmtDT(r.last_used_at) : '—'}</span>
        <span class="muted small-text">${daysLeft}d</span>
        <span>${i === 0 ? '' : `<button class="secondary danger" data-revoke="${r.id}">×</button>`}</span>
      </div>`;
    }).join('');
    el.querySelectorAll('[data-revoke]').forEach(b => b.onclick = async () => {
      if (!confirm('Revoke this device?')) return;
      const r = await api(`/admin/trusted-devices/${b.dataset.revoke}`, { method: 'DELETE' });
      if (r && r.ok) { flash('ok', 'Revoked.'); loadTrustedDevices(); } else flash('err', 'Failed.');
    });
  }
  document.getElementById('revokeAllDevicesBtn').onclick = async () => {
    if (!confirm('Revoke ALL trusted devices? You will have to re-verify 2FA on next access.')) return;
    const r = await api('/admin/trusted-devices', { method: 'DELETE' });
    if (r && r.ok) { flash('ok', `Revoked ${r.revoked}.`); setTimeout(() => location.reload(), 1500); } else flash('err', 'Failed.');
  };

  // ============================================================
  // Audit log — lazy-loaded when the details is opened
  // ============================================================
  let auditLoaded = false;
  document.querySelector('#audit details').addEventListener('toggle', async (e) => {
    if (e.target.open && !auditLoaded) { auditLoaded = true; loadAudit(); }
  });
  async function loadAudit() {
    const d = await api('/admin/audit?limit=20');
    if (!d) return;
    document.getElementById('auditSummary').textContent = `(${d.log.length} recent)`;
    document.getElementById('auditRows').innerHTML = d.log.length
      ? d.log.map(a => `<div class="kvrow small-text">
          <span class="muted mono">${fmtDT(a.created_at)}</span>
          <span>${esc(a.actor_email || 'system')}</span>
          <span><span class="pill info">${esc(a.action)}</span></span>
          <span class="muted">${esc(a.target || '')}</span>
        </div>`).join('')
      : '<div class="muted">No events.</div>';
  }

  // ============================================================
  // Logout
  // ============================================================
  document.getElementById('logout').onclick = async (e) => {
    e.preventDefault();
    await api('/auth/logout', { method: 'POST' });
    location.href = '/login';
  };

  // ============================================================
  // 2FA wizard (still shows up if not enabled/verified)
  // ============================================================
  async function check2FAState() {
    const s = await api('/admin/2fa/status');
    if (!s || s.error) return;
    if (!s.enabled) show2FABanner({ state: 'unenrolled' });
    else if (!s.verified_this_session) show2FABanner({ state: 'needs_verify' });
  }
  function show2FABanner({ state }) {
    const existing = document.getElementById('twofaBanner');
    if (existing) existing.remove();
    const main = document.querySelector('main');
    if (!main) return;
    const div = document.createElement('div');
    div.id = 'twofaBanner';
    div.className = 'panel';
    div.style.borderLeft = '4px solid var(--warn)';
    div.style.background = 'var(--bg-3)';
    if (state === 'unenrolled') {
      div.innerHTML = `<h2>⚠️ Enable 2FA</h2><p>Superadmin accounts must enable 2FA. <button id="enrollStart" class="warn">Set up</button></p>
        <div id="enrollPanel" style="display:none;margin-top:1rem">
          <p>Scan with Google Authenticator / 1Password / Authy:</p>
          <div id="qrCode" style="margin:.5rem 0"></div>
          <details><summary>or paste secret</summary><code id="secretText" class="mono"></code></details>
          <form id="enrollForm" class="mt-2">
            <input name="token" pattern="\\d{6}" required maxlength="6" placeholder="123456" />
            <button type="submit" class="warn">Enable</button>
            <label style="font-size:.8rem"><input type="checkbox" id="rememberDeviceEnroll" checked /> remember this device 30d</label>
          </form>
          <div id="backupCodes" style="display:none;margin-top:1rem"></div>
        </div>`;
      main.prepend(div);
      document.getElementById('enrollStart').onclick = async () => {
        const r = await api('/admin/2fa/start', { method: 'POST' });
        if (r && r.secret) {
          document.getElementById('enrollPanel').style.display = '';
          document.getElementById('qrCode').innerHTML = `<img alt="2FA QR" src="https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=${encodeURIComponent(r.otpauth_url)}" />`;
          document.getElementById('secretText').textContent = r.secret;
        } else flash('err', r.message || 'Failed');
      };
      document.getElementById('enrollForm').onsubmit = async (e) => {
        e.preventDefault();
        const remember = document.getElementById('rememberDeviceEnroll').checked !== false;
        const r = await api('/admin/2fa/verify', { method: 'POST', body: JSON.stringify({ token: e.target.token.value, remember_device: remember }) });
        if (r && r.ok) {
          flash('ok', r.trusted_device_issued ? '2FA enabled. Device trusted for 30d.' : '2FA enabled.');
          const bc = document.getElementById('backupCodes');
          bc.style.display = '';
          bc.innerHTML = `<h3>Backup codes</h3><p>Save these. Each can be used ONCE.</p><pre class="mono">${(r.backup_codes || []).join('\n')}</pre>`;
          setTimeout(() => location.reload(), 8000);
        } else flash('err', r.message || 'Verification failed');
      };
    } else if (state === 'needs_verify') {
      div.innerHTML = `<h2>🔐 Verify 2FA</h2>
        <form id="verifyForm" class="row">
          <input name="token" pattern="\\d{6}" maxlength="6" required placeholder="123456" autofocus />
          <button type="submit" class="warn">Verify</button>
          <label style="font-size:.8rem"><input type="checkbox" id="rememberDevice" checked /> remember this device 30d</label>
        </form>
        <p class="muted" style="font-size:.75rem">Cookie is client-side; survives deploys.</p>`;
      main.prepend(div);
      document.getElementById('verifyForm').onsubmit = async (e) => {
        e.preventDefault();
        const remember = document.getElementById('rememberDevice').checked !== false;
        const r = await api('/2fa/verify', { method: 'POST', body: JSON.stringify({ token: e.target.token.value, remember_device: remember }) });
        if (r && r.ok) { flash('ok', r.trusted_device_issued ? 'Verified. Trusted 30d.' : 'Verified.'); location.reload(); }
        else flash('err', r.message || 'Invalid code');
      };
    }
  }
  window.addEventListener('ncii:2fa_required', () => check2FAState());

  // ============================================================
  // Contact us modal — admin → support.
  // IDOR-safe: server scopes every read to req.user.id. We never
  // put an id in a request that wasn't returned to us in the same
  // session; the server validates the CSRF token.
  // ============================================================
  const contactModal = document.getElementById('contactModal');
  const contactForm  = document.getElementById('contactForm');
  const contactErr   = document.getElementById('contactErr');
  const contactHist  = document.getElementById('contactHistory');

  function fmtRel(ms) {
    const diff = (Date.now() - ms) / 1000;
    if (diff < 60)        return 'just now';
    if (diff < 3600)      return Math.floor(diff/60) + 'm ago';
    if (diff < 86400)     return Math.floor(diff/3600) + 'h ago';
    if (diff < 604800)    return Math.floor(diff/86400) + 'd ago';
    return new Date(ms).toISOString().slice(0,10);
  }

  async function loadContactHistory() {
    if (!contactHist) return;
    contactHist.textContent = 'Loading…';
    const r = await api('/api/admin/contact?limit=10');
    if (!r || !r.items) {
      contactHist.innerHTML = '<span class="muted">No messages yet.</span>';
      return;
    }
    if (!r.items.length) {
      contactHist.innerHTML = '<span class="muted">No messages yet.</span>';
      return;
    }
    contactHist.innerHTML = r.items.map(m => {
      const statusColor = m.status === 'replied' ? 'var(--ok)' : m.status === 'closed' ? 'var(--fg-muted)' : 'var(--warn)';
      const esc = s => String(s||'').replace(/[<>&"']/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;',"'":'&#39;'})[c]);
      return '<div style="padding:.5rem 0;border-bottom:1px solid var(--border)">' +
        '<div style="display:flex;justify-content:space-between;gap:8px">' +
          '<strong style="font-weight:500">' + esc(m.subject) + '</strong>' +
          '<span class="muted" style="white-space:nowrap;font-size:12px">' + esc(fmtRel(m.created_at)) + '</span>' +
        '</div>' +
        '<div style="font-size:11px;color:var(--fg-muted);margin-top:2px">' +
          esc(m.priority) + ' · <span style="color:' + statusColor + '">' + esc(m.status) + '</span>' +
        '</div>' +
      '</div>';
    }).join('');
  }

  if (document.getElementById('openContact')) {
    document.getElementById('openContact').onclick = (e) => {
      e.preventDefault();
      if (!contactModal) return;
      contactModal.classList.remove('hidden');
      contactErr.textContent = '';
      loadContactHistory();
    };
  }
  if (document.getElementById('contactCancel')) {
    document.getElementById('contactCancel').onclick = () => {
      contactModal.classList.add('hidden');
    };
  }
  if (contactForm) {
    contactForm.onsubmit = async (e) => {
      e.preventDefault();
      contactErr.textContent = '';
      const f = e.target;
      const body = {
        subject:  (f.subject.value  || '').trim(),
        body:     (f.body.value     || '').trim(),
        priority: (f.priority.value || 'normal'),
      };
      if (!body.subject || !body.body) {
        contactErr.textContent = 'Subject and message are required.';
        return;
      }
      const r = await api('/api/admin/contact', { method: 'POST', body: JSON.stringify(body) });
      if (r && r.ok) {
        f.reset();
        f.priority.value = 'normal';
        loadContactHistory();
        flash('ok', 'Sent. We\'ll reply by email.');
      } else {
        contactErr.textContent = (r && (r.message || r.error)) || 'Send failed.';
      }
    };
  }

  // ============================================================
  // Boot
  // ============================================================
  loadKpis();
  loadCases();
  loadTakedownReso();
  loadKeys();
  loadUsers();
  loadAlerts();
  loadTrustedDevices();
  check2FAState();
})();