<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Auth tab. Manages the stack sitting in front of every protected hostname — NPM proxy
//   hosts and certificates, LLDAP users and groups, and Authelia access-control rules.
//
// OPERATIONAL MODEL
//   The most consequential page in the UI. A wrong edit here does not render badly, it locks
//   people out of every service or exposes one that should be protected.
//
// DESIGN PRINCIPLES
//   Requires include/auth.php directly rather than going through an endpoint for everything.
//       Server-rendered for the initial state, with api/auth.php handling mutations. The
//       credentials involved never reach the browser either way.
//
//   Only the owner host edits auth config.
//       Changes are made here and reach the partner through Critical-Data sync, not by the
//       browser writing to two hosts. One source of truth, one direction of travel.
//
//   Authelia rules are edited as a block, preserving the rest of the YAML untouched.
//
// OPERATIONAL SAFEGUARDS
//   Every destructive action is confirmed — deleting a user, removing a proxy host, or
//   rewriting the rule set are all one click away from locking out a household.
//
//   Rule writes are atomic and refuse a missing config. include/auth.php writes to a temp
//   file and renames; a missing configuration.yml is an error, never a freshly created file
//   with a permissive default policy.
//
//   Certificate state is read-only here. Renewal is owned by the cert monitor.
//
//   All values render escaped — usernames, domains and rule fields are attacker-adjacent
//   strings by definition on this page.
//
// RENDERS
//   NPM proxy host list and editor, certificate status
//   LLDAP user and group management
//   Authelia access-control rules and default policy
//
// DEPENDS ON
//   include/auth.php  required directly for initial render
//   api/auth.php      mutations
//   api/cert.php      certificate status
?>
<style>
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
.vv-au-toolbar  { display:flex;align-items:center;gap:8px;margin-bottom:12px;flex-wrap:wrap; }
.vv-au-title    { font-size:13px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;flex:1; }
.vv-au-tab      { font-size:11px;padding:3px 12px;border-radius:3px;border:1px solid #2a2a2a;background:#1a1a1a;color:#666;cursor:pointer;white-space:nowrap; }
.vv-au-tab:hover{ background:#222;color:#aaa; }
.vv-au-tab.active { background:#0d1f2a;border-color:#1a3a5a;color:#5c9fd4; }
.vv-au-btn      { font-size:11px;padding:3px 10px;border-radius:3px;border:1px solid #2a2a2a;background:#1a1a1a;color:#888;cursor:pointer;white-space:nowrap; }
.vv-au-btn:hover{ background:#222;color:#bbb; }
.vv-au-btn.prim { border-color:#1a3a5a;background:#0d1f2a;color:#5c9fd4; }
.vv-au-btn.warn { border-color:#3a2000;background:#1f1200;color:#ff9800; }
.vv-au-btn.danger{border-color:#3a1a1a;background:#200d0d;color:#ef5350; }
.vv-au-btn.green{ border-color:#1a3a1a;background:#0d1f0d;color:#4caf50; }
.vv-au-btn:disabled { opacity:.4;cursor:default; }

/* ── Panels ──────────────────────────────────────────────────────────────── */
.vv-au-panel    { display:none; }
.vv-au-panel.active { display:block; }

/* ── Tables ──────────────────────────────────────────────────────────────── */
.vv-au-tbl      { width:100%;border-collapse:collapse;font-size:11px; }
.vv-au-tbl th   { text-align:left;padding:5px 10px;font-size:10px;color:#444;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid #1e1e1e;white-space:nowrap; }
.vv-au-tbl td   { padding:7px 10px;border-bottom:1px solid #161616;vertical-align:middle; }
.vv-au-tbl tr:hover td { background:#141414; }
.vv-au-tbl tr:last-child td { border-bottom:none; }

/* ── Badges ──────────────────────────────────────────────────────────────── */
.vv-au-badge    { font-size:10px;padding:1px 6px;border-radius:2px;white-space:nowrap;display:inline-block; }
.vv-au-badge.ssl   { background:#0d1f0d;border:1px solid #1a3a1a;color:#4caf50; }
.vv-au-badge.nossl { background:#111;border:1px solid #222;color:#444; }
.vv-au-badge.on    { background:#0d1f0d;border:1px solid #1a3a1a;color:#4caf50; }
.vv-au-badge.off   { background:#1a0a0a;border:1px solid #2a1a1a;color:#555; }
.vv-au-badge.bypass  { background:#0a1a2a;border:1px solid #1a3a5a;color:#5c9fd4; }
.vv-au-badge.one_factor { background:#1a1a0a;border:1px solid #3a3a1a;color:#cddc39; }
.vv-au-badge.two_factor { background:#0d1f0d;border:1px solid #1a3a1a;color:#4caf50; }
.vv-au-badge.deny    { background:#1a0a0a;border:1px solid #3a1a1a;color:#ef5350; }
.vv-au-badge.grp  { background:#1a0d2a;border:1px solid #2a1a4a;color:#9c6ff7;margin:1px 2px; }
.vv-au-badge.host { background:#111;border:1px solid #1e1e1e;color:#444; }

/* ── Cards ───────────────────────────────────────────────────────────────── */
.vv-au-card     { background:#161616;border:1px solid #222;border-radius:6px;overflow:hidden; }
.vv-au-card-h   { display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid #1e1e1e;background:#111; }
.vv-au-card-title { font-size:11px;font-weight:bold;color:#666;text-transform:uppercase;letter-spacing:.06em;flex:1; }

/* ── Users / Groups split ────────────────────────────────────────────────── */
.vv-au-ug-grid  { display:grid;grid-template-columns:3fr 2fr;gap:12px; }
@media (max-width:700px) { .vv-au-ug-grid { grid-template-columns:1fr; } }

.vv-au-user-row { padding:8px 12px;border-bottom:1px solid #1a1a1a;display:flex;align-items:center;gap:8px;flex-wrap:wrap; }
.vv-au-user-row:last-child { border-bottom:none; }
.vv-au-user-row:hover { background:#141414; }
.vv-au-user-name { font-size:12px;color:#bbb;font-weight:bold;min-width:80px; }
.vv-au-user-email{ font-size:10px;color:#444;flex:1; }
.vv-au-user-acts { display:flex;gap:4px;margin-left:auto;flex-shrink:0; }

.vv-au-grp-row  { padding:8px 12px;border-bottom:1px solid #1a1a1a;cursor:pointer; }
.vv-au-grp-row:last-child { border-bottom:none; }
.vv-au-grp-row:hover { background:#141414; }
.vv-au-grp-name { font-size:12px;color:#bbb;font-weight:bold; }
.vv-au-grp-cnt  { font-size:10px;color:#444;margin-top:2px; }
.vv-au-grp-acts { float:right;display:flex;gap:4px;margin-top:1px; }

.vv-au-grp-members { padding:6px 12px 10px 20px;background:#0f0f0f;border-bottom:1px solid #1a1a1a; display:none; }
.vv-au-grp-members.open { display:block; }
.vv-au-grp-member { font-size:11px;color:#666;padding:2px 0;display:flex;align-items:center;gap:6px; }
.vv-au-grp-member-rm { font-size:11px;color:#3a1a1a;cursor:pointer;padding:0 2px; }
.vv-au-grp-member-rm:hover { color:#ef5350; }

/* ── Access Control ──────────────────────────────────────────────────────── */
.vv-au-ac-defpol { display:flex;align-items:center;gap:10px;padding:8px 12px;background:#111;border:1px solid #222;border-radius:4px;margin-bottom:10px;font-size:11px;color:#555; }
.vv-au-ac-defpol select { background:#0d0d0d;border:1px solid #2a2a2a;border-radius:3px;color:#888;font-size:11px;padding:2px 6px; }
.vv-au-rule-num { color:#333;font-size:10px;width:24px;text-align:right;flex-shrink:0; }
.vv-au-rule-acts { display:flex;gap:3px;white-space:nowrap; }
.vv-au-icon-btn { font-size:12px;color:#444;cursor:pointer;padding:1px 3px;border-radius:2px;background:none;border:none;line-height:1; }
.vv-au-icon-btn:hover { color:#bbb;background:#222; }
.vv-au-icon-btn.del:hover { color:#ef5350;background:#1a0808; }

/* ── Section toolbar (within panel) ─────────────────────────────────────── */
.vv-au-sec-bar  { display:flex;align-items:center;gap:8px;margin-bottom:10px; }
.vv-au-sec-title{ font-size:11px;color:#555;flex:1; }

/* ── Toggle switch ───────────────────────────────────────────────────────── */
.vv-au-tog      { width:28px;height:16px;border-radius:8px;background:#1e1e1e;border:1px solid #2a2a2a;position:relative;cursor:pointer;display:inline-block;flex-shrink:0; }
.vv-au-tog.on   { background:#1a3a1a;border-color:#2d5a2d; }
.vv-au-tog::after { content:'';position:absolute;top:2px;left:2px;width:10px;height:10px;border-radius:50%;background:#444;transition:left .12s,background .12s; }
.vv-au-tog.on::after { left:14px;background:#4caf50; }

/* ── Modal ───────────────────────────────────────────────────────────────── */
.vv-au-overlay  { position:fixed;inset:0;background:#0009;z-index:9000;display:none;align-items:center;justify-content:center; }
.vv-au-overlay.open { display:flex; }
.vv-au-modal    { background:#181818;border:1px solid #2a2a2a;border-radius:6px;padding:18px 20px;min-width:360px;max-width:520px;width:90vw;max-height:85vh;overflow-y:auto; }
.vv-au-modal h3 { font-size:12px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;margin:0 0 14px; }
.vv-au-field    { margin-bottom:10px; }
.vv-au-label    { display:block;font-size:10px;color:#555;margin-bottom:3px;text-transform:uppercase;letter-spacing:.04em; }
.vv-au-input    { width:100%;box-sizing:border-box;background:#0d0d0d;border:1px solid #252525;border-radius:3px;color:#bbb;font-size:11px;padding:5px 8px;outline:none; }
.vv-au-input:focus { border-color:#1a3a5a; }
.vv-au-select   { width:100%;box-sizing:border-box;background:#0d0d0d;border:1px solid #252525;border-radius:3px;color:#bbb;font-size:11px;padding:5px 8px;outline:none; }
.vv-au-hint     { font-size:10px;color:#333;margin-top:2px; }
.vv-au-modal-acts { display:flex;justify-content:flex-end;gap:8px;margin-top:16px; }
.vv-au-tog-row  { display:flex;align-items:center;gap:8px;margin-bottom:10px; }
.vv-au-tog-lbl  { font-size:11px;color:#666; }
.vv-au-adv-toggle { font-size:10px;color:#333;cursor:pointer;margin-bottom:8px; }
.vv-au-adv-toggle:hover { color:#666; }
.vv-au-adv-section { display:none; }
.vv-au-adv-section.open { display:block; }
.vv-au-err      { font-size:11px;color:#ef5350;margin-top:8px;display:none; }
.vv-au-err.show { display:block; }

/* ── Misc ────────────────────────────────────────────────────────────────── */
.vv-au-empty    { padding:24px;text-align:center;font-size:11px;color:#333; }
.vv-au-domain   { font-size:12px;color:#bbb;font-weight:bold; }
.vv-au-fwd      { font-size:10px;color:#444; }
.vv-au-loading  { color:#333;font-size:11px;padding:16px;text-align:center; }

/* ── Certs panel ─────────────────────────────────────────────────────────── */
.vv-au-cert-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px; }
.vv-au-cert-days { font-size:28px;font-weight:700;line-height:1;margin:6px 0 2px; }
.vv-au-cert-bar  { height:3px;border-radius:2px;background:#1a1a1a;overflow:hidden;margin-top:8px; }
.vv-au-cert-fill { height:100%;border-radius:2px;transition:width .3s; }
</style>

<?php
require_once __DIR__ . '/../include/auth.php';
$isOwner = vv_is_owner();
?>

<div class="vv-au-toolbar">
  <span class="vv-au-title">Auth Stack</span>
  <button class="vv-au-tab active" data-tab="proxies">Proxies</button>
  <button class="vv-au-tab" data-tab="users">Users &amp; Groups</button>
  <button class="vv-au-tab" data-tab="acl">Access Control</button>
  <button class="vv-au-tab" data-tab="certs">Certs</button>
  <button class="vv-au-btn" id="vv-au-refresh" title="Refresh current tab">&#8635; Refresh</button>
</div>

<!-- ── Proxies ─────────────────────────────────────────────────────────────── -->
<div class="vv-au-panel active" id="vv-au-panel-proxies">
  <div class="vv-au-sec-bar">
    <span class="vv-au-sec-title" id="vv-au-proxy-count"></span>
    <button class="vv-au-btn prim" id="vv-au-proxy-add">+ Add Proxy</button>
  </div>
  <div class="vv-au-card">
    <div class="vv-au-loading" id="vv-au-proxy-loading">Loading…</div>
    <table class="vv-au-tbl" id="vv-au-proxy-tbl" style="display:none">
      <thead><tr>
        <th>Domain</th><th>Forward</th><th>SSL</th><th>Status</th><th></th>
      </tr></thead>
      <tbody id="vv-au-proxy-body"></tbody>
    </table>
    <div class="vv-au-empty" id="vv-au-proxy-empty" style="display:none">No proxy hosts configured.</div>
  </div>
</div>

<!-- ── Users & Groups ─────────────────────────────────────────────────────── -->
<div class="vv-au-panel" id="vv-au-panel-users">
  <div class="vv-au-ug-grid">

    <div class="vv-au-card">
      <div class="vv-au-card-h">
        <span class="vv-au-card-title">Users</span>
        <button class="vv-au-btn prim" id="vv-au-user-add">+ Add User</button>
      </div>
      <div class="vv-au-loading" id="vv-au-users-loading">Loading…</div>
      <div id="vv-au-users-list"></div>
      <div class="vv-au-empty" id="vv-au-users-empty" style="display:none">No users found.</div>
    </div>

    <div class="vv-au-card">
      <div class="vv-au-card-h">
        <span class="vv-au-card-title">Groups</span>
        <button class="vv-au-btn prim" id="vv-au-group-add">+ Add Group</button>
      </div>
      <div class="vv-au-loading" id="vv-au-groups-loading">Loading…</div>
      <div id="vv-au-groups-list"></div>
      <div class="vv-au-empty" id="vv-au-groups-empty" style="display:none">No groups found.</div>
    </div>

  </div>
</div>

<!-- ── Access Control ─────────────────────────────────────────────────────── -->
<div class="vv-au-panel" id="vv-au-panel-acl">
  <div class="vv-au-ac-defpol" id="vv-au-ac-defpol-bar">
    <span>Default policy:</span>
    <select id="vv-au-ac-defpol" <?= $isOwner ? '' : 'disabled' ?>>
      <option value="deny">deny</option>
      <option value="two_factor">two_factor</option>
      <option value="one_factor">one_factor</option>
      <option value="bypass">bypass</option>
    </select>
    <?php if (!$isOwner): ?>
      <span style="font-size:10px;color:#3a2a1a;">default policy editable on HOST1</span>
    <?php endif; ?>
    <button class="vv-au-btn green" id="vv-au-ac-save" style="margin-left:auto">Save &amp; Restart Authelia</button>
  </div>
  <div class="vv-au-sec-bar">
    <span class="vv-au-sec-title" id="vv-au-acl-count"></span>
    <button class="vv-au-btn prim" id="vv-au-rule-add">+ Add Rule</button>
  </div>
  <div class="vv-au-card">
    <div class="vv-au-loading" id="vv-au-acl-loading">Loading…</div>
    <table class="vv-au-tbl" id="vv-au-acl-tbl" style="display:none">
      <thead><tr>
        <th style="width:24px">#</th>
        <th>Domain</th><th>Policy</th><th>Subject</th><th></th>
      </tr></thead>
      <tbody id="vv-au-acl-body"></tbody>
    </table>
    <div class="vv-au-empty" id="vv-au-acl-empty" style="display:none">No rules configured.</div>
  </div>
</div>

<!-- ── Certs ───────────────────────────────────────────────────────────────── -->
<div class="vv-au-panel" id="vv-au-panel-certs">
  <div class="vv-au-sec-bar">
    <span class="vv-au-sec-title" id="vv-au-cert-ts"></span>
    <button class="vv-au-btn prim" id="vv-au-cert-run">Refresh</button>
  </div>
  <div class="vv-au-cert-grid" id="vv-au-cert-grid">
    <div class="vv-au-loading">Loading…</div>
  </div>
  <div id="vv-au-cert-cfg" style="margin-top:10px;font-size:10px;color:#3a3a3a;"></div>
</div>

<!-- ── Modal overlay ──────────────────────────────────────────────────────── -->
<div class="vv-au-overlay" id="vv-au-overlay">
  <div class="vv-au-modal" id="vv-au-modal"></div>
</div>

<script>
(function () {
'use strict';

const API      = '/plugins/varaverk/api/auth.php';
const CERT_API = '/plugins/varaverk/api/cert.php';
const IS_OWNER = <?= $isOwner ? 'true' : 'false' ?>;

// ── State ─────────────────────────────────────────────────────────────────────
let _proxies = [], _certs = [];
let _users   = [], _groups = [];
let _rules   = [], _defaultPolicy = 'deny';
let _activeTab = 'proxies';

// ── Helpers ───────────────────────────────────────────────────────────────────
function _esc(s) {
  return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

function _get(action, cb) {
  fetch(API + '?action=' + action)
    .then(r => r.json()).then(cb)
    .catch(e => cb({ ok: false, error: String(e) }));
}

function _post(params, cb) {
  const fd = new FormData();
  for (const [k, v] of Object.entries(params)) fd.append(k, v);
  fetch(API, { method: 'POST', body: fd })
    .then(r => r.json()).then(cb)
    .catch(e => cb({ ok: false, error: String(e) }));
}

function _modal(html) {
  document.getElementById('vv-au-modal').innerHTML = html;
  document.getElementById('vv-au-overlay').classList.add('open');
}
function _closeModal() {
  document.getElementById('vv-au-overlay').classList.remove('open');
}

function _togHtml(id, on, title) {
  return `<span class="vv-au-tog${on?' on':''}" data-tog="${_esc(id)}" title="${_esc(title)}"></span>`;
}

function _policyBadge(p) {
  return `<span class="vv-au-badge ${_esc(p)}">${_esc(p)}</span>`;
}

function _normSubject(val) {
  if (!val) return [];
  if (Array.isArray(val)) return val;
  return [val];
}

function _normDomain(val) {
  if (!val) return [];
  if (Array.isArray(val)) return val;
  return [val];
}

// ── Tab switching ─────────────────────────────────────────────────────────────
document.querySelectorAll('.vv-au-tab').forEach(btn => {
  btn.addEventListener('click', () => {
    const tab = btn.dataset.tab;
    document.querySelectorAll('.vv-au-tab').forEach(b => b.classList.remove('active'));
    document.querySelectorAll('.vv-au-panel').forEach(p => p.classList.remove('active'));
    btn.classList.add('active');
    document.getElementById('vv-au-panel-' + tab).classList.add('active');
    _activeTab = tab;
    _loadTab(tab);
  });
});

document.getElementById('vv-au-refresh').addEventListener('click', () => _loadTab(_activeTab));

function _loadTab(tab) {
  if (tab === 'proxies') _loadProxies();
  if (tab === 'users')   { _loadUsers(); _loadGroups(); }
  if (tab === 'acl')     _loadAcl();
  if (tab === 'certs')   _loadCerts();
}

// ── Proxies ───────────────────────────────────────────────────────────────────
function _loadProxies() {
  const loading = document.getElementById('vv-au-proxy-loading');
  const tbl     = document.getElementById('vv-au-proxy-tbl');
  const empty   = document.getElementById('vv-au-proxy-empty');
  loading.style.display = 'block';
  tbl.style.display = 'none';
  empty.style.display = 'none';

  // Load certs and proxies in parallel
  let certsLoaded = false, proxiesLoaded = false;
  function _check() {
    if (!certsLoaded || !proxiesLoaded) return;
    loading.style.display = 'none';
    if (!_proxies.length) { empty.style.display = 'block'; return; }
    tbl.style.display = 'table';
    _renderProxies();
  }

  _get('npm_certs', r => { _certs = r.certs || []; certsLoaded = true; _check(); });
  _get('npm_proxies', r => {
    if (!r.ok) { loading.innerHTML = '<span style="color:#ef5350">'+_esc(r.error)+'</span>'; return; }
    _proxies = r.proxies || [];
    proxiesLoaded = true;
    _check();
  });
}

function _renderProxies() {
  const count = document.getElementById('vv-au-proxy-count');
  count.textContent = _proxies.length + ' host' + (_proxies.length !== 1 ? 's' : '');

  const body = document.getElementById('vv-au-proxy-body');
  body.innerHTML = _proxies.map(p => {
    const domains  = (p.domain_names || []).join(', ');
    const fwd      = p.forward_scheme + '://' + p.forward_host + ':' + p.forward_port;
    const hasSsl   = p.certificate_id && p.certificate_id !== '0';
    const sslBadge = hasSsl
      ? `<span class="vv-au-badge ssl">SSL</span>`
      : `<span class="vv-au-badge nossl">none</span>`;
    const enabled  = p.enabled;
    return `<tr>
      <td><div class="vv-au-domain">${_esc(domains)}</div></td>
      <td><div class="vv-au-fwd">${_esc(fwd)}</div></td>
      <td>${sslBadge}</td>
      <td>${_togHtml('proxy-' + p.id, enabled, enabled ? 'Enabled — click to disable' : 'Disabled — click to enable')}</td>
      <td style="text-align:right">
        <div class="vv-au-rule-acts">
          <button class="vv-au-icon-btn" data-proxy-edit="${p.id}" title="Edit">✎</button>
          <button class="vv-au-icon-btn del" data-proxy-del="${p.id}" title="Delete">✕</button>
        </div>
      </td>
    </tr>`;
  }).join('');
}

function _proxyModal(id) {
  const p = id ? _proxies.find(x => x.id === id) : null;
  const certOptions = _certs.map(c =>
    `<option value="${c.id}"${p && p.certificate_id == c.id ? ' selected' : ''}>${_esc(c.nice_name || c.domain_names?.join(', '))}</option>`
  ).join('');

  _modal(`<h3>${p ? 'Edit Proxy' : 'Add Proxy'}</h3>
    <div class="vv-au-field">
      <label class="vv-au-label">Domain Names</label>
      <input class="vv-au-input" id="pm-domains" value="${_esc((p?.domain_names||[]).join(', '))}" placeholder="example.com, *.example.com">
      <div class="vv-au-hint">Comma-separated</div>
    </div>
    <div style="display:grid;grid-template-columns:1fr 2fr 80px;gap:8px">
      <div class="vv-au-field">
        <label class="vv-au-label">Scheme</label>
        <select class="vv-au-select" id="pm-scheme">
          <option${(!p||p.forward_scheme==='http')?' selected':''}>http</option>
          <option${(p?.forward_scheme==='https')?' selected':''}>https</option>
        </select>
      </div>
      <div class="vv-au-field">
        <label class="vv-au-label">Forward Host</label>
        <input class="vv-au-input" id="pm-host" value="${_esc(p?.forward_host||'')}" placeholder="192.168.1.100">
      </div>
      <div class="vv-au-field">
        <label class="vv-au-label">Port</label>
        <input class="vv-au-input" id="pm-port" type="number" value="${_esc(p?.forward_port||80)}">
      </div>
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">SSL Certificate</label>
      <select class="vv-au-select" id="pm-cert">
        <option value="0">None</option>
        ${certOptions}
      </select>
    </div>
    <div class="vv-au-tog-row">
      <span class="vv-au-tog${p?.ssl_forced?' on':''}" id="pm-ssl-forced"></span>
      <span class="vv-au-tog-lbl">Force SSL</span>
    </div>
    <div class="vv-au-tog-row">
      <span class="vv-au-tog${p?.block_exploits?' on':''}" id="pm-block-exploits"></span>
      <span class="vv-au-tog-lbl">Block Common Exploits</span>
    </div>
    <div class="vv-au-tog-row">
      <span class="vv-au-tog${(!p||p.allow_websocket_upgrade!==false)?' on':''}" id="pm-websocket"></span>
      <span class="vv-au-tog-lbl">WebSocket Support</span>
    </div>
    <div class="vv-au-err" id="pm-err"></div>
    <div class="vv-au-modal-acts">
      <button class="vv-au-btn" id="pm-cancel">Cancel</button>
      <button class="vv-au-btn prim" id="pm-save">${p ? 'Save' : 'Create'}</button>
    </div>`);

  // Wire inline toggles
  document.querySelectorAll('#vv-au-modal .vv-au-tog').forEach(t => {
    t.addEventListener('click', () => t.classList.toggle('on'));
  });

  document.getElementById('pm-cancel').onclick = _closeModal;
  document.getElementById('pm-save').onclick = () => {
    const domains = document.getElementById('pm-domains').value.split(',').map(s=>s.trim()).filter(Boolean);
    if (!domains.length) { _showModalErr('pm-err', 'Domain required'); return; }
    const host = document.getElementById('pm-host').value.trim();
    if (!host) { _showModalErr('pm-err', 'Forward host required'); return; }

    const data = {
      domain_names:          domains,
      forward_scheme:        document.getElementById('pm-scheme').value,
      forward_host:          host,
      forward_port:          parseInt(document.getElementById('pm-port').value) || 80,
      certificate_id:        parseInt(document.getElementById('pm-cert').value) || 0,
      ssl_forced:            document.getElementById('pm-ssl-forced').classList.contains('on'),
      block_exploits:        document.getElementById('pm-block-exploits').classList.contains('on'),
      allow_websocket_upgrade: document.getElementById('pm-websocket').classList.contains('on'),
      http2_support:         false,
      hsts_enabled:          false,
      hsts_subdomains:       false,
      meta:                  {},
      locations:             [],
      advanced_config:       '',
      enabled:               true,
    };

    const btn = document.getElementById('pm-save');
    btn.disabled = true; btn.textContent = 'Saving…';

    const done = r => {
      if (!r.ok) { _showModalErr('pm-err', r.error||'Save failed'); btn.disabled=false; btn.textContent = p?'Save':'Create'; return; }
      _closeModal(); _loadProxies();
    };
    if (p) _post({ action:'npm_update', id: p.id, data: JSON.stringify(data) }, done);
    else   _post({ action:'npm_create',             data: JSON.stringify(data) }, done);
  };
}

function _showModalErr(id, msg) {
  const el = document.getElementById(id);
  if (el) { el.textContent = msg; el.classList.add('show'); }
}

// Proxy event delegation
document.getElementById('vv-au-panel-proxies').addEventListener('click', e => {
  // Add button
  if (e.target.id === 'vv-au-proxy-add') { _proxyModal(null); return; }
  // Toggle
  const tog = e.target.closest('[data-tog^="proxy-"]');
  if (tog) {
    const id  = parseInt(tog.dataset.tog.split('-')[1]);
    const was = tog.classList.contains('on');
    tog.classList.toggle('on');
    _post({ action:'npm_toggle', id, enabled: was?'0':'1' }, r => {
      if (!r.ok) { tog.classList.toggle('on'); }
    });
    return;
  }
  // Edit
  const editBtn = e.target.closest('[data-proxy-edit]');
  if (editBtn) { _proxyModal(parseInt(editBtn.dataset.proxyEdit)); return; }
  // Delete
  const delBtn = e.target.closest('[data-proxy-del]');
  if (delBtn) {
    const id = parseInt(delBtn.dataset.proxyDel);
    const p  = _proxies.find(x => x.id === id);
    if (!confirm('Delete proxy for ' + (p?.domain_names||['this host']).join(', ') + '?')) return;
    _post({ action:'npm_delete', id }, r => { if (r.ok) _loadProxies(); });
  }
});

// ── Users ─────────────────────────────────────────────────────────────────────
function _loadUsers() {
  const loading = document.getElementById('vv-au-users-loading');
  const list    = document.getElementById('vv-au-users-list');
  const empty   = document.getElementById('vv-au-users-empty');
  loading.style.display = 'block';
  list.innerHTML = '';
  empty.style.display = 'none';

  _get('lldap_users', r => {
    loading.style.display = 'none';
    if (!r.ok) { loading.innerHTML = '<span style="color:#ef5350;padding:10px;display:block">'+_esc(r.error)+'</span>'; loading.style.display='block'; return; }
    _users = r.users || [];
    if (!_users.length) { empty.style.display = 'block'; return; }
    list.innerHTML = _users.map(u => {
      const grpBadges = (u.groups||[]).map(g => `<span class="vv-au-badge grp" title="Click to remove" data-rm-from-group="${_esc(u.id)}" data-gid="${g.id}">${_esc(g.displayName)}</span>`).join('');
      return `<div class="vv-au-user-row">
        <span class="vv-au-user-name">${_esc(u.displayName||u.id)}</span>
        <span class="vv-au-user-email">${_esc(u.email||'')}</span>
        <div style="display:flex;gap:3px;align-items:center;flex-wrap:wrap">${grpBadges}</div>
        <div class="vv-au-user-acts">
          <button class="vv-au-icon-btn" data-user-grp="${_esc(u.id)}" title="Add to group">+grp</button>
          <button class="vv-au-icon-btn" data-user-pass="${_esc(u.id)}" title="Change password">🔑</button>
          <button class="vv-au-icon-btn" data-user-edit="${_esc(u.id)}" title="Edit">✎</button>
          <button class="vv-au-icon-btn del" data-user-del="${_esc(u.id)}" title="Delete">✕</button>
        </div>
      </div>`;
    }).join('');
  });
}

function _userModal(uid) {
  const u = uid ? _users.find(x => x.id === uid) : null;
  _modal(`<h3>${u ? 'Edit User' : 'Add User'}</h3>
    <div class="vv-au-field">
      <label class="vv-au-label">Username (ID)</label>
      <input class="vv-au-input" id="um-uid" value="${_esc(u?.id||'')}" ${u?'readonly':''} placeholder="johndoe">
      ${u ? '' : '<div class="vv-au-hint">Lowercase letters, digits, hyphens — cannot be changed later</div>'}
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">Display Name</label>
      <input class="vv-au-input" id="um-name" value="${_esc(u?.displayName||'')}" placeholder="John Doe">
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">Email</label>
      <input class="vv-au-input" id="um-email" type="email" value="${_esc(u?.email||'')}" placeholder="john@example.com">
    </div>
    ${!u ? `<div class="vv-au-field">
      <label class="vv-au-label">Password</label>
      <input class="vv-au-input" id="um-pass" type="password" placeholder="Initial password">
    </div>` : ''}
    <div class="vv-au-err" id="um-err"></div>
    <div class="vv-au-modal-acts">
      <button class="vv-au-btn" id="um-cancel">Cancel</button>
      <button class="vv-au-btn prim" id="um-save">${u ? 'Save' : 'Create'}</button>
    </div>`);

  document.getElementById('um-cancel').onclick = _closeModal;
  document.getElementById('um-save').onclick = () => {
    const id   = (document.getElementById('um-uid').value||'').trim();
    const name = (document.getElementById('um-name').value||'').trim();
    const email= (document.getElementById('um-email').value||'').trim();
    const pass = document.getElementById('um-pass')?.value || '';
    if (!id)   { _showModalErr('um-err','Username required'); return; }
    if (!email){ _showModalErr('um-err','Email required'); return; }

    const btn = document.getElementById('um-save');
    btn.disabled = true; btn.textContent = 'Saving…';

    const done = r => {
      if (!r.ok) { _showModalErr('um-err', r.error||'Save failed'); btn.disabled=false; btn.textContent=u?'Save':'Create'; return; }
      _closeModal(); _loadUsers();
    };
    if (u) _post({ action:'lldap_update_user', uid:id, email, display_name:name }, done);
    else   _post({ action:'lldap_create_user', uid:id, email, display_name:name, password:pass }, done);
  };
}

function _passModal(uid) {
  _modal(`<h3>Change Password</h3>
    <div class="vv-au-field">
      <label class="vv-au-label">User</label>
      <input class="vv-au-input" value="${_esc(uid)}" readonly>
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">New Password</label>
      <input class="vv-au-input" id="pw-pass" type="password" placeholder="New password" autofocus>
    </div>
    <div class="vv-au-err" id="pw-err"></div>
    <div class="vv-au-modal-acts">
      <button class="vv-au-btn" id="pw-cancel">Cancel</button>
      <button class="vv-au-btn prim" id="pw-save">Set Password</button>
    </div>`);
  document.getElementById('pw-cancel').onclick = _closeModal;
  document.getElementById('pw-save').onclick = () => {
    const pass = document.getElementById('pw-pass').value;
    if (!pass) { _showModalErr('pw-err','Password required'); return; }
    const btn = document.getElementById('pw-save');
    btn.disabled = true; btn.textContent = 'Saving…';
    _post({ action:'lldap_set_password', uid, password:pass }, r => {
      if (!r.ok) { _showModalErr('pw-err', r.error||'Failed'); btn.disabled=false; btn.textContent='Set Password'; return; }
      _closeModal();
    });
  };
}

function _addToGroupModal(uid) {
  const user    = _users.find(u => u.id === uid);
  const userGids= new Set((user?.groups||[]).map(g => g.id));
  const available = _groups.filter(g => !userGids.has(g.id));
  if (!available.length) { alert('User is already in all groups.'); return; }
  const opts = available.map(g => `<option value="${g.id}">${_esc(g.displayName)}</option>`).join('');
  _modal(`<h3>Add to Group</h3>
    <div class="vv-au-field">
      <label class="vv-au-label">User</label>
      <input class="vv-au-input" value="${_esc(user?.displayName||uid)}" readonly>
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">Group</label>
      <select class="vv-au-select" id="ag-grp">${opts}</select>
    </div>
    <div class="vv-au-err" id="ag-err"></div>
    <div class="vv-au-modal-acts">
      <button class="vv-au-btn" id="ag-cancel">Cancel</button>
      <button class="vv-au-btn prim" id="ag-save">Add</button>
    </div>`);
  document.getElementById('ag-cancel').onclick = _closeModal;
  document.getElementById('ag-save').onclick = () => {
    const gid = parseInt(document.getElementById('ag-grp').value);
    const btn = document.getElementById('ag-save');
    btn.disabled = true; btn.textContent = 'Adding…';
    _post({ action:'lldap_add_to_group', uid, gid }, r => {
      if (!r.ok) { _showModalErr('ag-err', r.error||'Failed'); btn.disabled=false; btn.textContent='Add'; return; }
      _closeModal(); _loadUsers(); _loadGroups();
    });
  };
}

// User event delegation
document.getElementById('vv-au-panel-users').addEventListener('click', e => {
  if (e.target.id === 'vv-au-user-add') { _userModal(null); return; }

  const editBtn = e.target.closest('[data-user-edit]');
  if (editBtn) { _userModal(editBtn.dataset.userEdit); return; }

  const passBtn = e.target.closest('[data-user-pass]');
  if (passBtn) { _passModal(passBtn.dataset.userPass); return; }

  const grpBtn = e.target.closest('[data-user-grp]');
  if (grpBtn) { _addToGroupModal(grpBtn.dataset.userGrp); return; }

  const delBtn = e.target.closest('[data-user-del]');
  if (delBtn) {
    const uid  = delBtn.dataset.userDel;
    const user = _users.find(u => u.id === uid);
    if (!confirm('Delete user "' + (user?.displayName||uid) + '"?')) return;
    _post({ action:'lldap_delete_user', uid }, r => { if (r.ok) _loadUsers(); });
    return;
  }

  const rmBadge = e.target.closest('[data-rm-from-group]');
  if (rmBadge) {
    const uid = rmBadge.dataset.rmFromGroup;
    const gid = parseInt(rmBadge.dataset.gid);
    const grp = _groups.find(g => g.id === gid);
    if (!confirm('Remove from group "' + (grp?.displayName||gid) + '"?')) return;
    _post({ action:'lldap_remove_from_group', uid, gid }, r => { if (r.ok) { _loadUsers(); _loadGroups(); } });
  }
});

// ── Groups ────────────────────────────────────────────────────────────────────
function _loadGroups() {
  const loading = document.getElementById('vv-au-groups-loading');
  const list    = document.getElementById('vv-au-groups-list');
  const empty   = document.getElementById('vv-au-groups-empty');
  loading.style.display = 'block';
  list.innerHTML = '';
  empty.style.display = 'none';

  _get('lldap_groups', r => {
    loading.style.display = 'none';
    if (!r.ok) { loading.innerHTML = '<span style="color:#ef5350;padding:10px;display:block">'+_esc(r.error)+'</span>'; loading.style.display='block'; return; }
    _groups = r.groups || [];
    if (!_groups.length) { empty.style.display = 'block'; return; }
    list.innerHTML = _groups.map(g => {
      const members = (g.users||[]).map(u =>
        `<div class="vv-au-grp-member">
          <span>${_esc(u.displayName||u.id)}</span>
          <span class="vv-au-grp-member-rm" data-rm-user="${_esc(u.id)}" data-gid="${g.id}" title="Remove from group">✕</span>
        </div>`
      ).join('');
      return `<div class="vv-au-grp-row" data-grp="${g.id}">
        <div class="vv-au-grp-acts">
          <button class="vv-au-icon-btn del" data-group-del="${g.id}" title="Delete group">✕</button>
        </div>
        <div class="vv-au-grp-name">${_esc(g.displayName)}</div>
        <div class="vv-au-grp-cnt">${(g.users||[]).length} member${(g.users||[]).length!==1?'s':''}</div>
      </div>
      <div class="vv-au-grp-members" id="gm-${g.id}">
        ${members || '<div style="font-size:11px;color:#333;padding:2px 0">No members</div>'}
      </div>`;
    }).join('');
  });
}

// Group event delegation
document.getElementById('vv-au-panel-users').addEventListener('click', e => {
  if (e.target.id === 'vv-au-group-add') {
    _modal(`<h3>Add Group</h3>
      <div class="vv-au-field">
        <label class="vv-au-label">Group Name</label>
        <input class="vv-au-input" id="gm-name" placeholder="admins" autofocus>
      </div>
      <div class="vv-au-err" id="gm-err"></div>
      <div class="vv-au-modal-acts">
        <button class="vv-au-btn" id="gm-cancel">Cancel</button>
        <button class="vv-au-btn prim" id="gm-save">Create</button>
      </div>`);
    document.getElementById('gm-cancel').onclick = _closeModal;
    document.getElementById('gm-save').onclick = () => {
      const name = (document.getElementById('gm-name').value||'').trim();
      if (!name) { _showModalErr('gm-err','Name required'); return; }
      const btn = document.getElementById('gm-save');
      btn.disabled = true; btn.textContent = 'Creating…';
      _post({ action:'lldap_create_group', name }, r => {
        if (!r.ok) { _showModalErr('gm-err', r.error||'Failed'); btn.disabled=false; btn.textContent='Create'; return; }
        _closeModal(); _loadGroups();
      });
    };
    return;
  }

  const grpRow = e.target.closest('[data-grp]');
  if (grpRow && !e.target.closest('button') && !e.target.closest('.vv-au-icon-btn')) {
    const panel = document.getElementById('gm-' + grpRow.dataset.grp);
    if (panel) panel.classList.toggle('open');
    return;
  }

  const delGrp = e.target.closest('[data-group-del]');
  if (delGrp) {
    e.stopPropagation();
    const id  = parseInt(delGrp.dataset.groupDel);
    const grp = _groups.find(g => g.id === id);
    if (!confirm('Delete group "' + (grp?.displayName||id) + '"?')) return;
    _post({ action:'lldap_delete_group', id }, r => { if (r.ok) _loadGroups(); });
    return;
  }

  const rmUser = e.target.closest('[data-rm-user]');
  if (rmUser) {
    e.stopPropagation();
    const uid = rmUser.dataset.rmUser;
    const gid = parseInt(rmUser.dataset.gid);
    _post({ action:'lldap_remove_from_group', uid, gid }, r => { if (r.ok) { _loadUsers(); _loadGroups(); } });
  }
});

// ── Access Control ────────────────────────────────────────────────────────────
function _loadAcl() {
  const loading = document.getElementById('vv-au-acl-loading');
  const tbl     = document.getElementById('vv-au-acl-tbl');
  const empty   = document.getElementById('vv-au-acl-empty');
  loading.style.display = 'block';
  tbl.style.display = 'none';
  empty.style.display = 'none';

  _get('authelia_rules', r => {
    loading.style.display = 'none';
    if (!r.ok) { loading.innerHTML = '<span style="color:#ef5350;padding:10px;display:block">'+_esc(r.error)+'</span>'; loading.style.display='block'; return; }
    _rules         = r.rules || [];
    _defaultPolicy = r.default_policy || 'deny';

    const sel = document.getElementById('vv-au-ac-defpol');
    if (sel) sel.value = _defaultPolicy;

    if (!_rules.length) { empty.style.display = 'block'; return; }
    tbl.style.display = 'table';
    _renderAcl();
  });
}

function _renderAcl() {
  const count = document.getElementById('vv-au-acl-count');
  count.textContent = _rules.length + ' rule' + (_rules.length !== 1 ? 's' : '');

  const body = document.getElementById('vv-au-acl-body');
  body.innerHTML = _rules.map((rule, i) => {
    const domains  = _normDomain(rule.domain).join(', ');
    const subjects = _normSubject(rule.subject).join(', ');
    const upBtn    = i === 0 ? '<span style="width:18px;display:inline-block"></span>' :
      `<button class="vv-au-icon-btn" data-rule-up="${i}" title="Move up">↑</button>`;
    const dnBtn    = i === _rules.length-1 ? '<span style="width:18px;display:inline-block"></span>' :
      `<button class="vv-au-icon-btn" data-rule-dn="${i}" title="Move down">↓</button>`;
    return `<tr>
      <td class="vv-au-rule-num">${i+1}</td>
      <td><div class="vv-au-domain">${_esc(domains)}</div></td>
      <td>${_policyBadge(rule.policy)}</td>
      <td style="font-size:10px;color:#555">${_esc(subjects||'—')}</td>
      <td>
        <div class="vv-au-rule-acts">
          ${upBtn}${dnBtn}
          <button class="vv-au-icon-btn" data-rule-edit="${i}" title="Edit">✎</button>
          <button class="vv-au-icon-btn del" data-rule-del="${i}" title="Delete">✕</button>
        </div>
      </td>
    </tr>`;
  }).join('');
}

function _ruleModal(idx) {
  const rule = idx !== null ? _rules[idx] : null;
  const domains  = _normDomain(rule?.domain).join(', ');
  const subjects = _normSubject(rule?.subject).join(', ');
  const nets     = (rule?.networks||[]).join(', ');
  const resources= (rule?.resources||[]).join(', ');

  const policyOpts = ['bypass','one_factor','two_factor','deny'].map(p =>
    `<option value="${p}"${(rule?.policy||'two_factor')===p?' selected':''}>${p}</option>`
  ).join('');

  _modal(`<h3>${rule ? 'Edit Rule' : 'Add Rule'}</h3>
    <div class="vv-au-field">
      <label class="vv-au-label">Domain</label>
      <input class="vv-au-input" id="rm-domain" value="${_esc(domains)}" placeholder="*.example.com, example.com">
      <div class="vv-au-hint">Comma-separated; wildcards supported</div>
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">Policy</label>
      <select class="vv-au-select" id="rm-policy">${policyOpts}</select>
    </div>
    <div class="vv-au-field">
      <label class="vv-au-label">Subject <span style="color:#2a2a2a">(optional)</span></label>
      <input class="vv-au-input" id="rm-subject" value="${_esc(subjects)}" placeholder="group:admins, user:john">
      <div class="vv-au-hint">Comma-separated; prefix with group: or user:</div>
    </div>
    <div class="vv-au-adv-toggle" id="rm-adv-toggle">▶ Advanced (networks, resources)</div>
    <div class="vv-au-adv-section" id="rm-adv">
      <div class="vv-au-field">
        <label class="vv-au-label">Networks <span style="color:#2a2a2a">(optional)</span></label>
        <input class="vv-au-input" id="rm-networks" value="${_esc(nets)}" placeholder="192.168.1.0/24, 10.0.0.0/8">
        <div class="vv-au-hint">Comma-separated CIDR ranges</div>
      </div>
      <div class="vv-au-field">
        <label class="vv-au-label">Resources <span style="color:#2a2a2a">(optional)</span></label>
        <input class="vv-au-input" id="rm-resources" value="${_esc(resources)}" placeholder="^/api, ^/admin">
        <div class="vv-au-hint">Comma-separated regex patterns</div>
      </div>
    </div>
    <div class="vv-au-err" id="rm-err"></div>
    <div class="vv-au-modal-acts">
      <button class="vv-au-btn" id="rm-cancel">Cancel</button>
      <button class="vv-au-btn prim" id="rm-save">${rule ? 'Save' : 'Add Rule'}</button>
    </div>`);

  document.getElementById('rm-adv-toggle').addEventListener('click', () => {
    const sec = document.getElementById('rm-adv');
    const tog = document.getElementById('rm-adv-toggle');
    const open = sec.classList.toggle('open');
    tog.textContent = (open ? '▼' : '▶') + ' Advanced (networks, resources)';
  });

  document.getElementById('rm-cancel').onclick = _closeModal;
  document.getElementById('rm-save').onclick = () => {
    const domains = document.getElementById('rm-domain').value.split(',').map(s=>s.trim()).filter(Boolean);
    if (!domains.length) { _showModalErr('rm-err','Domain required'); return; }

    const subjects  = document.getElementById('rm-subject').value.split(',').map(s=>s.trim()).filter(Boolean);
    const networks  = document.getElementById('rm-networks').value.split(',').map(s=>s.trim()).filter(Boolean);
    const resources = document.getElementById('rm-resources').value.split(',').map(s=>s.trim()).filter(Boolean);

    const newRule = {
      domain: domains.length === 1 ? domains[0] : domains,
      policy: document.getElementById('rm-policy').value,
    };
    if (subjects.length)  newRule.subject   = subjects.length === 1 ? subjects[0]  : subjects;
    if (networks.length)  newRule.networks  = networks;
    if (resources.length) newRule.resources = resources;

    if (idx !== null) _rules[idx] = newRule;
    else              _rules.push(newRule);

    _closeModal();
    _renderAcl();
  };
}

// ACL event delegation
document.getElementById('vv-au-panel-acl').addEventListener('click', e => {
  if (e.target.id === 'vv-au-rule-add') { _ruleModal(null); return; }

  if (e.target.id === 'vv-au-ac-save') {
    const dp  = document.getElementById('vv-au-ac-defpol').value;
    const btn = document.getElementById('vv-au-ac-save');
    btn.disabled = true; btn.textContent = 'Saving…';
    _post({ action:'authelia_save', rules: JSON.stringify(_rules), default_policy: dp }, r => {
      btn.disabled = false; btn.textContent = 'Save & Restart Authelia';
      if (!r.ok) { alert('Save failed: ' + (r.error||'unknown error')); return; }
      // brief visual confirmation
      btn.textContent = 'Saved ✓';
      setTimeout(() => { btn.textContent = 'Save & Restart Authelia'; }, 2000);
    });
    return;
  }

  const editBtn = e.target.closest('[data-rule-edit]');
  if (editBtn) { _ruleModal(parseInt(editBtn.dataset.ruleEdit)); return; }

  const delBtn = e.target.closest('[data-rule-del]');
  if (delBtn) {
    const i = parseInt(delBtn.dataset.ruleDel);
    if (!confirm('Delete this rule?')) return;
    _rules.splice(i, 1);
    _renderAcl();
    return;
  }

  const upBtn = e.target.closest('[data-rule-up]');
  if (upBtn) {
    const i = parseInt(upBtn.dataset.ruleUp);
    if (i > 0) { [_rules[i-1], _rules[i]] = [_rules[i], _rules[i-1]]; _renderAcl(); }
    return;
  }

  const dnBtn = e.target.closest('[data-rule-dn]');
  if (dnBtn) {
    const i = parseInt(dnBtn.dataset.ruleDn);
    if (i < _rules.length-1) { [_rules[i], _rules[i+1]] = [_rules[i+1], _rules[i]]; _renderAcl(); }
  }
});

// ── Close modal on overlay click ──────────────────────────────────────────────
document.getElementById('vv-au-overlay').addEventListener('click', e => {
  if (e.target === document.getElementById('vv-au-overlay')) _closeModal();
});

// ── Certs ─────────────────────────────────────────────────────────────────────
function _certBadgeCls(s) {
  return ({OK:'ssl', WARN:'one_factor', CRIT:'deny', FAIL:'deny'})[s] || 'nossl';
}
function _certBadgeTxt(s) {
  return ({OK:'healthy', WARN:'warning', CRIT:'critical', FAIL:'failed', UNKN:'not checked'})[s] || s;
}
function _certDayColor(days, warn, crit) {
  if (days == null) return '#3a3a3a';
  return days <= crit ? '#ef5350' : days <= warn ? '#ffb74d' : '#4caf50';
}
function _certRel(ts) {
  if (!ts) return '—';
  const d = Math.floor(Date.now()/1000) - ts;
  if (d < 60)    return 'just now';
  if (d < 3600)  return Math.floor(d/60) + 'm ago';
  if (d < 86400) return Math.floor(d/3600) + 'h ago';
  return Math.floor(d/86400) + 'd ago';
}

function _renderNpmCerts(data) {
  const grid = document.getElementById('vv-au-cert-grid');
  const ts   = document.getElementById('vv-au-cert-ts');
  const cfg  = document.getElementById('vv-au-cert-cfg');
  const warn = data.warn_days || 30, crit = data.crit_days || 7;
  const certs = data.certs || [];

  ts.textContent  = certs.length + ' certificate' + (certs.length !== 1 ? 's' : '') + ' · live from NPM';
  cfg.textContent = `Warn: ${warn}d · Crit: ${crit}d`;

  if (!certs.length) {
    grid.innerHTML = '<div class="vv-au-empty">No certificates found in NPM</div>';
    return;
  }

  grid.innerHTML = certs.map(c => {
    const s      = c.status || 'UNKN';
    const days   = c.days;
    const col    = _certDayColor(days, warn, crit);
    const barPct = days != null ? Math.min(Math.round(days / 90 * 100), 100) : 0;
    const expStr = c.expires ? 'Expires ' + c.expires : '';
    const extra  = (c.domain_names || []).filter(d => d !== c.nice_name).join(', ');
    return `<div class="vv-au-card" style="padding:12px 14px;">
      <div class="vv-au-domain">${_esc(c.nice_name)}</div>
      ${extra ? `<div style="font-size:9px;color:#444;margin-bottom:2px;word-break:break-all;">${_esc(extra)}</div>` : ''}
      <div class="vv-au-cert-days" style="color:${col}">${days != null ? days : '—'}</div>
      <div style="font-size:9px;color:#444;margin-bottom:6px;">${days != null ? 'days remaining' : ''}</div>
      <span class="vv-au-badge ${_certBadgeCls(s)}">${_certBadgeTxt(s)}</span>
      <div style="font-size:9px;color:#3a3a3a;margin-top:5px;">${_esc(expStr)}</div>
      ${days != null ? `<div class="vv-au-cert-bar"><div class="vv-au-cert-fill" style="width:${barPct}%;background:${col};"></div></div>` : ''}
    </div>`;
  }).join('');
}

function _loadCerts(onDone) {
  const grid = document.getElementById('vv-au-cert-grid');
  grid.innerHTML = '<div class="vv-au-loading">Loading…</div>';
  fetch(CERT_API + '?action=npm')
    .then(r => r.json())
    .then(d => {
      if (d.ok) _renderNpmCerts(d);
      else throw new Error(d.error || 'NPM error');
      if (onDone) onDone();
    })
    .catch(e => {
      grid.innerHTML = `<div class="vv-au-empty" style="color:#ef5350">${_esc(e.message || 'Failed to load')}</div>`;
      if (onDone) onDone();
    });
}

document.getElementById('vv-au-cert-run').addEventListener('click', function() {
  const btn = this;
  btn.disabled = true; btn.textContent = 'Loading…';
  _loadCerts(() => { btn.disabled = false; btn.textContent = 'Refresh'; });
});

// ── Boot ──────────────────────────────────────────────────────────────────────
_loadProxies();

})();
</script>
