const extraState = { app: false, all: false };
let selectedUser = null;
let manualBackupJobId = null;
let manualBackupPoll = null;
let currentJobId = null;
let pollInterval = null;
let allContainersCache = [];
let allContainersStats = {};

function el(id) { return document.getElementById(id); }

function escapeHtml(s) {
  if (s == null) return '';
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function auditScoreBlockTierClass(tier) {
  const t = String(tier || 'fair');
  if (t === 'excellent' || t === 'good') return 'audit-tier-good';
  if (t === 'fair') return 'audit-tier-fair';
  if (t === 'poor') return 'audit-tier-poor';
  return 'audit-tier-critical';
}

function renderAuditModalContent(d, filename) {
  if (d.error) {
    return `<div class="empty-state" style="color:var(--red)">${escapeHtml(d.error)}</div>
<div class="audit-footer"><button type="button" class="btn btn-ghost btn-sm" onclick="closeAuditModal()">Close</button></div>`;
  }

  const tierCls = auditScoreBlockTierClass(d.health_tier);
  const score = Math.max(0, Math.min(100, Number(d.score) || 0));
  const fileSize = d.file_size_display
    ? escapeHtml(d.file_size_display)
    : (d.file_size_bytes != null ? escapeHtml(String(d.file_size_bytes)) + ' B' : '—');
  const fname = escapeHtml(d.backup_file || filename);
  const summary = escapeHtml(d.summary || '');
  const healthLabel = escapeHtml(d.health_label || '');
  const badgeSafe = !!d.ok;
  const badgeText = badgeSafe ? '✓ Safe to restore' : '✗ Review before restore';
  const badgeClass = badgeSafe ? 'audit-health-badge audit-health-badge-safe' : 'audit-health-badge audit-health-badge-unsafe';

  const checksHtml = (d.checks || []).map((c) => {
    const st = c.status === 'fail' ? 'fail' : c.status === 'warn' ? 'warn' : 'pass';
    const icon = st === 'pass' ? '✓' : st === 'warn' ? '⚠' : '✗';
    const iconCls = `audit-check-icon audit-check-icon-${st}`;
    const detail = escapeHtml(c.detail || '');
    const moreLines = Array.isArray(c.more) ? c.more : [];
    const moreBlock = moreLines.length
      ? `<ul class="audit-check-more">${moreLines.map((line) => `<li>${escapeHtml(line)}</li>`).join('')}</ul>`
      : '';

    return `<details class="audit-check-row status-${st}">
<summary class="audit-check-summary">
<span class="${iconCls}">${icon}</span>
<span class="audit-check-name">${escapeHtml(c.name)}</span>
<span class="audit-check-pill audit-check-pill-${st}">${st}</span>
<span class="audit-check-chevron" aria-hidden="true"></span>
</summary>
<div class="audit-check-body">
<p class="audit-check-detail">${detail}</p>
${moreBlock}
</div>
</details>`;
  }).join('');

  return `<div class="audit-file-label">File</div>
<code class="audit-filename">${fname}</code>
<div class="audit-file-meta"><span class="audit-file-meta-label">Size</span><span class="audit-file-meta-value">${fileSize}</span></div>
<div class="audit-score-block ${tierCls}">
<div class="audit-score-numcol">
<div class="audit-score-number">${score}</div>
<div class="audit-score-label">Score</div>
</div>
<div class="audit-score-info">
<div class="audit-health-row">
<span class="audit-health-label">${healthLabel}</span>
<span class="${badgeClass}">${badgeText}</span>
</div>
<p class="audit-summary">${summary}</p>
<div class="audit-progress" role="progressbar" aria-valuenow="${score}" aria-valuemin="0" aria-valuemax="100"><div class="audit-progress-fill ${tierCls}" style="width:${score}%"></div></div>
</div>
</div>
<p class="audit-checks-hint">Expand a row to see exactly what was verified (size, checksum, archive test, paths, …).</p>
<div class="audit-checks">${checksHtml}</div>
<div class="audit-footer">
<button type="button" class="btn btn-ghost btn-sm" onclick="closeAuditModal()">Close</button>
<button type="button" class="btn btn-primary btn-sm" onclick="quickRestoreFromAuditModal()"><i class="fas fa-rotate-left"></i> Restore this backup</button>
</div>`;
}

function quickRestoreFromAuditModal() {
  const modal = el('audit-modal');
  if (!modal) return;
  const s = modal.dataset.auditSource || '';
  const f = modal.dataset.auditFile || '';
  modal.style.display = 'none';
  if (s && f) quickRestore(s, f);
}

function setText(id, value) {
  const node = el(id);
  if (node) node.textContent = value;
}

function toggleExtraColumns(prefix) {
  extraState[prefix] = !extraState[prefix];
  const show = extraState[prefix];
  document.querySelectorAll(`.${prefix}-extra`).forEach((node) => {
    node.style.display = show ? "" : "none";
  });
  const btn = el(`${prefix}-toggle-btn`);
  if (btn) {
    btn.innerHTML = show
      ? '<i class="fas fa-eye-slash"></i> Show less'
      : '<i class="fas fa-eye"></i> Show more';
  }
}

/** Sidebar counts (present in base.html on every page). */
async function refreshSidebarNavBadges() {
  const setBadges = (containerCount, userCount) => {
    const c = el('nav-badge-containers');
    const u = el('nav-badge-users');
    if (c) c.textContent = containerCount != null ? containerCount : '—';
    if (u) u.textContent = userCount != null ? userCount : '—';
  };
  try {
    const r = await fetch('/api/nav-summary', { credentials: 'same-origin' });
    const d = r.ok ? await r.json() : null;
    const cc = d && Number.isFinite(Number(d.container_count)) ? Number(d.container_count) : null;
    const uc = d && Number.isFinite(Number(d.user_count)) ? Number(d.user_count) : null;
    setBadges(cc, uc);
  } catch (_) {
    setBadges(null, null);
  }
}

async function checkServerStatus() {
  try {
    const r = await fetch('/server/status');
    const d = await r.json();
    const dot = el('pulse-dot');
    const text = el('server-status-text');
    if (!dot || !text) return;
    if (d.status === 'online') {
      dot.className = 'pulse-dot online';
      text.textContent = 'Online';
    } else {
      dot.className = 'pulse-dot offline';
      text.textContent = 'Offline';
    }
  } catch (_) {}
}

async function refreshSystemMetrics() {
  try {
    const r = await fetch('/api/system');
    const d = await r.json();
    if (el('m-cpu')) el('m-cpu').innerHTML = d.cpu_pct + '<span>%</span>';
    setText('m-mem', d.memory);
    setText('m-disk', d.disk);
    setText('m-load', d.load);
    if (el('g-cpu')) el('g-cpu').style.width = Math.min(parseFloat(d.cpu_pct) || 0, 100) + '%';
    if (el('g-mem')) el('g-mem').style.width = Math.min(parseFloat(d.mem_pct) || 0, 100) + '%';
    if (el('g-disk')) el('g-disk').style.width = Math.min(parseFloat(d.disk_pct) || 0, 100) + '%';
    setText('uptime-chip', d.uptime);
    if (el('settings-uptime')) el('settings-uptime').value = d.uptime;
    if (d.hostname && el('this-server-desc')) el('this-server-desc').textContent = d.hostname;
  } catch (_) {}
}

async function refreshContainerStats() {
  try {
    const r = await fetch('/api/stats');
    const stats = await r.json();
    document.querySelectorAll('[data-stat]').forEach((node) => {
      const name = node.dataset.ctr;
      const stat = node.dataset.stat;
      const s = stats[name];
      if (!s) return;
      if (stat === 'cpu') node.textContent = s.cpu || '—';
      if (stat === 'net') node.textContent = s.net || '—';
      if (stat === 'block') node.textContent = s.block || '—';
      if (stat === 'mem_pct') node.textContent = s.mem_pct || '—';
      if (stat === 'mem_bar') {
        const pct = parseFloat(s.mem_pct) || 0;
        node.style.width = Math.min(pct, 100) + '%';
        node.className = 'stat-bar-fill' + (pct > 85 ? ' crit' : pct > 65 ? ' warn' : '');
      }
    });
  } catch (_) {}
}

function statusBadgeHTML(status) {
  if (status === 'running') return '<span class="badge badge-run">Running</span>';
  if (status === 'stopped') return '<span class="badge badge-stop">Stopped</span>';
  return '<span class="badge" style="background:rgba(107,114,128,0.15);color:var(--text2)">Unknown</span>';
}

function updateContainerStatusBadge(name, status) {
  document.querySelectorAll(`.ctr-status-cell[data-ctr="${name}"]`).forEach((cell) => {
    cell.innerHTML = statusBadgeHTML(status);
  });
  document.querySelectorAll(`#all-containers-body tr[data-ctr="${name}"] .ctr-status-cell`).forEach((cell) => {
    cell.innerHTML = statusBadgeHTML(status);
  });
}

async function ctrAction(name, action, btn) {
  const origHTML = btn.innerHTML;
  btn.disabled = true;
  btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
  try {
    const r = await fetch('/api/container/action', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, action })
    });
    const d = await r.json();
    if (d.new_status) updateContainerStatusBadge(name, d.new_status);
    btn.innerHTML = d.success ? '<i class="fas fa-check"></i>' : '<i class="fas fa-times"></i>';
    setTimeout(() => {
      btn.innerHTML = origHTML;
      btn.disabled = false;
      setTimeout(() => {
        refreshContainerStats();
        if (el('all-containers-body')) loadAllContainers();
      }, 1500);
    }, 1200);
  } catch (_) {
    btn.innerHTML = origHTML;
    btn.disabled = false;
  }
}

function buildActionBtns(name) {
  return `<div class="action-btns">
    <button class="ctr-action-btn restart" title="Restart" onclick="ctrAction('${name}','restart',this)"><i class="fas fa-rotate-right"></i></button>
    <button class="ctr-action-btn stop" title="Stop" onclick="ctrAction('${name}','stop',this)"><i class="fas fa-stop"></i></button>
    <button class="ctr-action-btn start" title="Start" onclick="ctrAction('${name}','start',this)"><i class="fas fa-play"></i></button>
  </div>`;
}

function parseCpuPct(cpuStr) {
  if (!cpuStr || cpuStr === '—') return 0;
  return parseFloat(String(cpuStr).replace('%', '')) || 0;
}

function parseCreatedTs(createdAt) {
  if (!createdAt) return 0;
  const t = Date.parse(createdAt);
  return Number.isNaN(t) ? 0 : t;
}

function parseUptimeSeconds(uptimeText) {
  if (!uptimeText || !uptimeText.startsWith('Up')) return 0;
  const text = uptimeText.replace(/^Up\s+/i, '').toLowerCase();
  let sec = 0;
  const dayM = text.match(/(\d+)\s*day/);
  const hrM = text.match(/(\d+)\s*hour/);
  const minM = text.match(/(\d+)\s*minute/);
  const secM = text.match(/(\d+)\s*second/);
  if (dayM) sec += parseInt(dayM[1], 10) * 86400;
  if (hrM) sec += parseInt(hrM[1], 10) * 3600;
  if (minM) sec += parseInt(minM[1], 10) * 60;
  if (secM) sec += parseInt(secM[1], 10);
  return sec;
}

function filterAndSortContainers(containers, stats, query, sortBy) {
  let list = [...containers];
  const q = (query || '').trim().toLowerCase();
  if (q) {
    list = list.filter((c) =>
      (c.name || '').toLowerCase().includes(q) ||
      (c.image || '').toLowerCase().includes(q) ||
      (c.owner || '').toLowerCase().includes(q) ||
      (c.ports || '').toLowerCase().includes(q) ||
      (c.uptime || '').toLowerCase().includes(q)
    );
  }
  list.sort((a, b) => {
    switch (sortBy) {
      case 'cpu_desc':
        return parseCpuPct((stats[b.name] || {}).cpu) - parseCpuPct((stats[a.name] || {}).cpu);
      case 'cpu_asc':
        return parseCpuPct((stats[a.name] || {}).cpu) - parseCpuPct((stats[b.name] || {}).cpu);
      case 'created_desc':
        return parseCreatedTs(b.created_at) - parseCreatedTs(a.created_at);
      case 'created_asc':
        return parseCreatedTs(a.created_at) - parseCreatedTs(b.created_at);
      case 'uptime_desc':
        return parseUptimeSeconds(b.uptime) - parseUptimeSeconds(a.uptime);
      case 'name':
      default:
        return (a.name || '').localeCompare(b.name || '');
    }
  });
  return list;
}

function renderAllContainersRows(containers, stats) {
  const body = el('all-containers-body');
  if (!body) return;
  const filterMeta = el('ctr-filter-meta');
  const total = allContainersCache.length;
  if (filterMeta) {
    filterMeta.textContent = containers.length === total
      ? `${total} shown`
      : `${containers.length} of ${total} shown`;
  }
  if (!containers.length) {
    body.innerHTML = `<tr><td colspan="11"><div class="empty-state"><i class="fas fa-inbox"></i>${total ? 'No matches' : 'No containers'}</div></td></tr>`;
    return;
  }
  const showExtra = extraState.all;
  const ed = showExtra ? '' : 'display:none;';
  body.innerHTML = containers.map((c) => {
    const up = c.running || (c.status && c.status.includes('Up'));
    const s = stats[c.name] || {};
    const pct = parseFloat(s.mem_pct) || 0;
    const cls = pct > 85 ? 'crit' : pct > 65 ? 'warn' : '';
    const uptimeCls = up ? 'ct-uptime' : 'ct-uptime stopped';
    const uptimeText = c.uptime || (up ? c.status : c.status || '—');
    return `<tr data-ctr="${escapeHtml(c.name)}">
      <td class="ct-name">${escapeHtml(c.name)}</td>
      <td class="ct-owner">${escapeHtml(c.owner || 'root')}</td>
      <td class="ctr-status-cell" data-ctr="${escapeHtml(c.name)}">${up ? '<span class="badge badge-run">Running</span>' : '<span class="badge badge-stop">Stopped</span>'}</td>
      <td><span class="${uptimeCls}">${escapeHtml(uptimeText)}</span></td>
      <td><span class="stat-pct">${escapeHtml(s.cpu || '—')}</span></td>
      <td><div class="stat-bar-wrap"><div class="stat-bar-bg"><div class="stat-bar-fill ${cls}" style="width:${Math.min(pct, 100)}%"></div></div><span class="stat-pct">${escapeHtml(s.mem_pct || '—')}</span></div></td>
      <td><span class="stat-pct" style="color:var(--cyan)">${escapeHtml(s.net || '—')}</span></td>
      <td class="col-extra all-extra" style="${ed}"><span class="stat-pct" style="color:var(--yellow)">${escapeHtml(s.block || '—')}</span></td>
      <td class="col-extra all-extra ct-image" style="${ed}">${escapeHtml(c.image)}</td>
      <td class="col-extra all-extra ct-ports" style="${ed}">${escapeHtml(c.ports || '—')}</td>
      <td>${buildActionBtns(c.name)}</td></tr>`;
  }).join('');
}

function applyContainersFilter() {
  const query = el('ctr-search')?.value || '';
  const sortBy = el('ctr-sort')?.value || 'name';
  const filtered = filterAndSortContainers(allContainersCache, allContainersStats, query, sortBy);
  renderAllContainersRows(filtered, allContainersStats);
}

async function loadAllContainers() {
  const body = el('all-containers-body');
  if (!body) return;
  const meta = el('all-ctr-meta');
  body.innerHTML = '<tr><td colspan="11"><div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Loading…</div></td></tr>';
  try {
    const [ctrRes, statRes] = await Promise.all([fetch('/api/containers/all'), fetch('/api/stats')]);
    const { containers, running } = await ctrRes.json();
    allContainersCache = containers || [];
    allContainersStats = await statRes.json();
    if (meta) meta.textContent = `${allContainersCache.length} total · ${running} running`;
    if (el('nav-badge-containers')) el('nav-badge-containers').textContent = allContainersCache.length;
    applyContainersFilter();
  } catch (e) {
    body.innerHTML = `<tr><td colspan="11"><div class="empty-state">${escapeHtml(String(e))}</div></td></tr>`;
  }
}

window.applyContainersFilter = applyContainersFilter;

async function refreshBackupsList() {
  try {
    const r = await fetch('/api/backups');
    const d = await r.json();
    renderBackupList(d.local, 'local-backup-list', 'local');
    renderBackupList(d.vm, 'vm-backup-list', 'vm');
    if (el('stat-local-bk')) el('stat-local-bk').textContent = d.local.length;
    if (el('stat-vm-bk')) el('stat-vm-bk').textContent = d.vm.length;
    if (el('local-options')) el('local-options').innerHTML = d.local.length ? d.local.map((b) => `<option value="${b}" data-source="local">${b}</option>`).join('') : '<option disabled>No local backups</option>';
    if (el('vm-options')) el('vm-options').innerHTML = d.vm.length ? d.vm.map((b) => `<option value="${b}" data-source="vm">${b}</option>`).join('') : '<option disabled>No VM backups</option>';
  } catch (_) {}
}

function renderBackupList(items, id, source) {
  const node = el(id);
  if (!node) return;
  if (!items || !items.length) {
    node.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i>No backups</div>';
    return;
  }
  node.innerHTML = items.map((b) => `
    <div class="backup-item" id="bk-item-${source}-${b.replace(/[^a-z0-9]/gi,'_')}">
      <span class="backup-name">${b}</span>
      <div class="backup-actions">
        <button class="btn btn-ghost btn-sm btn-audit" onclick="auditBackup('${source}','${b}',this)"><i class="fas fa-shield-check"></i> Audit</button>
        <button class="btn btn-ghost btn-sm" onclick="quickRestore('${source}','${b}')">↩ Restore</button>
        <button class="btn btn-ghost btn-sm btn-delete-backup" onclick="deleteBackup('${source}','${b}',this)"><i class="fas fa-trash"></i></button>
      </div>
    </div>`).join('');
}

async function auditBackup(source, filename, btn) {
  const modal = el('audit-modal');
  const content = el('audit-modal-content');
  if (!modal || !content) return;
  modal.dataset.auditSource = source;
  modal.dataset.auditFile = filename;
  modal.style.display = 'flex';
  content.innerHTML = '<div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Running audit…</div>';
  if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; }
  try {
    const r = await fetch('/api/backups/audit', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ backup_file: filename, source })
    });
    const d = await r.json();
    if (!r.ok) {
      content.innerHTML = renderAuditModalContent({ error: d.error || d.message || `HTTP ${r.status}` }, filename);
      return;
    }
    content.innerHTML = renderAuditModalContent(d, filename);
  } catch (e) {
    content.innerHTML = `<div class="empty-state" style="color:var(--red)">Audit failed: ${escapeHtml(e)}</div>
<div class="audit-footer"><button type="button" class="btn btn-ghost btn-sm" onclick="closeAuditModal()">Close</button></div>`;
  } finally {
    if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-shield-check"></i> Audit'; }
  }
}

function closeAuditModal(e) {
  const modal = el('audit-modal');
  if (!modal) return;
  if (!e || e.target === modal) modal.style.display = 'none';
}

async function deleteBackup(source, filename, btn) {
  if (!confirm(`Delete backup:\n${filename}\n\nThis cannot be undone.`)) return;
  if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; }
  try {
    const r = await fetch('/api/backups/delete', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ backup_file: filename, source })
    });
    const d = await r.json();
    if (!d.success) alert(`Delete failed: ${d.message}`);
    refreshBackupsList();
  } catch (e) {
    alert(`Error: ${e}`);
  } finally {
    if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-trash"></i>'; }
  }
}

async function runManualBackup() {
  const btn = el('manual-backup-btn');
  if (!btn) return;
  btn.disabled = true;
  btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting…';
  const wrapper = el('manual-backup-wrapper');
  const logEl = el('manual-backup-log');
  if (wrapper) wrapper.style.display = '';
  if (logEl) logEl.innerHTML = '';
  try {
    const r = await fetch('/api/backups/run', { method: 'POST' });
    const d = await r.json();
    if (!d.success) throw new Error(d.message || 'Failed');
    manualBackupJobId = d.job_id;
    pollManualBackup();
  } catch (e) {
    if (logEl) logEl.textContent = `❌ ${e}`;
    btn.disabled = false;
    btn.innerHTML = '<i class="fas fa-play"></i> Run Backup Now';
  }
}

function pollManualBackup() {
  if (manualBackupPoll) clearInterval(manualBackupPoll);
  let lastLine = 0;
  manualBackupPoll = setInterval(async () => {
    if (!manualBackupJobId) return;
    try {
      const r = await fetch(`/api/backups/run/status/${manualBackupJobId}`);
      const d = await r.json();
      d.log.slice(lastLine).forEach(appendBackupLog);
      lastLine = d.log.length;
      if (el('manual-backup-elapsed')) el('manual-backup-elapsed').textContent = `⏱ ${d.elapsed}s`;
      if (d.status !== 'running') {
        clearInterval(manualBackupPoll);
        if (el('manual-backup-btn')) {
          el('manual-backup-btn').disabled = false;
          el('manual-backup-btn').innerHTML = '<i class="fas fa-play"></i> Run Backup Now';
        }
        if (d.status === 'done') setTimeout(refreshBackupsList, 1200);
      }
    } catch (_) {}
  }, 1500);
}

function appendBackupLog(line) {
  const logEl = el('manual-backup-log');
  if (!logEl) return;
  const div = document.createElement('div');
  div.className = 'log-line';
  div.textContent = line;
  logEl.appendChild(div);
  logEl.scrollTop = logEl.scrollHeight;
}

async function loadBackupLog() {
  const node = el('backup-history-list');
  if (!node) return;
  node.innerHTML = '<div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Loading…</div>';
  try {
    const r = await fetch('/api/backups/log?limit=15');
    const d = await r.json();
    if (!d.entries || !d.entries.length) {
      node.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i>No backup history yet</div>';
      return;
    }
    node.innerHTML = d.entries.map((e) => `<div class="backup-item"><span class="backup-name">${e.name}</span><span class="card-meta">${e.status}</span></div>`).join('');
  } catch (e) {
    node.innerHTML = `<div class="empty-state">Error loading log: ${e}</div>`;
  }
}

const userColors = ['#3b82f6', '#a78bfa', '#22c55e', '#f59e0b', '#ef4444', '#22d3ee'];
async function loadUsers() {
  const grid = el('users-grid');
  if (!grid) return;
  try {
    const r = await fetch('/api/users');
    const users = await r.json();
    if (el('nav-badge-users')) el('nav-badge-users').textContent = users.length;
    if (el('stat-users')) el('stat-users').textContent = users.length;
    if (!users.length) {
      grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><i class="fas fa-user-slash"></i>No users</div>';
      return;
    }
    grid.innerHTML = users.map((u, i) => `<div class="user-card" onclick="loadUserContainers('${u.name}')"><div class="user-card-top"><div class="user-avatar" style="background:${userColors[i % userColors.length]}">${u.name[0].toUpperCase()}</div><div><div class="user-name">${u.name}</div><div class="user-uid">uid ${u.uid}</div></div></div><div class="user-tags">${u.has_docker ? '<span class="user-tag docker"><i class="fab fa-docker"></i> docker</span>' : ''}${u.linger ? '<span class="user-tag linger">linger</span>' : ''}${u.has_vdisk ? '<span class="user-tag" style="background:rgba(34,211,238,0.12);color:var(--cyan)">💾 vdisk</span>' : ''}</div><div class="user-stats"><div class="user-stat">Disk: <strong>${u.disk_used}</strong></div><div class="user-stat">Ctrs: <strong>${u.container_count}</strong></div></div></div>`).join('');
  } catch (_) {}
}

async function loadUserContainers(username) {
  selectedUser = username;
  if (el('user-detail-panel')) el('user-detail-panel').style.display = '';
  setText('user-detail-title', `${username}'s Containers`);
  const body = el('user-containers-body');
  if (!body) return;
  body.innerHTML = '<tr><td colspan="4"><div class="empty-state"><i class="fas fa-spinner fa-spin"></i></div></td></tr>';
  try {
    const r = await fetch(`/api/users/${username}/containers`);
    const ctrs = await r.json();
    if (!ctrs.length) {
      body.innerHTML = '<tr><td colspan="4"><div class="empty-state"><i class="fas fa-inbox"></i>No containers</div></td></tr>';
      return;
    }
    body.innerHTML = ctrs.map((c) => `<tr><td class="ct-name">${c.name}</td><td>${c.status.includes('Up') ? '<span class="badge badge-run">Running</span>' : '<span class="badge badge-stop">Stopped</span>'}</td><td class="ct-image">${c.image}</td><td class="ct-ports">${c.ports || '—'}</td></tr>`).join('');
  } catch (_) {}
}

function showAlert(id, type, msg) {
  const node = el(id);
  if (!node) return;
  node.className = `alert alert-${type} show`;
  node.textContent = msg;
}

async function createUser() {
  const username = el('new-username')?.value.trim();
  const password = el('new-password')?.value.trim();
  const quota = el('new-quota')?.value.trim();
  const docker = !!el('new-docker')?.checked;
  if (!username) return showAlert('create-user-result', 'error', 'Username required');
  const btn = event.target;
  btn.disabled = true;
  try {
    const r = await fetch('/api/users/create', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password: password || null, setup_docker: docker, disk_quota_mb: quota ? parseInt(quota, 10) : null })
    });
    const d = await r.json();
    showAlert('create-user-result', d.success ? 'success' : 'error', d.message || '');
    if (d.success) loadUsers();
  } catch (e) {
    showAlert('create-user-result', 'error', `${e}`);
  } finally {
    btn.disabled = false;
  }
}

async function deleteUser() {
  if (!selectedUser || !confirm(`Delete user "${selectedUser}"?`)) return;
  const removeHome = confirm(`Also delete /home/${selectedUser}?`);
  try {
    const r = await fetch('/api/users/delete', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: selectedUser, remove_home: removeHome })
    });
    const d = await r.json();
    showAlert('user-action-result', d.success ? 'success' : 'error', d.message || '');
    if (d.success) {
      if (el('user-detail-panel')) el('user-detail-panel').style.display = 'none';
      loadUsers();
    }
  } catch (e) {
    showAlert('user-action-result', 'error', `${e}`);
  }
}

function updateBackupList() {
  const src = document.querySelector('input[name="backup_source"]:checked')?.value;
  if (!src) return;
  if (el('local-options')) el('local-options').style.display = src === 'local' ? '' : 'none';
  if (el('vm-options')) el('vm-options').style.display = src === 'vm' ? '' : 'none';
}
function toggleRemoteFields() {
  const v = document.querySelector('input[name="restore_target"]:checked')?.value;
  if (el('remote-fields')) el('remote-fields').style.display = v === 'remote' ? '' : 'none';
}
function toggleAuthFields() {
  const m = document.querySelector('input[name="auth_method"]:checked')?.value;
  if (el('key-field')) el('key-field').style.display = m === 'key' ? '' : 'none';
  if (el('password-field')) el('password-field').style.display = m === 'password' ? '' : 'none';
}

function quickRestore(source, filename) {
  window.location.href = `/restore?source=${encodeURIComponent(source)}&file=${encodeURIComponent(filename)}`;
}

function appendLog(line) {
  const logEl = el('restore-log');
  if (!logEl) return;
  const div = document.createElement('div');
  div.className = 'log-line';
  div.textContent = line;
  logEl.appendChild(div);
  logEl.scrollTop = logEl.scrollHeight;
}

async function launchRestore() {
  const src = document.querySelector('input[name="backup_source"]:checked')?.value;
  const file = el('backup-file-select')?.value;
  const target = document.querySelector('input[name="restore_target"]:checked')?.value;
  if (!file) return alert('Select a backup file.');
  const payload = { backup_source: src, backup_file: file, target };
  if (target === 'remote') {
    payload.remote_ip = el('remote-ip')?.value.trim();
    payload.remote_port = el('remote-port')?.value.trim() || '22';
    payload.remote_user = el('remote-user')?.value.trim() || 'root';
    payload.auth_method = document.querySelector('input[name="auth_method"]:checked')?.value;
    if (payload.auth_method === 'key') payload.ssh_key_path = el('ssh-key-path')?.value.trim();
    else payload.ssh_password = el('ssh-password')?.value || '';
    if (!payload.remote_ip) return alert('Enter target IP.');
  }
  if (!confirm(`Restore "${file}" now?`)) return;
  if (el('restore-log-wrapper')) el('restore-log-wrapper').style.display = '';
  if (el('restore-log')) el('restore-log').innerHTML = '';
  try {
    const res = await fetch('/restore/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
    const data = await res.json();
    if (data.error) return appendLog(`❌ ${data.error}`);
    currentJobId = data.job_id;
    pollRestore();
  } catch (e) {
    appendLog(`❌ ${e}`);
  }
}

function pollRestore() {
  if (pollInterval) clearInterval(pollInterval);
  let lastLine = 0;
  pollInterval = setInterval(async () => {
    if (!currentJobId) return;
    try {
      const r = await fetch(`/restore/status/${currentJobId}`);
      const d = await r.json();
      d.log.slice(lastLine).forEach(appendLog);
      lastLine = d.log.length;
      if (el('restore-elapsed')) el('restore-elapsed').textContent = `⏱ ${d.elapsed}s`;
      if (d.status !== 'running') clearInterval(pollInterval);
    } catch (_) {}
  }, 1500);
}

function refreshAll() {
  const btn = document.querySelector('.icon-btn');
  if (btn) btn.classList.add('spinning');
  const extras = [];
  if (typeof window.loadSites === 'function') extras.push(window.loadSites(true));
  Promise.all([
    checkServerStatus(),
    refreshSystemMetrics(),
    refreshContainerStats(),
    refreshSidebarNavBadges(),
    ...extras,
  ]).finally(() => { if (btn) btn.classList.remove('spinning'); });
}

document.addEventListener('DOMContentLoaded', () => {
  checkServerStatus();
  refreshSystemMetrics();
  refreshContainerStats();
  refreshSidebarNavBadges();
  if (el('all-containers-body')) loadAllContainers();
  if (el('backup-history-list')) { refreshBackupsList(); loadBackupLog(); }
  if (el('users-grid')) loadUsers();
  if (window.restorePrefill && el('backup-file-select')) {
    const { source, file } = window.restorePrefill;
    if (source) {
      const sourceRadio = document.querySelector(`input[name="backup_source"][value="${source}"]`);
      if (sourceRadio) sourceRadio.checked = true;
      updateBackupList();
    }
    if (file) {
      const sel = el('backup-file-select');
      for (const opt of sel.options) {
        if (opt.value === file) { opt.selected = true; break; }
      }
    }
  }
  setInterval(() => { refreshSystemMetrics(); refreshContainerStats(); refreshSidebarNavBadges(); }, 15000);
  setInterval(checkServerStatus, 30000);
});
