<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Docker tab. Container inventory grouped into Varaverk-owned folders, with start / stop /
//   restart controls and WebUI links.
//
// DESIGN PRINCIPLES
//   Pure view — inventory and folder state come from api/docker.php; actions go to
//   api/docker_action.php. Folder storage and the conf mirror live in include/docker.php.
//
//   Folders are Varaverk's own, not folder.view3's. The grouping survives that plugin being
//   absent or reset; it is synced to only when installed.
//
// OPERATIONAL SAFEGUARDS
//   Destructive actions are confirmed in the browser before the request is sent.
//
//   Container actions are routed through a dedicated action endpoint that validates the name
//   against the real inventory — the page never composes a docker command.
//
//   Container names are escaped on render, so a name containing markup cannot inject.
//
// RENDERS
//   Folder-grouped container grid, per-container state, WebUI links, action buttons
//
// DEPENDS ON
//   api/docker.php         polled every 60s   → include/docker.php
//   api/docker_action.php  start/stop/restart
?>
<style>
.vv-dk-toolbar    { display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:0 2px;flex-wrap:wrap; }
.vv-dk-title      { font-size:13px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;flex:1; }
.vv-dk-ts         { font-size:11px;color:#3a3a3a; }
.vv-dk-btn        { font-size:11px;padding:3px 10px;border-radius:3px;border:1px solid #2a2a2a;background:#1a1a1a;color:#888;cursor:pointer;white-space:nowrap; }
.vv-dk-btn:hover  { background:#222;color:#bbb; }
.vv-dk-btn.active { background:#1a2a1a;border-color:#2d4a2d;color:#6fcf97; }
.vv-dk-btn.warn   { border-color:#3a2800;background:#1f1500;color:#ffb74d; }
.vv-dk-btn.prim   { border-color:#1a3a5a;background:#0d1f2a;color:#5c9fd4; }

/* Folder card */
.vv-dk-folder     { background:#161616;border:1px solid #222;border-radius:6px;min-width:0;grid-column:span 4;overflow:hidden; }
.vv-dk-folder.wide{ grid-column:span 8; }
.vv-dk-folder.edit{ border-color:#2a3a2a; }
.vv-dk-folder-h   { display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid #1e1e1e;background:#111; }
.vv-dk-folder-name{ font-size:12px;font-weight:bold;color:#777;flex:1;min-width:0; }
.vv-dk-folder-name input { background:#0d0d0d;border:1px solid #333;border-radius:3px;color:#bbb;font-size:12px;padding:1px 6px;width:100%;box-sizing:border-box; }
.vv-dk-folder-count{ font-size:10px;color:#333;white-space:nowrap; }
.vv-dk-folder-del { font-size:13px;color:#444;cursor:pointer;padding:0 2px;line-height:1; }
.vv-dk-folder-del:hover { color:#ef5350; }

/* Container rows */
.vv-dk-ctr-list   { max-height:480px;overflow-y:auto; }
.vv-dk-ctr        { padding:8px 12px;border-bottom:1px solid #1a1a1a;display:grid;grid-template-columns:auto 1fr;gap:0 10px;align-items:start; }
.vv-dk-ctr:last-child { border-bottom:none; }
.vv-dk-ctr:hover  { background:#191919; }
.vv-dk-ctr.stopped { opacity:.6; }
.vv-dk-ctr.edit-mode { cursor:pointer; }
.vv-dk-ctr.edit-mode:hover { background:#141f14; }
.vv-dk-ctr-icon   { width:28px;height:28px;border-radius:4px;object-fit:contain;background:#111;grid-row:span 3;align-self:center; }
.vv-dk-ctr-icon-ph{ width:28px;height:28px;border-radius:4px;background:#1a1a1a;border:1px solid #222;grid-row:span 3;align-self:center;display:flex;align-items:center;justify-content:center;font-size:11px;color:#333; }
.vv-dk-ctr-name-row{ display:flex;align-items:baseline;gap:8px;flex-wrap:wrap; }
.vv-dk-ctr-name   { font-size:12px;font-weight:bold;color:#bbb; }
.vv-dk-ctr-name a { color:#bbb;text-decoration:none; }
.vv-dk-ctr-name a:hover { color:#6fcf97; }
.vv-dk-ctr-image  { font-size:10px;color:#3a3a3a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px; }
.vv-dk-ctr-status { font-size:10px;font-weight:bold;letter-spacing:.04em; }
.vv-dk-ctr-status.running { color:#4caf50; }
.vv-dk-ctr-status.stopped { color:#555; }
.vv-dk-ctr-status.paused  { color:#ffb74d; }
.vv-dk-ctr-status.exited  { color:#ef5350; }
.vv-dk-meta-row   { display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin-top:3px; }
.vv-dk-net-badge  { font-size:10px;padding:1px 6px;border-radius:2px;background:#0d1a2a;color:#5c7cfa;border:1px solid #1a2a3a;white-space:nowrap; }
.vv-dk-port-badge { font-size:10px;padding:1px 6px;border-radius:2px;background:#1a1a0a;color:#cddc39;border:1px solid #2a2a1a;white-space:nowrap; }
.vv-dk-paths      { margin-top:4px;display:flex;flex-direction:column;gap:1px; }
.vv-dk-path       { font-size:10px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
.vv-dk-path .src  { color:#3a3a3a; }
.vv-dk-path .arr  { color:#2a2a2a;margin:0 3px; }
.vv-dk-path .dst  { color:#2e3e2e; }
.vv-dk-paths-more { font-size:10px;color:#2a2a2a;cursor:pointer;margin-top:1px; }
.vv-dk-paths-more:hover { color:#555; }

/* Ungrouped */
.vv-dk-ungroup    { grid-column:1/-1;background:#111;border:1px solid #1e1e1e;border-radius:6px;overflow:hidden; }
.vv-dk-ungroup-h  { padding:8px 12px;border-bottom:1px solid #1a1a1a;background:#0d0d0d; }
.vv-dk-ungroup-ht { font-size:11px;color:#3a3a3a;font-weight:bold;text-transform:uppercase;letter-spacing:.05em; }

/* New folder placeholder */
.vv-dk-new-card   { grid-column:span 4;background:#0d0d0d;border:1px dashed #222;border-radius:6px;min-height:80px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#2a2a2a;font-size:12px; }
.vv-dk-new-card:hover { border-color:#333;color:#555; }

/* Drift */
.vv-dk-drift      { grid-column:1/-1;border:1px solid #3a2800;background:#1a1200;border-radius:6px;padding:10px;margin-bottom:12px; }
.vv-dk-drift-h    { font-size:11px;color:#aa7020;font-weight:bold;margin-bottom:6px; }
.vv-dk-drift-row  { font-size:11px;color:#7a5020;margin:2px 0; }

/* Popover */
.vv-dk-popover    { position:fixed;z-index:9999;background:#1c1c1c;border:1px solid #333;border-radius:5px;padding:5px 0;min-width:170px;box-shadow:0 4px 20px #000c; }
.vv-dk-pop-item   { padding:5px 14px;font-size:12px;color:#888;cursor:pointer;white-space:nowrap; }
.vv-dk-pop-item:hover { background:#252525;color:#ccc; }
.vv-dk-pop-item.current { color:#4caf50; }
.vv-dk-pop-item.sep { border-top:1px solid #222;margin-top:4px;padding-top:8px; }
.vv-dk-pop-item.blue { color:#5c9fd4; }
.vv-dk-pop-item.red  { color:#ef5350; }

/* Action button + job badge */
.vv-dk-ctr-act-btn  { font-size:11px;padding:0 5px;border-radius:3px;border:1px solid #222;background:transparent;color:#333;cursor:pointer;line-height:1.7;margin-left:auto;flex-shrink:0; }
.vv-dk-ctr-act-btn:hover { background:#1e1e1e;color:#777; }
.vv-dk-job-badge    { font-size:10px;padding:1px 6px;border-radius:2px;white-space:nowrap;flex-shrink:0; }
.vv-dk-job-badge.pulling    { background:#0d1a2a;color:#5c7cfa; }
.vv-dk-job-badge.rebuilding { background:#1a1a0a;color:#cddc39; }
.vv-dk-job-badge.restarting { background:#1a1a2a;color:#9c89d4; }
.vv-dk-job-badge.done-ok    { background:#0d1a0d;color:#4caf50; }
.vv-dk-job-badge.done-err   { background:#1a0d0d;color:#ef5350; }

/* Log panel */
.vv-dk-log-panel { padding:8px 12px;background:#080808;border-top:1px solid #161616; }
.vv-dk-log-pre   { margin:0;font-size:10px;color:#4a4a4a;font-family:monospace;white-space:pre-wrap;word-break:break-all;max-height:260px;overflow-y:auto; }
</style>

<div class="vv-dk-toolbar">
  <span class="vv-dk-title">Docker</span>
  <button class="vv-dk-btn warn" id="vv-dk-sync-c2j" title="Apply conf desired state to JSON">Sync conf → JSON</button>
  <button class="vv-dk-btn prim" id="vv-dk-sync-j2c" title="Capture JSON state into conf">Sync JSON → conf</button>
  <button class="vv-dk-btn" id="vv-dk-edit-toggle">Edit folders</button>
  <span style="font-size:10px;color:#1a3a1a;" id="vv-dk-fv3"></span>
  <span class="vv-dk-ts" id="vv-dk-ts"></span>
</div>

<div id="vv-dk-drift-banner"></div>

<div id="vv-dk-grid" style="display:grid;grid-template-columns:repeat(8,1fr);gap:12px;">
  <div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">Loading…</div>
</div>

<div class="vv-dk-popover" id="vv-dk-popover" style="display:none;"></div>

<script>
(function() {

let _data     = null;
let _editMode = false;
let _popTarget = null;

// ── API ───────────────────────────────────────────────────────────────────────

function _api(params, cb) {
  const fd = new FormData();
  for (const [k,v] of Object.entries(params)) fd.append(k, v);
  fetch('/plugins/varaverk/api/docker.php', {method:'POST', body:fd})
    .then(r => r.json()).then(cb)
    .catch(e => console.error('docker api', e));
}

// ── Helpers ───────────────────────────────────────────────────────────────────

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

function _shortImage(img) {
  // strip registry host if present (e.g. lscr.io/linuxserver/sonarr → linuxserver/sonarr)
  return img.replace(/^[a-z0-9.-]+\.[a-z]{2,}\//i, '');
}

// ── Container row ─────────────────────────────────────────────────────────────

function _ctrRow(c, folderId) {
  const statusCls = c.running ? 'running' : (c.status || 'stopped');
  const statusLbl = c.running ? 'RUNNING' : (c.status || 'STOPPED').toUpperCase();
  const rowCls    = 'vv-dk-ctr' + (c.running ? '' : ' stopped') + (_editMode ? ' edit-mode' : '');
  const editAttr  = _editMode ? `data-ctr="${_esc(c.name)}" data-folder="${folderId||''}" title="Move ${c.name}"` : '';
  const ctrAttr   = `data-ctr-name="${_esc(c.name)}"`;
  const actBtn    = !_editMode ? `<button class="vv-dk-ctr-act-btn" data-act-ctr="${_esc(c.name)}" title="Actions">···</button>` : '';
  const jobBadge  = !_editMode ? `<span class="vv-dk-job-badge" id="vv-dk-job-${_esc(c.name)}" style="display:none"></span>` : '';

  // Icon or placeholder
  const iconHtml = c.icon
    ? `<img class="vv-dk-ctr-icon" src="${_esc(c.icon)}" alt="" onerror="this.style.display='none'">`
    : `<div class="vv-dk-ctr-icon-ph">◻</div>`;

  // Name — link to WebUI if available
  const nameHtml = c.webui
    ? `<a href="${_esc(c.webui)}" target="_blank">${_esc(c.name)}</a>`
    : _esc(c.name);

  // Networks + IPs
  const nets = Object.entries(c.networks || {});
  const netBadges = nets.map(([net, ip]) =>
    `<span class="vv-dk-net-badge" title="${_esc(net)}">${_esc(ip)}</span>`).join('');

  // Ports (cap at 4)
  const ports = c.ports || [];
  const portBadges = ports.slice(0,4).map(p =>
    `<span class="vv-dk-port-badge">${_esc(p)}</span>`).join('');
  const morePorts = ports.length > 4 ? `<span class="vv-dk-port-badge" style="color:#555">+${ports.length-4}</span>` : '';

  // Paths (bind mounts, show 3 with expand)
  const mounts = c.mounts || [];
  const visibleMounts = mounts.slice(0, 3);
  const hiddenCount  = mounts.length - visibleMounts.length;
  let pathsHtml = '';
  if (visibleMounts.length) {
    pathsHtml = `<div class="vv-dk-paths">` +
      visibleMounts.map(m =>
        `<div class="vv-dk-path"><span class="vv-dk-path src">${_esc(m.src)}</span><span class="vv-dk-path arr">→</span><span class="vv-dk-path dst">${_esc(m.dst)}</span></div>`
      ).join('') +
      (hiddenCount > 0 ? `<div class="vv-dk-paths-more" data-expand="${_esc(c.name)}">+ ${hiddenCount} more path${hiddenCount>1?'s':''}</div>` : '') +
      `</div>`;
  }

  return `<div class="${rowCls}" ${editAttr} ${ctrAttr}>
    ${iconHtml}
    <div>
      <div class="vv-dk-ctr-name-row">
        <span class="vv-dk-ctr-name">${nameHtml}</span>
        <span class="vv-dk-ctr-status ${statusCls}">${statusLbl}</span>
        ${jobBadge}${actBtn}
      </div>
      <div class="vv-dk-ctr-image">${_esc(_shortImage(c.image||''))}</div>
      ${(netBadges || portBadges) ? `<div class="vv-dk-meta-row">${netBadges}${portBadges}${morePorts}</div>` : ''}
      ${pathsHtml}
    </div>
  </div>
  <div class="vv-dk-log-panel" id="vv-dk-log-${_esc(c.name)}" style="display:none">
    <pre class="vv-dk-log-pre"></pre>
  </div>`;
}

// ── Folder card ───────────────────────────────────────────────────────────────

function _folderCard(f) {
  const wide    = f.total > 7;
  const cls     = 'vv-dk-folder' + (wide ? ' wide' : '') + (_editMode ? ' edit' : '');
  const running = f.running, total = f.total;

  let nameHtml;
  if (_editMode) {
    nameHtml = `<span class="vv-dk-folder-name"><input type="text" value="${_esc(f.name)}" data-folder-id="${f.id}" class="vv-dk-rename-input"></span>`;
  } else {
    nameHtml = `<span class="vv-dk-folder-name">${_esc(f.name)}</span>`;
  }

  const delBtn = _editMode
    ? `<span class="vv-dk-folder-del" data-delete-folder="${f.id}" title="Delete folder">×</span>` : '';

  const rows = (f.containers || []).map(c => _ctrRow(c, f.id)).join('');

  return `<div class="${cls}" data-folder-id="${f.id}">
    <div class="vv-dk-folder-h">
      ${nameHtml}
      <span class="vv-dk-folder-count">${running}/${total} running</span>
      ${delBtn}
    </div>
    <div class="vv-dk-ctr-list">${rows || '<div style="padding:10px 12px;color:#2a2a2a;font-size:11px;">Empty</div>'}</div>
  </div>`;
}

// ── Ungrouped section ─────────────────────────────────────────────────────────

function _ungroupedCard(containers) {
  if (!containers.length) return '';
  const rows = containers.map(c => _ctrRow(c, '')).join('');
  return `<div class="vv-dk-ungroup">
    <div class="vv-dk-ungroup-h">
      <span class="vv-dk-ungroup-ht">Ungrouped (${containers.length})</span>
    </div>
    <div class="vv-dk-ctr-list">${rows}</div>
  </div>`;
}

// ── Drift banner ──────────────────────────────────────────────────────────────

function _driftBanner(drift) {
  const el = document.getElementById('vv-dk-drift-banner');
  if (!drift || !drift.length) { el.innerHTML = ''; return; }
  const rows = drift.map(d => {
    const actual = d.actual ? `in <strong>${d.actual}</strong>` : 'ungrouped';
    return `<div class="vv-dk-drift-row"><strong>${d.container}</strong> — conf wants <strong>${d.conf}</strong>, currently ${actual}</div>`;
  }).join('');
  el.innerHTML = `<div class="vv-dk-drift">
    <div class="vv-dk-drift-h">⚠ Conf / JSON drift — ${drift.length} container${drift.length>1?'s':''} out of sync</div>
    ${rows}
    <button class="vv-dk-btn warn" style="margin-top:8px;" id="vv-dk-fix-drift">Apply conf → JSON now</button>
  </div>`;
  document.getElementById('vv-dk-fix-drift')?.addEventListener('click', _syncC2J);
}

// ── Main render ───────────────────────────────────────────────────────────────

function _render(data) {
  _data = data;
  let html = '';
  for (const f of data.folders   || []) html += _folderCard(f);
  if (_editMode) html += `<div class="vv-dk-new-card" id="vv-dk-add-folder">+ New folder</div>`;
  html += _ungroupedCard(data.ungrouped || []);
  if (!html) html = '<div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">No containers found.</div>';

  document.getElementById('vv-dk-grid').innerHTML = html;
  _driftBanner(data.drift);

  const ts = data.ts ? new Date(data.ts*1000).toLocaleString([],{month:'numeric',day:'numeric',year:'numeric',hour:'2-digit',minute:'2-digit',second:'2-digit'}) : '';
  document.getElementById('vv-dk-ts').textContent = ts ? 'Updated: '+ts : '';
  const fv3El = document.getElementById('vv-dk-fv3');
  if (fv3El) fv3El.textContent = data.fv3_synced ? '⇄ folder.view3' : '';

  _bindEvents();
}

// ── Container actions ─────────────────────────────────────────────────────────

let _activeJobs = {}; // { ctrName: intervalId }

function _actApi(params, cb) {
  const fd = new FormData();
  for (const [k, v] of Object.entries(params)) fd.append(k, v);
  fetch('/plugins/varaverk/api/docker_action.php', {method: 'POST', body: fd})
    .then(r => r.json()).then(cb)
    .catch(() => cb({ok: false, error: 'Request failed'}));
}

function _jobBadgeEl(name) {
  return document.getElementById('vv-dk-job-' + name);
}

function _setBadge(name, cls, text, autohide) {
  const el = _jobBadgeEl(name);
  if (!el) return;
  el.className = 'vv-dk-job-badge ' + cls;
  el.textContent = text;
  el.style.display = '';
  if (autohide) setTimeout(() => { if (el.parentNode) el.style.display = 'none'; }, 4000);
}

function _pollJob(name, jobId) {
  _actApi({action: 'job_status', job_id: jobId}, data => {
    const st = data.status;
    if (st === 'pulling')    _setBadge(name, 'pulling',    'Pulling…',    false);
    if (st === 'rebuilding') _setBadge(name, 'rebuilding', 'Rebuilding…', false);
    if (st === 'done') {
      clearInterval(_activeJobs[name]);
      delete _activeJobs[name];
      if (data.ok) {
        _setBadge(name, 'done-ok', data.message || 'Done', true);
      } else {
        _setBadge(name, 'done-err', data.error || 'Failed', true);
      }
      setTimeout(_reload, 1500);
    }
  });
}

function _startJob(name, jobId) {
  if (_activeJobs[name]) clearInterval(_activeJobs[name]);
  _setBadge(name, 'pulling', 'Pulling…', false);
  _activeJobs[name] = setInterval(() => _pollJob(name, jobId), 2000);
}

function _doRestart(name) {
  _setBadge(name, 'restarting', 'Restarting…', false);
  _actApi({action: 'restart', name}, data => {
    if (data.ok) {
      _setBadge(name, 'done-ok', 'Restarted', true);
      setTimeout(_reload, 1000);
    } else {
      _setBadge(name, 'done-err', 'Failed', true);
    }
  });
}

function _doStartStop(name, start) {
  _actApi({action: start ? 'start' : 'stop', name}, data => {
    if (data.ok) setTimeout(_reload, 800);
    else alert((start ? 'Start' : 'Stop') + ' failed: ' + (data.output || data.error || '?'));
  });
}

function _doPullRebuild(name) {
  _actApi({action: 'pull_rebuild', name}, data => {
    if (data.ok && data.job_id) {
      _startJob(name, data.job_id);
    } else {
      alert('Update failed: ' + (data.error || '?'));
    }
  });
}

function _toggleLog(name) {
  const panel = document.getElementById('vv-dk-log-' + name);
  if (!panel) return;
  if (panel.style.display !== 'none') {
    panel.style.display = 'none';
    return;
  }
  const pre = panel.querySelector('.vv-dk-log-pre');
  pre.textContent = 'Loading…';
  panel.style.display = '';
  _actApi({action: 'logs', name}, data => {
    pre.textContent = data.ok ? (data.logs || '(no output)') : ('Error: ' + (data.error || '?'));
    pre.scrollTop = pre.scrollHeight;
  });
}

function _showActionsMenu(name, running, x, y) {
  const pop = document.getElementById('vv-dk-popover');
  let html = '';
  if (running) {
    html += `<div class="vv-dk-pop-item red" data-act="stop"   data-act-n="${_esc(name)}">Stop</div>`;
    html += `<div class="vv-dk-pop-item"     data-act="restart" data-act-n="${_esc(name)}">Restart</div>`;
  } else {
    html += `<div class="vv-dk-pop-item current" data-act="start" data-act-n="${_esc(name)}">Start</div>`;
  }
  html += `<div class="vv-dk-pop-item sep blue" data-act="pull_rebuild" data-act-n="${_esc(name)}">Update (Pull &amp; Rebuild)</div>`;
  html += `<div class="vv-dk-pop-item sep"      data-act="logs"         data-act-n="${_esc(name)}">View Logs</div>`;
  pop.innerHTML = html;
  pop.style.display = 'block';
  const vw = window.innerWidth, vh = window.innerHeight;
  pop.style.left = Math.min(x, vw - 200) + 'px';
  pop.style.top  = Math.min(y + 8, vh - 160) + 'px';

  pop.querySelectorAll('[data-act]').forEach(el => {
    el.addEventListener('click', () => {
      const act = el.dataset.act, n = el.dataset.actN;
      _hidePopover();
      if (act === 'start' || act === 'stop') _doStartStop(n, act === 'start');
      else if (act === 'restart')            _doRestart(n);
      else if (act === 'pull_rebuild')       _doPullRebuild(n);
      else if (act === 'logs')               _toggleLog(n);
    });
  });
}

// ── Events ────────────────────────────────────────────────────────────────────

function _bindEvents() {
  // Action menu button (non-edit mode)
  document.querySelectorAll('[data-act-ctr]').forEach(btn => {
    btn.addEventListener('click', e => {
      e.stopPropagation();
      const name = btn.dataset.actCtr;
      const allCtrs = [...(_data.folders||[]).flatMap(f=>f.containers), ...(_data.ungrouped||[])];
      const c = allCtrs.find(x => x.name === name);
      _showActionsMenu(name, c?.running ?? false, e.clientX, e.clientY);
    });
  });

  // Container click in edit mode → folder picker
  document.querySelectorAll('.vv-dk-ctr.edit-mode').forEach(el => {
    el.addEventListener('click', e => {
      e.stopPropagation();
      _popTarget = { container: el.dataset.ctr, currentFolderId: el.dataset.folder };
      _showPopover(e.clientX, e.clientY);
    });
  });

  // "show more paths" expand
  document.querySelectorAll('[data-expand]').forEach(el => {
    el.addEventListener('click', e => {
      e.stopPropagation();
      const cname    = el.dataset.expand;
      const allCtrs  = [...(_data.folders||[]).flatMap(f=>f.containers), ...(_data.ungrouped||[])];
      const c        = allCtrs.find(x => x.name === cname);
      if (!c) return;
      const paths = document.querySelector(`.vv-dk-paths-more[data-expand="${CSS.escape(cname)}"]`)?.closest('.vv-dk-paths');
      if (!paths || !c.mounts) return;
      paths.innerHTML = c.mounts.map(m =>
        `<div class="vv-dk-path"><span class="src">${_esc(m.src)}</span><span class="arr">→</span><span class="dst">${_esc(m.dst)}</span></div>`
      ).join('');
    });
  });

  // Rename inputs
  document.querySelectorAll('.vv-dk-rename-input').forEach(el => {
    el.addEventListener('keydown', e => { if (e.key==='Enter') el.blur(); });
    el.addEventListener('blur', () => {
      const fid = el.dataset.folderId, name = el.value.trim();
      if (!fid || !name) return;
      const f = (_data.folders||[]).find(x=>x.id===fid);
      if (f && name===f.name) return;
      el.disabled = true; el.style.opacity = '0.5';
      _api({action:'rename_folder',folder_id:fid,name}, r => {
        el.disabled = false; el.style.opacity = '';
        if (r.ok) _reload(); else { alert('Rename failed: '+(r.error||'?')); el.value = f?.name ?? name; }
      });
    });
  });

  // Delete folder
  document.querySelectorAll('[data-delete-folder]').forEach(el => {
    el.addEventListener('click', e => {
      e.stopPropagation();
      const fid = el.dataset.deleteFolder;
      const f   = (_data.folders||[]).find(x=>x.id===fid);
      if (!f || !confirm(`Delete "${f.name}"? Containers will be ungrouped.`)) return;
      _api({action:'delete_folder',folder_id:fid}, r => { if(r.ok) _reload(); else alert('Delete failed: '+(r.error||'?')); });
    });
  });

  // New folder
  document.getElementById('vv-dk-add-folder')?.addEventListener('click', () => {
    const name = prompt('New folder name:');
    if (name?.trim()) _api({action:'create_folder',name:name.trim()}, r => { if(r.ok) _reload(); else alert(r.error); });
  });
}

// ── Popover ───────────────────────────────────────────────────────────────────

function _showPopover(x, y) {
  if (!_data || !_popTarget) return;
  const pop = document.getElementById('vv-dk-popover');
  const folders = _data.folders || [];
  let html = folders.map(f => {
    const cur = f.id === _popTarget.currentFolderId;
    return `<div class="vv-dk-pop-item${cur?' current':''}" data-fid="${f.id}">${_esc(f.name)}${cur?' ✓':''}</div>`;
  }).join('');
  html += `<div class="vv-dk-pop-item sep" data-fid="">Ungrouped</div>`;
  html += `<div class="vv-dk-pop-item blue sep" data-fid="__new__">+ New folder…</div>`;
  pop.innerHTML = html;
  pop.style.display = 'block';
  const vw=window.innerWidth, vh=window.innerHeight;
  pop.style.left = Math.min(x, vw-180)+'px';
  pop.style.top  = Math.min(y+8, vh-180)+'px';

  pop.querySelectorAll('[data-fid]').forEach(el => {
    el.addEventListener('click', () => {
      const fid = el.dataset.fid;
      _hidePopover();
      if (fid==='__new__') {
        const name = prompt('New folder name:');
        if (!name?.trim()) return;
        _api({action:'create_folder',name:name.trim()}, r => {
          if (!r.ok) { alert(r.error); return; }
          _api({action:'move_container',container:_popTarget.container,folder_id:r.id}, r2 => { if(r2.ok) _reload(); });
        });
      } else {
        _api({action:'move_container',container:_popTarget.container,folder_id:fid}, r => { if(r.ok) _reload(); else alert(r.error); });
      }
    });
  });
}

function _hidePopover() {
  document.getElementById('vv-dk-popover').style.display='none';
  _popTarget=null;
}
document.addEventListener('click', e => {
  if (!document.getElementById('vv-dk-popover').contains(e.target)) _hidePopover();
});

// ── Toolbar ───────────────────────────────────────────────────────────────────

document.getElementById('vv-dk-edit-toggle').addEventListener('click', function() {
  _editMode = !_editMode;
  this.textContent = _editMode ? 'Done editing' : 'Edit folders';
  this.classList.toggle('active', _editMode);
  if (_data) _render(_data);
});

function _syncC2J() {
  _api({action:'sync_conf_to_json'}, r => { if(r.ok) _reload(); else alert(r.error); });
}
document.getElementById('vv-dk-sync-c2j').addEventListener('click', _syncC2J);
document.getElementById('vv-dk-sync-j2c').addEventListener('click', () => {
  if (!confirm('Overwrite conf map with current JSON state?')) return;
  _api({action:'sync_json_to_conf'}, r => { if(r.ok) _reload(); else alert(r.error); });
});

// ── Load ──────────────────────────────────────────────────────────────────────

function _reload() {
  fetch('/plugins/varaverk/api/docker.php')
    .then(r=>r.json()).then(_render).catch(()=>{});
}

_reload();
setInterval(_reload, 60000);

})();
</script>
