// public/js/adminStorage.js
import { t } from './i18n.js?v={{APP_QVER}}';
import { showToast } from './domUtils.js?v={{APP_QVER}}';
import { sendRequest } from './networkUtils.js?v={{APP_QVER}}';

// tiny helper like tf in adminPanel
const tf = (key, fallback) => {
  const v = t(key);
  return (v && v !== key) ? v : fallback;
};

function escapeHtml(str) {
  if (!str) return '';
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function formatBytes(bytes) {
  bytes = Number(bytes) || 0;
  if (bytes <= 0) return '0 B';
  const units = ['B','KB','MB','GB','TB','PB'];
  const i = Math.min(
    units.length - 1,
    Math.floor(Math.log(bytes) / Math.log(1024))
  );
  const val = bytes / Math.pow(1024, i);
  return `${val.toFixed(val >= 100 ? 0 : val >= 10 ? 1 : 2)} ${units[i]}`;
}

function formatDate(ts) {
  if (!ts) return '';
  try {
    const d = new Date(ts * 1000);
    if (Number.isNaN(d.getTime())) return '';
    return d.toLocaleString();
  } catch (e) {
    return '';
  }
}

function getCsrfToken() {
  return (document.querySelector('meta[name="csrf-token"]')?.content || '');
}

function getDefaultScanHint(isPro) {
  return isPro
    ? tf(
        'storage_rescan_hint_pro',
        'Run a fresh disk usage snapshot when storage changes.'
      )
    : tf(
        'storage_rescan_cli_hint',
        'Click Rescan to run a snapshot now, or schedule the CLI scanner via cron.'
      );
}

function isCurrentSourceLocal() {
  if (!sourcesEnabled) return true;
  const t = String(currentSourceType || '').toLowerCase();
  return t === '' || t === 'local';
}

function getStoredSourceId() {
  try {
    return localStorage.getItem('fr_active_source') || '';
  } catch (e) {
    return '';
  }
}

function pickActiveSourceId(sources, activeId) {
  const active = String(activeId || '').trim();
  if (active) return active;
  const stored = getStoredSourceId();
  if (stored) return stored;
  return sources[0]?.id || sources[0]?.key || sources[0]?.name || '';
}

function setCurrentSourceFromId(id) {
  currentSourceId = String(id || '').trim();
  currentSourceName = '';
  currentSourceType = '';
  if (!currentSourceId) return;
  const match = sourcesList.find(src => String(src.id || '') === currentSourceId);
  if (match && typeof match === 'object') {
    currentSourceName = String(match.name || currentSourceId);
    currentSourceType = String(match.type || '');
  } else {
    currentSourceName = currentSourceId;
  }
}

function buildSourceLabel(src) {
  if (!src || typeof src !== 'object') return '';
  const id = String(src.id || src.key || src.name || '').trim();
  if (!id) return '';
  const name = String(src.name || id);
  const type = String(src.type || '');
  const ro = src.readOnly ? ` [${tf('read_only', 'Read only')}]` : '';
  return type ? `${name} (${type})${ro}` : `${name}${ro}`;
}

function syncSourceControls() {
  const isLocal = isCurrentSourceLocal();
  const rescanBtn = document.getElementById('adminStorageRescan');
  const deleteBtn = document.getElementById('adminStorageDeleteSnapshot');
  const hintEl = document.getElementById('adminStorageScanHint');
  if (rescanBtn) rescanBtn.disabled = !isLocal;
  if (deleteBtn) deleteBtn.disabled = !isLocal;
  if (hintEl) {
    hintEl.textContent = isLocal
      ? (defaultScanHint || getDefaultScanHint(isProGlobal))
      : tf('storage_source_unsupported_hint', 'Disk usage scans are only available for local sources.');
  }
}

async function initStorageSources() {
  if (sourcesInitPromise) return sourcesInitPromise;
  sourcesInitPromise = (async () => {
    sourcesEnabled = false;
    sourcesList = [];
    setCurrentSourceFromId('');

    if (!isProGlobal) {
      syncSourceControls();
      return;
    }

    try {
      const res = await fetch('/api/pro/sources/visible.php', {
        credentials: 'include',
        headers: { 'Accept': 'application/json' }
      });
      const data = await res.json().catch(() => null);
      if (data && data.ok && data.enabled && Array.isArray(data.sources) && data.sources.length) {
        sourcesEnabled = true;
        sourcesList = data.sources;
        const nextId = pickActiveSourceId(sourcesList, data.activeId || '');
        setCurrentSourceFromId(nextId);
      }
    } catch (e) {
      sourcesEnabled = false;
      sourcesList = [];
      setCurrentSourceFromId('');
    }

    renderSourceSelector();
    syncSourceControls();
  })();
  return sourcesInitPromise;
}

function renderSourceSelector() {
  const wrap = document.getElementById('adminStorageSourceWrap');
  const select = document.getElementById('adminStorageSourceSelect');
  if (!wrap || !select) return;

  if (!sourcesEnabled || sourcesList.length <= 1) {
    wrap.style.display = 'none';
    select.innerHTML = '';
    return;
  }

  select.innerHTML = '';
  sourcesList.forEach(src => {
    const id = String(src.id || src.key || src.name || '').trim();
    if (!id) return;
    const opt = document.createElement('option');
    opt.value = id;
    opt.textContent = buildSourceLabel(src) || id;
    select.appendChild(opt);
  });

  if (select.options.length) {
    select.value = currentSourceId || select.options[0].value;
    setCurrentSourceFromId(select.value);
  }

  wrap.style.display = '';

  if (!select.dataset.wired) {
    select.dataset.wired = '1';
    select.addEventListener('change', async () => {
      const nextId = select.value || '';
      if (!nextId || nextId === currentSourceId) return;
      setCurrentSourceFromId(nextId);
      try { localStorage.setItem('fr_active_source', nextId); } catch (e) { /* ignore */ }
      syncSourceControls();
      currentFolderKey = 'root';
      await refreshStorageSummary();
      if (isProGlobal) {
        if (currentExplorerTab === 'folders') {
          loadFolderChildren('root');
        } else {
          loadTopFiles();
        }
      }
    });
  }
}

let confirmModalEl = null;
let showAllTopFolders = false;

function ensureConfirmModal() {
  if (confirmModalEl) return confirmModalEl;

  const wrapper = document.createElement('div');
  wrapper.innerHTML = `
    <div class="modal fade" id="adminStorageConfirmModal" tabindex="-1" aria-hidden="true">
      <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content" style="border-radius: var(--menu-radius);">
          <div class="modal-header">
            <h5 class="modal-title" id="adminStorageConfirmTitle">${tf('confirm','Confirm')}</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${tf('close','Close')}"></button>
          </div>
          <div class="modal-body">
            <p id="adminStorageConfirmMessage" class="mb-0"></p>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
              ${tf('cancel','Cancel')}
            </button>
            <button type="button" class="btn btn-danger" id="adminStorageConfirmOk">
              ${tf('delete','Delete')}
            </button>
          </div>
        </div>
      </div>
    </div>
  `.trim();

  confirmModalEl = wrapper.firstChild;
  document.body.appendChild(confirmModalEl);
  return confirmModalEl;
}

function showConfirmDialog({ title, message, confirmLabel }) {
  // Fallback to window.confirm if Bootstrap is not available
  if (!window.bootstrap || !window.bootstrap.Modal) {
    return Promise.resolve(window.confirm(message));
  }

  return new Promise(resolve => {
    const el = ensureConfirmModal();
    const titleEl = el.querySelector('#adminStorageConfirmTitle');
    const msgEl   = el.querySelector('#adminStorageConfirmMessage');
    const okBtn   = el.querySelector('#adminStorageConfirmOk');

    if (titleEl) titleEl.textContent = title || tf('confirm','Confirm');
    if (msgEl) msgEl.textContent = message || '';
    if (okBtn) okBtn.textContent = confirmLabel || tf('delete','Delete');

    const modal = window.bootstrap.Modal.getOrCreateInstance(el);

    const handleOk = () => {
      cleanup();
      resolve(true);
    };

    const handleHidden = () => {
      cleanup();
      resolve(false);
    };

    function cleanup() {
      if (!el) return;
      el.removeEventListener('hidden.bs.modal', handleHidden);
      if (okBtn) okBtn.removeEventListener('click', handleOk);
    }

    if (okBtn) okBtn.addEventListener('click', handleOk, { once: true });
    el.addEventListener('hidden.bs.modal', handleHidden, { once: true });

    modal.show();
  });
}

// --- module-level tracking ---

// snapshot / scanning
let lastGeneratedAt = 0;
let scanPollTimer = null;
let lastScanLogPath = '';
let lastScanLogMtime = null;
let defaultScanHint = '';

// Pro-only dangerous mode: deep delete for folders
let deepDeleteEnabled = false;

// pro explorer
let isProGlobal = false;
let currentFolderKey = 'root';
let currentExplorerTab = 'folders'; // "folders" | "topFiles"
let folderMinSizeBytes = 0;
let topFilesMinSizeBytes = 0;
let sourcesEnabled = false;
let currentSourceId = '';
let currentSourceName = '';
let currentSourceType = '';
let sourcesList = [];
let sourcesInitPromise = null;

// ---------- Scan status ----------

function setScanStatus(isScanning) {
  const statusEl = document.getElementById('adminStorageScanStatus');
  if (!statusEl) return;

  if (!isScanning) {
    statusEl.innerHTML = '';
    return;
  }

  statusEl.innerHTML = `
    <div class="mb-1">
      <div class="progress" style="height: 6px;">
        <div
          class="progress-bar progress-bar-striped progress-bar-animated"
          role="progressbar"
          style="width: 100%;"
        ></div>
      </div>
    </div>
    <div class="small text-muted">
      ${tf('storage_scan_in_progress', 'Disk usage scan in progress...')}
    </div>
  `;
}

function renderScanProblem({ message, logTail, logPath }) {
  const statusEl = document.getElementById('adminStorageScanStatus');
  if (!statusEl) return;

  const tailHtml = logTail
    ? `<pre class="small bg-light p-2 rounded border mb-1" style="user-select:text; white-space:pre-wrap;">${escapeHtml(logTail)}</pre>`
    : '';

  const pathHtml = logPath
    ? `<div class="small text-muted">${tf('storage_scan_log_path', 'Log file')}: <code>${escapeHtml(logPath)}</code></div>`
    : '';

  statusEl.innerHTML = `
    <div class="alert alert-warning mb-2" style="border-radius: var(--menu-radius);">
      <div class="fw-semibold mb-1">
        ${tf('storage_scan_failed', 'Disk usage scan did not finish.')}
      </div>
      ${message ? `<div class="small mb-1">${escapeHtml(message)}</div>` : ''}
      ${tailHtml}
      ${pathHtml}
    </div>
  `;
}

// Make sure delete buttons visually reflect whether deep delete is enabled
function updateDeleteButtonsForDeepDelete() {
    const host = document.getElementById('adminStorageProTeaser');
    if (!host) return;
  
    host.querySelectorAll('.admin-storage-delete-folder').forEach(btn => {
      const icon = btn.querySelector('.material-icons');
  
      if (deepDeleteEnabled) {
        btn.classList.remove('btn-outline-danger');
        btn.classList.add('btn-outline-warning');
        // let the icon inherit currentColor so it goes white on hover
        if (icon) icon.classList.remove('text-warning');
        btn.title = tf('storage_deep_delete_folder_title', 'Deep delete folder (no Trash)');
      } else {
        btn.classList.remove('btn-outline-warning');
        btn.classList.add('btn-outline-danger');
        if (icon) icon.classList.remove('text-warning');
        btn.title = tf('delete_folder', 'Delete folder');
      }
    });
  }
  
  // Wire the toggle switch in the explorer header
  function wireDeepDeleteToggle() {
    const toggle = document.getElementById('adminStorageDeepDeleteToggle');
    if (!toggle) return;
    if (toggle.dataset.wired === '1') return;
    toggle.dataset.wired = '1';
  
    toggle.addEventListener('change', () => {
      deepDeleteEnabled = !!toggle.checked;
      updateDeleteButtonsForDeepDelete();
    });
  }

// ---------- Layout ----------

/**
 * Render the basic layout (header, summary area, tabs placeholder) into storageContent.
 * Pro explorer UI gets injected into #adminStorageProTeaser later.
 */
function renderBaseLayout(container, { isPro }) {
    container.innerHTML = `
    <div class="storage-section mt-2">
      <div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-2 mb-2">
          <div>
            <h5 class="mb-1">${tf('storage_disk_usage', 'Storage / Disk Usage')}</h5>
            <small class="text-muted">
              ${tf(
                'storage_disk_usage_help',
                'Analyze which folders and files are consuming space under your FileRise upload root.'
              )}
            </small>
          </div>
          <div class="text-end">
            <div id="adminStorageSourceWrap" class="mb-1" style="display:none; min-width:220px;">
              <label class="form-label form-label-sm mb-0">${tf('storage_source_label', 'Source')}</label>
              <select id="adminStorageSourceSelect" class="form-select form-select-sm"></select>
            </div>
            <div class="btn-group" role="group">
              <button
                type="button"
                id="adminStorageRescan"
                class="btn btn-sm btn-primary">
                <i class="material-icons" style="vertical-align:middle;font-size:18px;color:currentColor;">refresh</i>
                <span style="vertical-align:middle;">${tf('rescan_now', 'Rescan')}</span>
              </button>
              <button
                type="button"
                id="adminStorageDeleteSnapshot"
                class="btn btn-sm btn-danger">
                <i class="material-icons" style="vertical-align:middle;font-size:18px;color:currentColor;">delete_forever</i>
                <span style="vertical-align:middle;">${tf('storage_delete_snapshot','Delete snapshot')}</span>
              </button>
            </div>
            <div>
              <small class="text-muted d-block" id="adminStorageScanHint">
                ${getDefaultScanHint(isPro)}
              </small>
            </div>
          </div>
        </div>
  
        <div id="adminStorageScanStatus" class="mb-2"></div>
  
        <div id="adminStorageSummary" class="mb-3">
          <div class="text-muted">${tf('loading', 'Loading...')}</div>
        </div>
  
        <div id="adminStorageProTeaser" class="mb-2">
          ${
            isPro
              ? `
            <!-- Pro explorer injected here at runtime -->
            `
              : `
            <div class="card" style="border-radius: var(--menu-radius); overflow:hidden; position:relative;">
              <div class="card-header py-2">
                <div class="d-flex justify-content-between align-items-center">
                  <div>
                    <strong>
                      ${tf('storage_explorer', 'Storage explorer')}
                      <span class="badge bg-warning text-dark ms-1 align-middle">Pro</span>
                    </strong>
                    <div class="small text-muted">
                      ${tf(
                        'storage_explorer_help',
                        'Drill down into folders or inspect the largest files.'
                      )}
                    </div>
                  </div>
                </div>
              </div>
              <div class="card-body p-2">
                <!-- Blurred fake table teaser -->
                <div class="table-responsive"
                     style="max-height:260px;overflow:hidden;filter:blur(3px);opacity:0.5;pointer-events:none;">
                  <table class="table table-sm mb-0">
                    <thead>
                      <tr>
                        <th>${tf('name','Name')}</th>
                        <th>${tf('size','Size')}</th>
                        <th>%</th>
                        <th>${tf('files','Files')}</th>
                        <th>${tf('modified','Modified')}</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr><td colspan="5">&nbsp;</td></tr>
                      <tr><td colspan="5">&nbsp;</td></tr>
                      <tr><td colspan="5">&nbsp;</td></tr>
                      <tr><td colspan="5">&nbsp;</td></tr>
                    </tbody>
                  </table>
                </div>
  
                <!-- Centered overlay copy -->
                <div
                  class="d-flex flex-column align-items-center justify-content-center text-center"
                  style="position:absolute; inset:0; padding:16px;">
                  <div class="mb-1">
                    <span class="badge bg-warning text-dark me-1">Pro</span>
                    <span class="fw-semibold">
                      ${tf('storage_pro_locked_title','Storage explorer is a Pro feature')}
                    </span>
                  </div>
                  <div class="small text-muted mb-2">
                    ${tf(
                      'storage_pro_locked_body',
                      'Upgrade to FileRise Pro to unlock folder drill-down, top files view, and inline cleanup tools.'
                    )}
                  </div>
                </div>
              </div>
            </div>
            `
          }
        </div>
      </div>
    `;
  }

// ---------- Summary / volumes ----------

function getSourceQueryParam() {
  return currentSourceId ? `&sourceId=${encodeURIComponent(currentSourceId)}` : '';
}

function getSourcePayload() {
  return currentSourceId ? { sourceId: currentSourceId } : null;
}

/**
 * Fetch summary JSON only (no UI changes) – used for polling after rescan.
 */
async function fetchSummaryRaw() {
  try {
    const res = await fetch(`/api/admin/diskUsageSummary.php?topFolders=20&topFiles=0${getSourceQueryParam()}`, {
      credentials: 'include',
      cache: 'no-store',
      headers: { 'Cache-Control': 'no-store' }
    });
    const text = await res.text();
    return JSON.parse(text || '{}');
  } catch (e) {
    console.error('fetchSummaryRaw error', e);
    return null;
  }
}

async function refreshStorageSummary() {
  const summaryEl = document.getElementById('adminStorageSummary');
  if (!summaryEl) return;

  if (!isCurrentSourceLocal()) {
    const label = currentSourceName
      ? `${currentSourceName}${currentSourceType ? ` (${currentSourceType})` : ''}`
      : (currentSourceId || tf('storage_selected_source', 'selected source'));
    summaryEl.innerHTML = `
      <div class="alert alert-warning mb-2" style="border-radius: var(--menu-radius);">
        ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
        ${label ? `<div class="small mt-1">${tf('storage_selected_source', 'Selected source')}: ${escapeHtml(label)}</div>` : ''}
      </div>
    `;
    return;
  }

  summaryEl.innerHTML = `<div class="text-muted">${tf('loading', 'Loading...')}</div>`;

  const data = await fetchSummaryRaw();
  const logInfo = data && typeof data === 'object' ? data.scanLog : null;
  if (logInfo && logInfo.path) {
    lastScanLogPath = logInfo.path;
  }
  if (logInfo && typeof logInfo.modifiedAt === 'number' && logInfo.modifiedAt > 0) {
    lastScanLogMtime = logInfo.modifiedAt;
  }

  if (!data || !data.ok) {
    if (data && data.error === 'unsupported_source') {
      summaryEl.innerHTML = `
        <div class="alert alert-warning mb-2" style="border-radius: var(--menu-radius);">
          ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
        </div>
      `;
      return;
    }
    if (data && data.error === 'invalid_source') {
      summaryEl.innerHTML = `
        <div class="alert alert-danger mb-2" style="border-radius: var(--menu-radius);">
          ${tf('storage_source_invalid', 'Invalid source selected for disk usage.')}
        </div>
      `;
      return;
    }
    const reason = data && data.message
      ? String(data.message)
      : data && data.error
        ? String(data.error)
        : '';
    const logPath = (logInfo && logInfo.path) || lastScanLogPath || '';
    const tailHtml = logInfo && logInfo.tail
      ? `<pre class="small bg-light p-2 rounded border mt-2" style="user-select:text; white-space:pre-wrap;">${escapeHtml(logInfo.tail)}</pre>`
      : '';
    const pathHtml = logPath
      ? `<div class="small text-muted mt-1">${tf('storage_scan_log_path','Log file')}: <code>${escapeHtml(logPath)}</code></div>`
      : '';

    if (data && data.error === 'no_snapshot') {
      const cmd = currentSourceId
        ? `php src/cli/disk_usage_scan.php ${currentSourceId}`
        : 'php src/cli/disk_usage_scan.php';
      summaryEl.innerHTML = `
        <div class="alert alert-warning mb-2" style="border-radius: var(--menu-radius);">
          ${tf(
            'storage_no_snapshot',
            'No disk usage snapshot found. Run the CLI scanner once to generate the first snapshot.'
          )}
          ${pathHtml}
        </div>
        <pre class="small bg-light p-2 rounded border" style="user-select:text; white-space:pre-wrap;">
${escapeHtml(cmd)}
        </pre>
        ${tailHtml}
      `;
      return;
    }

    summaryEl.innerHTML = `
      <div class="alert alert-danger mb-2" style="border-radius: var(--menu-radius);">
        <div class="fw-semibold">${tf('storage_summary_error', 'Unable to load disk usage summary.')}</div>
        ${reason ? `<div class="small">${escapeHtml(reason)}</div>` : ''}
        ${pathHtml}
        ${tailHtml}
      </div>
    `;
    if (logInfo && logInfo.tail) {
      renderScanProblem({
        message: reason || tf('storage_summary_error', 'Unable to load disk usage summary.'),
        logTail: logInfo.tail,
        logPath
      });
    }
    return;
  }

  // remember last snapshot timestamp for polling logic
  if (data.generatedAt) {
    lastGeneratedAt = data.generatedAt;
  }

  const totalBytes   = data.totalBytes || 0;
  const totalFiles   = data.totalFiles || 0;
  const totalFolders = data.totalFolders || 0;
  const generatedAt  = data.generatedAt || 0;
  const scanSeconds  = data.scanSeconds || 0;
  const topFolders   = Array.isArray(data.topFolders) ? data.topFolders : [];

  const totalSizeStr  = formatBytes(totalBytes);
  const scannedAtStr  = generatedAt
    ? formatDate(generatedAt)
    : tf('storage_never_scanned', 'Not available');

  // grouped volumes info from PHP
  const volumes = Array.isArray(data.volumes) ? data.volumes : [];

  // Decide how many top folders to display
const initialLimit = 5;
const displayTopFolders = (isProGlobal && showAllTopFolders)
  ? topFolders
  : topFolders.slice(0, initialLimit);

  const topRows = displayTopFolders.map(f => {
    const pct   = f.percentOfTotal || 0;
    const width = Math.max(3, Math.min(100, Math.round(pct)));
    const label = f.folder === 'root' ? '/' : `/${f.folder}`;
    return `
      <tr>
        <td class="align-middle">
          ${
            isProGlobal
              ? `<button type="button"
                         class="btn btn-link btn-sm p-0 admin-storage-summary-folder-link"
                         data-folder="${f.folder}">
                   <code>${label}</code>
                 </button>`
              : `<code>${label}</code>`
          }
        </td>
        <td class="align-middle text-nowrap">
          ${formatBytes(f.bytes || 0)}
        </td>
        <td class="align-middle text-nowrap">
          ${pct.toFixed(1)}%
        </td>
        <td class="align-middle" style="width:40%;">
          <div class="progress" style="height: 6px; background-color: rgba(0,0,0,0.05);">
            <div
              class="progress-bar"
              role="progressbar"
              style="width:${width}%;"
              aria-valuenow="${pct.toFixed(1)}"
              aria-valuemin="0"
              aria-valuemax="100">
            </div>
          </div>
        </td>
      </tr>
    `;
  }).join('') || `
      <tr>
        <td colspan="4" class="text-muted">
          ${tf('storage_no_folders', 'No folders found in snapshot.')}
        </td>
      </tr>
  `;

  // --- Volumes metrics block (Uploads / Users / Metadata) ---
  let rootVolumeHtml = '';

  if (volumes.length) {
    rootVolumeHtml = volumes.map((vol) => {
      const usedBytes   = Number(vol.usedBytes || 0);
      const totalBytesV = Number(vol.totalBytes || 0);
      const usedPercent = Number(vol.usedPercent || 0);
      const pctRounded  = Math.max(0, Math.min(100, Math.round(usedPercent)));

      const usedStr  = formatBytes(usedBytes);
      const totalStr = formatBytes(totalBytesV);

      const roots = Array.isArray(vol.roots) ? vol.roots : [];

      // Build a human label like "Uploads + Users" or "Uploads + Users + Metadata"
      const labelParts = [];
      const mounts = roots.map(r => {
        const kind = (r.kind || '').toLowerCase();
        let label;
        if (kind === 'uploads') label = tf('storage_kind_uploads', 'Uploads');
        else if (kind === 'users') label = tf('storage_kind_users', 'Users');
        else if (kind === 'meta') label = tf('storage_kind_meta', 'Metadata');
        else label = kind || 'Root';

        const safeLabel = escapeHtml(label);
        const safePath = escapeHtml(r.path || '');

        if (!labelParts.includes(safeLabel)) {
          labelParts.push(safeLabel);
        }

        return `<span class="me-2">${safeLabel}: <code>${safePath}</code></span>`;
      }).join(' ');

      const volumeTitle = labelParts.length
        ? `${tf('storage_volume_label', 'Volume')} ${labelParts.join(' + ')}`
        : tf('storage_volume_generic', 'Volume');

      return `
        <div class="mt-2">
          <div class="small fw-bold mb-1">
            ${volumeTitle}
          </div>
          <div class="d-flex justify-content-between small">
            <span>${usedStr} / ${totalStr}</span>
            <span>${usedPercent.toFixed(1)}% ${tf('full', 'full')}</span>
          </div>
          <div class="progress mt-1" style="height: 6px;">
            <div
              class="progress-bar"
              role="progressbar"
              style="width:${pctRounded}%;"
              aria-valuenow="${usedPercent.toFixed(1)}"
              aria-valuemin="0"
              aria-valuemax="100">
            </div>
          </div>
          ${
            mounts
              ? `<div class="small text-muted mt-1">${mounts}</div>`
              : ''
          }
        </div>
      `;
    }).join('');
  } else {
    // Fallback to single-root view if volumes not present (old style)
    const fsTotalBytes  = data.fsTotalBytes ?? null;
    const fsUsedBytes   = data.fsUsedBytes ?? null;
    const fsUsedPercent = data.fsUsedPercent ?? null;
    const uploadRoot    = data.uploadRoot || '';

    if (fsTotalBytes && fsTotalBytes > 0 && fsUsedBytes != null && fsUsedPercent != null) {
      const usedStr  = formatBytes(fsUsedBytes);
      const totalStr = formatBytes(fsTotalBytes);
      const pct      = Math.max(0, Math.min(100, Math.round(fsUsedPercent)));

      rootVolumeHtml = `
        <div class="mt-2">
          <div class="small fw-bold mb-1">
            ${tf('storage_root_volume', 'Root volume')}
          </div>
          <div class="d-flex justify-content-between small">
            <span>${usedStr} / ${totalStr}</span>
            <span>${fsUsedPercent.toFixed(1)}% ${tf('full', 'full')}</span>
          </div>
          <div class="progress mt-1" style="height: 6px;">
            <div
              class="progress-bar"
              role="progressbar"
              style="width:${pct}%;"
              aria-valuenow="${fsUsedPercent.toFixed(1)}"
              aria-valuemin="0"
              aria-valuemax="100">
            </div>
          </div>
          ${
            uploadRoot
              ? `<div class="small text-muted mt-1">
                   ${tf('storage_root_path', 'Upload root')}: <code>${escapeHtml(uploadRoot)}</code>
                 </div>`
              : ''
          }
        </div>
      `;
    }
  }

  summaryEl.innerHTML = `
  <div class="card mb-2" style="border-radius: var(--menu-radius); overflow: hidden;">
    <div class="card-body py-2">
      <div class="row">
        <div class="col-12 col-md-4">
          <div class="fw-bold">
            ${tf('storage_total_used', 'Total used (FileRise snapshot)')}
          </div>
          <div>${totalSizeStr}</div>
        </div>
        <div class="col-6 col-md-4">
          <div class="fw-bold">
            ${tf('storage_total_files', 'Total files')}
          </div>
          <div>${totalFiles.toLocaleString()}</div>
        </div>
        <div class="col-6 col-md-4 text-md-end">
          <div class="fw-bold">
            ${tf('storage_total_folders', 'Total folders')}
          </div>
          <div>${totalFolders.toLocaleString()}</div>
        </div>
      </div>
      <div class="mt-2 small text-muted">
        ${tf('storage_last_scan', 'Last scan:')} ${scannedAtStr}
        ${scanSeconds ? ` &middot; ${scanSeconds.toFixed(1)}s` : ''}
      </div>
      ${rootVolumeHtml}
    </div>
  </div>

    <div class="card" style="border-radius: var(--menu-radius); overflow: hidden;">
      <div class="card-header py-2">
        <strong>${tf('storage_top_folders', 'Top folders by size')}</strong>
      </div>
      <div class="card-body p-0">
        <div class="table-responsive">
          <table class="table table-sm mb-0">
            <thead>
              <tr>
                <th>${tf('folder', 'Folder')}</th>
                <th>${tf('size', 'Size')}</th>
                <th>%</th>
                <th style="width:40%;">${tf('usage', 'Usage')}</th>
              </tr>
            </thead>
            <tbody>
              ${topRows}
            </tbody>
          </table>
        </div>
      <div class="d-flex justify-content-end px-2 py-1 border-top small" id="adminStorageTopFoldersMoreWrap"></div>
    </div>
  `;
      // Make "Top folders by size" clickable for Pro: jump into explorer
  if (isProGlobal) {
    summaryEl.querySelectorAll('.admin-storage-summary-folder-link').forEach(btn => {
      btn.addEventListener('click', () => {
        const folder = btn.getAttribute('data-folder') || 'root';
        const host = document.getElementById('adminStorageProTeaser');
        if (host && !document.getElementById('adminStorageExplorerInner')) {
          renderProExplorerSkeleton();
        }
        switchExplorerTab('folders');
        currentFolderKey = folder;
        loadFolderChildren(folder);
      });
    });
  }

  // Pro: "Show more / Show less" for Top folders by size
  const moreWrap = summaryEl.querySelector('#adminStorageTopFoldersMoreWrap');
  if (isProGlobal && moreWrap && topFolders.length > 5) {
    const label = showAllTopFolders
      ? tf('storage_top_folders_show_less', 'Show top 5')
      : tf('storage_top_folders_show_more', 'Show more');

      moreWrap.innerHTML = `
      <button type="button" class="btn btn-sm btn-link p-0" id="adminStorageTopFoldersToggle">
        ${label}
        <span class="badge bg-warning text-dark ms-1 align-middle">Pro</span>
      </button>
    `;

    const toggleBtn = moreWrap.querySelector('#adminStorageTopFoldersToggle');
    if (toggleBtn) {
      toggleBtn.addEventListener('click', () => {
        showAllTopFolders = !showAllTopFolders;
        refreshStorageSummary();
      });
    }
  }
}

// ---------- Scan polling ----------

/**
 * Poll for a new snapshot after a rescan is triggered.
 * We don't know real progress %, but as soon as generatedAt increases
 * we refresh the summary and stop polling.
 */
function startScanPolling(initialGeneratedAt, { logPath = '', logMtime = null } = {}) {
  if (scanPollTimer) {
    clearInterval(scanPollTimer);
    scanPollTimer = null;
  }

  setScanStatus(true);
  const startTime = Date.now();
  const maxMs = 10 * 60 * 1000; // 10 minutes safety
  let lastSeenLogMtime = logMtime ?? null;
  let lastSeenLogTail = '';
  let lastSeenErrorMsg = '';
  let lastSeenLogPath = logPath || '';
  const stallWarnMs = 60 * 1000; // warn if nothing moves after 60s

  scanPollTimer = window.setInterval(async () => {
    if (Date.now() - startTime > maxMs) {
      clearInterval(scanPollTimer);
      scanPollTimer = null;
      setScanStatus(false);
      renderScanProblem({
        message: lastSeenErrorMsg || tf('storage_scan_timeout', 'Disk usage scan did not finish within 10 minutes. See the scan log for details.'),
        logTail: lastSeenLogTail,
        logPath: lastSeenLogPath || lastScanLogPath
      });
      return;
    }

    const data = await fetchSummaryRaw();
    if (!data) return;

    const logInfo = data.scanLog || null;
    const gen = data.generatedAt || 0;

    if (logInfo) {
      if (logInfo.path) {
        lastSeenLogPath = logInfo.path;
        lastScanLogPath = logInfo.path;
      }
      if (typeof logInfo.modifiedAt === 'number' && logInfo.modifiedAt > 0) {
        if (lastSeenLogMtime === null) {
          lastSeenLogMtime = logInfo.modifiedAt;
        } else if (logInfo.hasError && logInfo.modifiedAt > lastSeenLogMtime && (!gen || gen <= initialGeneratedAt)) {
          clearInterval(scanPollTimer);
          scanPollTimer = null;
          setScanStatus(false);
          renderScanProblem({
            message: lastSeenErrorMsg || (logInfo.tail ? logInfo.tail.split('\n').pop() : ''),
            logTail: logInfo.tail || lastSeenLogTail,
            logPath: lastSeenLogPath
          });
          return;
        }
        lastSeenLogMtime = logInfo.modifiedAt;
      }
      if (logInfo.tail) {
        lastSeenLogTail = logInfo.tail;
      }
    }

    if (!data.ok) {
      if (data.message) {
        lastSeenErrorMsg = String(data.message);
      }
      if (!lastSeenLogMtime && Date.now() - startTime > stallWarnMs) {
        // No snapshot change and no log activity: likely worker not running or empty
        clearInterval(scanPollTimer);
        scanPollTimer = null;
        setScanStatus(false);
        renderScanProblem({
          message: tf('storage_scan_no_progress', 'Disk usage scan did not produce a snapshot yet. Verify the CLI worker (src/cli/disk_usage_scan.php) and PHP CLI path.'),
          logTail: lastSeenLogTail,
          logPath: lastSeenLogPath || lastScanLogPath
        });
      }
      // still no snapshot / error, keep waiting
      return;
    }

    if (gen && gen > initialGeneratedAt) {
      clearInterval(scanPollTimer);
      scanPollTimer = null;
      lastGeneratedAt = gen;
      // refresh full UI once
      await refreshStorageSummary();
      setScanStatus(false);
      showToast(tf('storage_scan_complete', 'Disk usage scan completed.'));
      // refresh explorer data if visible
      if (isProGlobal) {
        if (currentExplorerTab === 'folders') {
          loadFolderChildren(currentFolderKey);
        } else {
          loadTopFiles();
        }
      }
      const hintEl = document.getElementById('adminStorageScanHint');
      if (hintEl) {
        hintEl.textContent = defaultScanHint || getDefaultScanHint(isProGlobal);
      }
    }
  }, 4000);
}

/**
 * Wire the Rescan button (Pro) to /api/pro/diskUsageTriggerScan.php
 */
function wireRescan(/* isPro */) {
  const btn = document.getElementById('adminStorageRescan');
  const hintEl = document.getElementById('adminStorageScanHint');
  const deleteBtn = document.getElementById('adminStorageDeleteSnapshot');
  if (!btn) return;

  if (btn.dataset.wired === '1') return;
  btn.dataset.wired = '1';

  btn.addEventListener('click', async () => {
    if (!isCurrentSourceLocal()) {
      showToast(tf('storage_source_unsupported', 'Storage explorer is only available for local sources.'));
      return;
    }
    const initialGenerated = lastGeneratedAt || 0;

    btn.disabled = true;
    const oldHtml = btn.innerHTML;
    btn.innerHTML = `
      <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
      <span class="ms-1">${tf('scanning', 'Scanning...')}</span>
    `;

    try {
      const payload = await sendRequest('/api/admin/diskUsageTriggerScan.php', 'POST', getSourcePayload(), {
        'X-CSRF-Token': getCsrfToken()
      });

      if (!payload || payload.ok !== true) {
        showToast(
          tf('storage_rescan_failed', 'Failed to start scan (see logs).')
        );
      } else {
        showToast(
          tf('storage_rescan_started', 'Disk usage scan started in the background.')
        );
        lastScanLogPath = payload.logFile || '';
        lastScanLogMtime = payload.logMtime || null;
        if (hintEl) {
          hintEl.textContent = tf(
            'storage_rescan_hint_with_log',
            'Scan is running in the background. The summary will update when it finishes.'
          );
        }
        startScanPolling(initialGenerated, {
          logPath: lastScanLogPath,
          logMtime: lastScanLogMtime
        });
      }
    } catch (err) {
      console.error('Rescan error', err);
      showToast(
        tf('storage_rescan_failed', 'Failed to start scan (see logs).')
      );
      setScanStatus(false);
    } finally {
      btn.disabled = false;
      btn.innerHTML = oldHtml;
    }
  });

  if (deleteBtn && !deleteBtn.dataset.wired) {
    deleteBtn.dataset.wired = '1';
    deleteBtn.addEventListener('click', async () => {
      const ok = await showConfirmDialog({
        title: tf('storage_delete_snapshot','Delete snapshot'),
        message: tf(
          'storage_delete_snapshot_confirm',
          'Delete the current disk usage snapshot? The Storage view will be empty until a new scan finishes.'
        ),
        confirmLabel: tf('delete','Delete')
      });
      if (!ok) return;

      deleteBtn.disabled = true;
      const old = deleteBtn.innerHTML;
      deleteBtn.innerHTML = `
        <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
        <span class="ms-1">${tf('deleting','Deleting...')}</span>
      `;

      try {
        const resp = await sendRequest('/api/admin/diskUsageDeleteSnapshot.php', 'POST', getSourcePayload(), {
          'X-CSRF-Token': getCsrfToken()
        });
        if (!resp || resp.ok !== true) {
          showToast(tf('storage_delete_snapshot_failed','Failed to delete snapshot. See logs.'));
        } else {
          lastGeneratedAt = 0;
          lastScanLogMtime = null;
          refreshStorageSummary();
          if (isProGlobal) {
            if (currentExplorerTab === 'folders') {
              loadFolderChildren('root');
            } else {
              loadTopFiles();
            }
          }
          showToast(tf('storage_delete_snapshot_ok','Snapshot deleted. Run Rescan to build a new one.'));
          const hintEl = document.getElementById('adminStorageScanHint');
          if (hintEl) {
            hintEl.textContent = defaultScanHint || getDefaultScanHint(isProGlobal);
          }
        }
      } catch (e) {
        console.error('Delete snapshot error', e);
        showToast(tf('storage_delete_snapshot_failed','Failed to delete snapshot. See logs.'));
      } finally {
        deleteBtn.disabled = false;
        deleteBtn.innerHTML = old;
      }
    });
  }
}

// ---------- Pro Explorer (ncdu-style) ----------

function renderProExplorerSkeleton() {
    const host = document.getElementById('adminStorageProTeaser');
    if (!host || host.dataset.inited === '1') return;
    host.dataset.inited = '1';
  
    host.innerHTML = `
      <div class="card" style="border-radius: var(--menu-radius); overflow:hidden;">
        <div class="card-header py-2">
          <div class="d-flex justify-content-between align-items-center">
            <div>
              <strong>
  ${tf('storage_explorer', 'Storage explorer')}
  <span class="badge bg-warning text-dark ms-1 align-middle">Pro</span>
</strong>
              <div class="small text-muted">
                ${tf('storage_explorer_help', 'Drill down into folders or inspect the largest files.')}
              </div>
            </div>
            <div class="d-flex align-items-center gap-3">
              <div class="btn-group btn-group-sm" role="group" id="adminStorageExplorerTabs">
                <button
                  type="button"
                  id="adminStorageTabFolders"
                  class="btn btn-outline-secondary active"
                  data-tab="folders">
                  ${tf('storage_tab_folders','Folders')}
                </button>
                <button
                  type="button"
                  id="adminStorageTabTopFiles"
                  class="btn btn-outline-secondary"
                  data-tab="topfiles">
                  ${tf('storage_tab_topfiles','Top files')}
                </button>
              </div>
              <div class="form-check fr-toggle">
                <input
                  class="form-check-input fr-toggle-input"
                  type="checkbox"
                  id="adminStorageDeepDeleteToggle">
                <label class="form-check-label small" for="adminStorageDeepDeleteToggle">
  ${tf('storage_deep_delete_toggle', 'Deep delete')}
  <span class="badge bg-warning text-dark ms-1 align-middle">Pro</span>
</label>
              </div>
            </div>
          </div>
        </div>
        <div class="card-body p-2">
          <div class="row g-2 mb-2">
  <div class="col-12 col-md-6" id="adminStorageFolderFilterWrap">
    <label class="form-label form-label-sm mb-0">
      ${tf('storage_folder_min_size','Min folder/file size')}
    </label>
    <select id="adminStorageFolderMinSize" class="form-select form-select-sm">
      <option value="0">${tf('storage_any_size','Any size')}</option>
      <option value="1048576">≥ 1 MB</option>
      <option value="10485760">≥ 10 MB</option>
      <option value="52428800">≥ 50 MB</option>
      <option value="104857600">≥ 100 MB</option>
      <option value="1073741824">≥ 1 GB</option>
    </select>
  </div>
  <div class="col-12 col-md-6" id="adminStorageTopFilesFilterWrap">
    <label class="form-label form-label-sm mb-0">
      ${tf('storage_topfiles_min_size','Min file size (Top files)')}
    </label>
    <select id="adminStorageTopFilesMinSize" class="form-select form-select-sm">
      <option value="0">${tf('storage_any_size','Any size')}</option>
      <option value="1048576">≥ 1 MB</option>
      <option value="10485760">≥ 10 MB</option>
      <option value="52428800">≥ 50 MB</option>
      <option value="104857600">≥ 100 MB</option>
      <option value="1073741824">≥ 1 GB</option>
    </select>
  </div>
</div>
          </div>
  
          <div class="mb-2 small" id="adminStorageBreadcrumb"></div>
          <div id="adminStorageExplorerInner" class="small"></div>
        </div>
      </div>
    `;
  
    wireDeepDeleteToggle();
    deepDeleteEnabled = false;
    updateDeleteButtonsForDeepDelete();

  const tabFolders = document.getElementById('adminStorageTabFolders');
  const tabTopFiles = document.getElementById('adminStorageTabTopFiles');
  const folderMin = document.getElementById('adminStorageFolderMinSize');
  const topMin = document.getElementById('adminStorageTopFilesMinSize');

  if (tabFolders && tabTopFiles) {
    tabFolders.addEventListener('click', () => {
      switchExplorerTab('folders');
    });
    tabTopFiles.addEventListener('click', () => {
      switchExplorerTab('topFiles');
    });
  }

  if (folderMin) {
    folderMin.addEventListener('change', () => {
      folderMinSizeBytes = Number(folderMin.value || '0') || 0;
      if (currentExplorerTab === 'folders') {
        loadFolderChildren(currentFolderKey);
      }
    });
  }

  if (topMin) {
    topMin.addEventListener('change', () => {
      topFilesMinSizeBytes = Number(topMin.value || '0') || 0;
      if (currentExplorerTab === 'topFiles') {
        loadTopFiles();
      }
    });
  }
}

function setBreadcrumb(folderKey) {
  const el = document.getElementById('adminStorageBreadcrumb');
  if (!el) return;

  // Clear existing content safely
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }

  let parts = [];
  if (folderKey && folderKey !== 'root') {
    const clean = folderKey.replace(/^\/+|\/+$/g, '');
    parts = clean ? clean.split('/') : [];
  }

  // Helper: add a simple separator " / "
  const appendSeparator = () => {
    const sep = document.createElement('span');
    sep.className = 'mx-1';
    sep.textContent = '/';
    el.appendChild(sep);
  };

  // Root crumb
  const rootBtn = document.createElement('button');
  rootBtn.type = 'button';
  rootBtn.className = 'btn btn-link btn-sm p-0 admin-storage-bc';
  rootBtn.dataset.folder = 'root';

  const rootIcon = document.createElement('span');
  rootIcon.className = 'material-icons';
  rootIcon.style.fontSize = '16px';
  rootIcon.style.verticalAlign = 'middle';
  rootIcon.style.color = 'currentColor';
  rootIcon.textContent = 'home';

  const rootText = document.createElement('span');
  rootText.style.verticalAlign = 'middle';
  rootText.textContent = tf('storage_root_label', 'root');

  rootBtn.appendChild(rootIcon);
  rootBtn.appendChild(rootText);
  el.appendChild(rootBtn);

  let accum = '';
  parts.forEach((p, idx) => {
    appendSeparator();
    accum = accum ? (accum + '/' + p) : p;
    const isLast = idx === parts.length - 1;

    if (isLast) {
      const span = document.createElement('span');
      span.className = 'fw-semibold';
      span.textContent = p;
      el.appendChild(span);
    } else {
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'btn btn-link btn-sm p-0 admin-storage-bc';
      btn.dataset.folder = accum;
      btn.textContent = p;
      el.appendChild(btn);
    }
  });

  // breadcrumb click handling
  el.querySelectorAll('.admin-storage-bc').forEach(btn => {
    btn.addEventListener('click', () => {
      const f = btn.getAttribute('data-folder') || 'root';
      currentFolderKey = f;
      loadFolderChildren(f);
    });
  });
}

/**
 * Switch between "folders" and "topFiles" tabs.
 */
function switchExplorerTab(tab) {
  currentExplorerTab = tab === 'topFiles' ? 'topFiles' : 'folders';

  const tabFolders = document.getElementById('adminStorageTabFolders');
  const tabTopFiles = document.getElementById('adminStorageTabTopFiles');
  const folderFilterWrap = document.getElementById('adminStorageFolderFilterWrap');
  const topFilterWrap = document.getElementById('adminStorageTopFilesFilterWrap');

  if (tabFolders && tabTopFiles) {
    if (currentExplorerTab === 'folders') {
      tabFolders.classList.add('active');
      tabTopFiles.classList.remove('active');
    } else {
      tabTopFiles.classList.add('active');
      tabFolders.classList.remove('active');
    }
  }

  if (folderFilterWrap && topFilterWrap) {
    folderFilterWrap.style.display = currentExplorerTab === 'folders' ? '' : 'none';
    topFilterWrap.style.display = currentExplorerTab === 'topFiles' ? '' : 'none';
  }

  if (currentExplorerTab === 'folders') {
    loadFolderChildren(currentFolderKey);
  } else {
    setBreadcrumb('root'); // breadcrumb not super meaningful for global top files, but keep root
    loadTopFiles();
  }
}

async function loadFolderChildren(folderKey) {
  currentFolderKey = folderKey || 'root';

  const inner = document.getElementById('adminStorageExplorerInner');
  if (!inner) return;

  inner.innerHTML = `
    <div class="text-muted small">
      ${tf('loading','Loading...')}
    </div>
  `;

  if (!isCurrentSourceLocal()) {
    inner.innerHTML = `<div class="text-warning small">
      ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
    </div>`;
    setBreadcrumb('root');
    return;
  }

  if (!isCurrentSourceLocal()) {
    inner.innerHTML = `<div class="text-warning small">
      ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
    </div>`;
    setBreadcrumb('root');
    return;
  }

  setBreadcrumb(currentFolderKey);

  let data;
  try {
    const url = `/api/pro/diskUsageChildren.php?folder=${encodeURIComponent(currentFolderKey)}${getSourceQueryParam()}`;
    const res = await fetch(url, {
      credentials: 'include',
      cache: 'no-store',
      headers: { 'Cache-Control': 'no-store' }
    });
    const text = await res.text();
    data = JSON.parse(text || '{}');
  } catch (e) {
    console.error('loadFolderChildren error', e);
    inner.innerHTML = `<div class="text-danger small">
      ${tf('storage_children_error','Please upgrade to the latest FileRise Pro bundle to use the Storage explorer.')}
    </div>`;
    return;
  }

  if (!data || !data.ok) {
    if (data && data.error === 'unsupported_source') {
      inner.innerHTML = `<div class="text-warning small">
        ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
      </div>`;
      return;
    }
    if (data && data.error === 'invalid_source') {
      inner.innerHTML = `<div class="text-danger small">
        ${tf('storage_source_invalid', 'Invalid source selected for disk usage.')}
      </div>`;
      return;
    }
    if (data && data.error === 'no_snapshot') {
      inner.innerHTML = `<div class="text-warning small">
        ${tf('storage_no_snapshot','No disk usage snapshot found. Run the disk usage scan first.')}
      </div>`;
    } else {
      // Special-case: backend missing ProDiskUsage / outdated Pro bundle
      let msgKey = 'storage_children_error';
      let fallback = 'Please upgrade to the latest FileRise Pro bundle to use the Storage explorer.';

      if (
        data &&
        data.error === 'internal_error' &&
        data.message &&
        /ProDiskUsage/i.test(String(data.message))
      ) {
        msgKey = 'storage_pro_bundle_outdated';
        fallback = 'Please upgrade to the latest FileRise Pro bundle to use the Storage explorer.';
      }

      inner.innerHTML = `<div class="text-danger small">
        ${tf(msgKey, fallback)}
      </div>`;
    }
    return;
  }

  const folders = Array.isArray(data.folders) ? data.folders : [];
  const files   = Array.isArray(data.files)   ? data.files   : [];

  const minBytes = folderMinSizeBytes || 0;

  const filteredFolders = folders.filter(f => Number(f.bytes || 0) >= minBytes);
  const filteredFiles   = files.filter(f => Number(f.bytes || 0) >= minBytes);

  // Build a unified list for pagination: { kind: 'folder' | 'file', item }
  const entries = [];
  filteredFolders.forEach(f => entries.push({ kind: 'folder', item: f }));
  filteredFiles.forEach(file => entries.push({ kind: 'file', item: file }));

  const total = entries.length;
  const pageSize = 100;
  let shown = Math.min(pageSize, total);

  function renderPage() {
    const hasRows = total > 0;
    const slice = hasRows ? entries.slice(0, shown) : [];

    // Clear container
    inner.innerHTML = '';

    // Table wrapper
    const wrapper = document.createElement('div');
    wrapper.className = 'table-responsive';
    wrapper.style.maxHeight = '340px';
    wrapper.style.overflow = 'auto';

    const table = document.createElement('table');
    table.className = 'table table-sm mb-0';

    // ----- thead -----
    const thead = document.createElement('thead');
    const headRow = document.createElement('tr');

    const thName = document.createElement('th');
    thName.textContent = tf('name','Name');
    headRow.appendChild(thName);

    const thSize = document.createElement('th');
    thSize.textContent = tf('size','Size');
    headRow.appendChild(thSize);

    const thPct = document.createElement('th');
    thPct.textContent = '%';
    headRow.appendChild(thPct);

    const thFiles = document.createElement('th');
    thFiles.textContent = tf('files','Files');
    headRow.appendChild(thFiles);

    const thMod = document.createElement('th');
    thMod.textContent = tf('modified','Modified');
    headRow.appendChild(thMod);

    const thActions = document.createElement('th');
    thActions.style.width = '1%';
    headRow.appendChild(thActions);

    thead.appendChild(headRow);
    table.appendChild(thead);

    // ----- tbody -----
    const tbody = document.createElement('tbody');

    if (!hasRows) {
      const tr = document.createElement('tr');
      const td = document.createElement('td');
      td.colSpan = 6;
      td.className = 'text-muted small';
      td.textContent = tf(
        'storage_no_children',
        'No matching items in this folder (for current filter).'
      );
      tr.appendChild(td);
      tbody.appendChild(tr);
    } else {
      slice.forEach(entry => {
        const { kind, item } = entry;
        const tr = document.createElement('tr');
        tr.classList.add('admin-storage-row');

        if (kind === 'folder') {
          // ---- Folder row ----
          tr.classList.add('admin-storage-row-folder');
          tr.dataset.type = 'folder';
          tr.dataset.folder = item.folder;

          const label = item.folder === 'root' ? '/' : `/${item.folder}`;
          const pct   = item.percentOfTotal || 0;

          // Name cell
          const tdName = document.createElement('td');
          tdName.className = 'align-middle';

          const icon = document.createElement('i');
          icon.className = 'material-icons';
          icon.style.fontSize = '16px';
          icon.style.verticalAlign = 'middle';
          icon.style.color = 'currentColor';
          icon.textContent = 'folder';

          const span = document.createElement('span');
          span.className = 'ms-1 align-middle';
          span.textContent = label;

          tdName.appendChild(icon);
          tdName.appendChild(span);
          tr.appendChild(tdName);

          // Size
          const tdSize = document.createElement('td');
          tdSize.className = 'align-middle text-nowrap';
          tdSize.textContent = formatBytes(item.bytes || 0);
          tr.appendChild(tdSize);

          // Percent
          const tdPct = document.createElement('td');
          tdPct.className = 'align-middle text-nowrap';
          tdPct.textContent = `${pct.toFixed(1)}%`;
          tr.appendChild(tdPct);

          // Files count
          const tdFiles = document.createElement('td');
          tdFiles.className = 'align-middle text-nowrap';
          tdFiles.textContent = (item.files || 0).toLocaleString();
          tr.appendChild(tdFiles);

          // Modified
          const tdMod = document.createElement('td');
          tdMod.className = 'align-middle text-nowrap';
          tdMod.textContent = item.latest_mtime ? formatDate(item.latest_mtime) : '';
          tr.appendChild(tdMod);

          // Actions
          const tdActions = document.createElement('td');
          tdActions.className = 'align-middle';
          tdActions.style.width = '1%';
          tdActions.style.whiteSpace = 'nowrap';

          const btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'btn btn-sm btn-outline-danger admin-storage-delete-folder';
          btn.title = tf('delete_folder','Delete folder');

          const delIcon = document.createElement('i');
          delIcon.className = 'material-icons';
          delIcon.style.fontSize = '16px';
          delIcon.style.color = 'currentColor';
          delIcon.textContent = 'delete';

          btn.appendChild(delIcon);
          tdActions.appendChild(btn);
          tr.appendChild(tdActions);

          // Folder row click: drilldown
          tr.addEventListener('click', e => {
            if (e.target.closest('.admin-storage-delete-folder')) return;
            const folder = tr.getAttribute('data-folder') || 'root';
            currentFolderKey = folder;
            loadFolderChildren(folder);
          });

          // Delete folder click
          btn.addEventListener('click', async e => {
            e.stopPropagation();
            const folder = tr.getAttribute('data-folder') || '';
            if (!folder) return;

            const labelDisp = folder === 'root' ? '/' : `/${folder}`;

            if (deepDeleteEnabled) {
              // SUPER DANGEROUS: deep delete entire subtree
              const ok = await showConfirmDialog({
                title: tf('storage_confirm_deep_delete_folder_title', 'Deep delete folder'),
                message: tf(
                  'storage_confirm_deep_delete_folder_msg',
                  `Permanently delete folder ${labelDisp} and ALL files and subfolders under it? This cannot be undone.`
                ),
                confirmLabel: tf('deep_delete_folder','Deep delete')
              });
              if (!ok) return;

              await deleteFolderFromInspector(folder, tr, { deep: true });
            } else {
              // Safe mode: only empty folders, same as existing UI
              const ok = await showConfirmDialog({
                title: tf('storage_confirm_delete_folder_title', 'Delete folder'),
                message: tf(
                  'storage_confirm_delete_folder_msg',
                  `Delete folder ${labelDisp}? If the folder is not empty, deletion will fail.`
                ),
                confirmLabel: tf('delete_folder','Delete folder')
              });
              if (!ok) return;

              await deleteFolderFromInspector(folder, tr, { deep: false });
            }
          });
        } else {
          // ---- File row ----
          tr.classList.add('admin-storage-row-file');
          tr.dataset.type = 'file';

          const folder = item.folder || currentFolderKey;
          const displayPath = item.path || (folder === 'root'
            ? `/${item.name}`
            : `/${folder}/${item.name}`);
          const pct   = item.percentOfTotal || 0;

          tr.dataset.folder = folder;
          tr.dataset.name = item.name;

          // Name cell
          const tdName = document.createElement('td');
          tdName.className = 'align-middle';

          const icon = document.createElement('i');
          icon.className = 'material-icons';
          icon.style.fontSize = '16px';
          icon.style.verticalAlign = 'middle';
          icon.style.color = 'currentColor';
          icon.textContent = 'insert_drive_file';

          const span = document.createElement('span');
          span.className = 'ms-1 align-middle';

          const code = document.createElement('code');
          code.textContent = displayPath;

          span.appendChild(code);
          tdName.appendChild(icon);
          tdName.appendChild(span);
          tr.appendChild(tdName);

          // Size
          const tdSize = document.createElement('td');
          tdSize.className = 'align-middle text-nowrap';
          tdSize.textContent = formatBytes(item.bytes || 0);
          tr.appendChild(tdSize);

          // Percent
          const tdPct = document.createElement('td');
          tdPct.className = 'align-middle text-nowrap';
          tdPct.textContent = `${pct.toFixed(2)}%`;
          tr.appendChild(tdPct);

          // Files (blank for files)
          const tdFiles = document.createElement('td');
          tdFiles.className = 'align-middle text-nowrap';
          tdFiles.textContent = '';
          tr.appendChild(tdFiles);

          // Modified
          const tdMod = document.createElement('td');
          tdMod.className = 'align-middle text-nowrap';
          tdMod.textContent = item.mtime ? formatDate(item.mtime) : '';
          tr.appendChild(tdMod);

          // Actions
          const tdActions = document.createElement('td');
          tdActions.className = 'align-middle';
          tdActions.style.width = '1%';
          tdActions.style.whiteSpace = 'nowrap';

          const btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'btn btn-sm btn-outline-danger admin-storage-delete-file';
          btn.title = tf('delete_file','Delete file');

          const delIcon = document.createElement('i');
          delIcon.className = 'material-icons';
          delIcon.style.fontSize = '16px';
          delIcon.style.color = 'currentColor';
          delIcon.textContent = 'delete';

          btn.appendChild(delIcon);
          tdActions.appendChild(btn);
          tr.appendChild(tdActions);

          btn.addEventListener('click', async e => {
            e.stopPropagation();
            const f = tr.getAttribute('data-folder') || currentFolderKey || 'root';
            const name = tr.getAttribute('data-name') || '';
            if (!name) return;

            const display = f === 'root' ? `/${name}` : `/${f}/${name}`;
            const ok = await showConfirmDialog({
              title: tf('storage_confirm_delete_file_title', 'Permanently delete file'),
              message: tf(
                'storage_confirm_delete_file_msg',
                `Permanently delete file ${display}? This bypasses Trash and cannot be undone.`
              ),
              confirmLabel: tf('delete_file','Delete file')
            });
            if (!ok) return;

            await deleteFileFromInspectorPermanent(f, name, tr);
          });
        }

        tbody.appendChild(tr);
      });
    }

    table.appendChild(tbody);
    wrapper.appendChild(table);
    inner.appendChild(wrapper);

    // ----- Footer: showing X of Y / Load more -----
    if (hasRows) {
      if (shown < total) {
        const footer = document.createElement('div');
        footer.className = 'd-flex justify-content-between align-items-center mt-1 small';

        const span = document.createElement('span');
        span.textContent = `${tf('storage_showing','Showing')} ${shown} ${tf('of','of')} ${total}`;
        footer.appendChild(span);

        const moreBtn = document.createElement('button');
        moreBtn.type = 'button';
        moreBtn.className = 'btn btn-sm btn-outline-secondary';
        moreBtn.id = 'adminStorageMoreFolder';
        moreBtn.textContent = tf('storage_load_more','Load more');

        moreBtn.addEventListener('click', () => {
          shown = Math.min(shown + pageSize, total);
          renderPage();
        });

        footer.appendChild(moreBtn);
        inner.appendChild(footer);
      } else {
        const footer = document.createElement('div');
        footer.className = 'mt-1 small text-muted text-end pe-2';
        footer.textContent = `${tf('storage_showing_all','Showing all')} ${total} ${tf('items','items')}.`;
        inner.appendChild(footer);
      }
    }

    // Sync button styles with deep delete toggle
    updateDeleteButtonsForDeepDelete();
  }

  renderPage();
}

  async function loadTopFiles() {
    const inner = document.getElementById('adminStorageExplorerInner');
    if (!inner) return;
  
    inner.innerHTML = `
      <div class="text-muted small">
        ${tf('loading','Loading...')}
      </div>
    `;
  
    let data;
    try {
      const url = `/api/pro/diskUsageTopFiles.php?limit=200${getSourceQueryParam()}`;
      const res = await fetch(url, {
        credentials: 'include',
        cache: 'no-store',
        headers: { 'Cache-Control': 'no-store' }
      });
      const text = await res.text();
      data = JSON.parse(text || '{}');
    } catch (e) {
      console.error('loadTopFiles error', e);
      inner.innerHTML = `<div class="text-danger small">
        ${tf('storage_topfiles_error','Unable to load top files.')}
      </div>`;
      return;
    }
  
    if (!data || !data.ok) {
      if (data && data.error === 'unsupported_source') {
        inner.innerHTML = `<div class="text-warning small">
          ${tf('storage_source_unsupported', 'Storage explorer is only available for local sources.')}
        </div>`;
        return;
      }
      if (data && data.error === 'invalid_source') {
        inner.innerHTML = `<div class="text-danger small">
          ${tf('storage_source_invalid', 'Invalid source selected for disk usage.')}
        </div>`;
        return;
      }
      if (data && data.error === 'no_snapshot') {
        inner.innerHTML = `<div class="text-warning small">
          ${tf('storage_no_snapshot','No disk usage snapshot found. Run the disk usage scan first.')}
        </div>`;
      } else {
        inner.innerHTML = `<div class="text-danger small">
          ${tf('storage_topfiles_error','Unable to load top files.')}
        </div>`;
      }
      return;
    }
  
    const files = Array.isArray(data.files) ? data.files : [];
    const minBytes = topFilesMinSizeBytes || 0;
  
    const filtered = files.filter(f => Number(f.bytes || 0) >= minBytes);
  
    const rowChunks = filtered.map(file => {
      const bytes = Number(file.bytes || 0);
      const pct   = file.percentOfTotal || 0;
      const width = Math.max(3, Math.min(100, Math.round(pct)));
      const folder = file.folder || 'root';
      const path   = file.path || (folder === 'root'
        ? `/${file.name}`
        : `/${folder}/${file.name}`);
  
      return `
        <tr
          class="admin-storage-row admin-storage-row-file"
          data-type="file"
          data-folder="${folder}"
          data-name="${file.name}">
          <td class="align-middle">
            <i class="material-icons" style="font-size:16px;vertical-align:middle;color:currentColor;">insert_drive_file</i>
            <span class="ms-1 align-middle"><code>${path}</code></span>
          </td>
          <td class="align-middle text-nowrap">
            ${formatBytes(bytes)}
          </td>
          <td class="align-middle text-nowrap">
            ${pct.toFixed(2)}%
          </td>
          <td class="align-middle" style="width:40%;">
            <div class="progress" style="height: 6px; background-color: rgba(0,0,0,0.05);">
              <div
                class="progress-bar"
                role="progressbar"
                style="width:${width}%;"
                aria-valuenow="${pct.toFixed(2)}"
                aria-valuemin="0"
                aria-valuemax="100">
              </div>
            </div>
          </td>
          <td class="align-middle text-nowrap">
            ${file.mtime ? formatDate(file.mtime) : ''}
          </td>
          <td class="align-middle" style="width:1%;white-space:nowrap;">
            <button
              type="button"
              class="btn btn-sm btn-outline-danger admin-storage-delete-file"
              title="${tf('delete_file','Delete file')}">
              <i class="material-icons" style="font-size:16px;color:currentColor;">delete</i>
            </button>
          </td>
        </tr>
      `;
    });
  
    const total = rowChunks.length;
    const pageSize = 100;
    let shown = Math.min(pageSize, total);
  
    function renderPage() {
      const hasRows = total > 0;
      const visibleRows = hasRows
        ? rowChunks.slice(0, shown).join('')
        : `<tr><td colspan="6" class="text-muted small">
             ${tf('storage_no_topfiles','No files match the current filter.')}
           </td></tr>`;
  
      const footer = hasRows && shown < total
        ? `<div class="d-flex justify-content-between align-items-center mt-1 small">
             <span>${tf('storage_showing','Showing')} ${shown} ${tf('of','of')} ${total}</span>
             <button type="button" class="btn btn-sm btn-outline-secondary" id="adminStorageMoreTopFiles">
               ${tf('storage_load_more','Load more')}
             </button>
           </div>`
        : hasRows
          ? `<div class="mt-1 small text-muted text-end pe-2">
               ${tf('storage_showing_all','Showing all')} ${total} ${tf('items','items')}.
             </div>`
          : '';
  
      inner.innerHTML = `
        <div class="table-responsive" style="max-height:340px;overflow:auto;">
          <table class="table table-sm mb-0">
            <thead>
              <tr>
                <th>${tf('file','File')}</th>
                <th>${tf('size','Size')}</th>
                <th>%</th>
                <th>${tf('usage','Usage')}</th>
                <th>${tf('modified','Modified')}</th>
                <th style="width:1%;"></th>
              </tr>
            </thead>
            <tbody>
              ${visibleRows}
            </tbody>
          </table>
        </div>
        ${footer}
      `;
  
      inner.querySelectorAll('.admin-storage-delete-file').forEach(btn => {
        btn.addEventListener('click', async e => {
          e.stopPropagation();
          const row = btn.closest('tr');
          if (!row) return;
          const folder = row.getAttribute('data-folder') || currentFolderKey || 'root';
          const name   = row.getAttribute('data-name') || '';
          if (!name) return;
      
          const display = folder === 'root' ? `/${name}` : `/${folder}/${name}`;
          const ok = await showConfirmDialog({
            title: tf('storage_confirm_delete_file_title', 'Permanently delete file'),
            message: tf(
              'storage_confirm_delete_file_msg',
              `Permanently delete file ${display}? This bypasses Trash and cannot be undone.`
            ),
            confirmLabel: tf('delete_file','Delete file')
          });
          if (!ok) return;
      
          await deleteFileFromInspectorPermanent(folder, name, row);
        });
      });
  
      const moreBtn = inner.querySelector('#adminStorageMoreTopFiles');
      if (moreBtn) {
        moreBtn.addEventListener('click', () => {
          shown = Math.min(shown + pageSize, total);
          renderPage();
        });
      }
    }
  
    renderPage();
    updateDeleteButtonsForDeepDelete();
    setBreadcrumb('root');
  }

// ---------- Delete helpers (Pro delete-from-inspector) ----------

  async function deleteFileFromInspectorPermanent(folderKey, name, rowEl) {
    const payload = {
      folder: folderKey || 'root',
      name
    };
    if (currentSourceId) {
      payload.sourceId = currentSourceId;
    }
  
    try {
      const resp = await sendRequest('/api/pro/diskUsageDeleteFilePermanent.php', 'POST', payload, {
        'X-CSRF-Token': getCsrfToken()
      });
  
      const hasError = resp && resp.error;
      const ok = resp && resp.ok !== false && !hasError;
  
      if (!ok) {
        const err = hasError ? String(resp.error) : null;
        if (err) {
          showToast(err);
        } else {
          showToast(tf('storage_delete_file_failed','Failed to delete file. See logs.'));
        }
        return;
      }
  
      if (rowEl && rowEl.parentNode) {
        rowEl.parentNode.removeChild(rowEl);
      }
      showToast(tf('storage_delete_file_perm_ok','File permanently deleted (snapshot will update after next scan).'));
    } catch (e) {
      console.error('deleteFileFromInspectorPermanent error', e);
      showToast(tf('storage_delete_file_failed','Failed to delete file. See logs.'));
    }
  }

  async function deleteFolderFromInspector(folderKey, rowEl, { deep = false } = {}) {
    const payload = { folder: folderKey };
    if (currentSourceId) {
      payload.sourceId = currentSourceId;
    }
  
    const url = deep
      ? '/api/pro/diskUsageDeleteFolderRecursive.php'
      : '/api/folder/deleteFolder.php';
  
    try {
      const resp = await sendRequest(url, 'POST', payload, {
        'X-CSRF-Token': getCsrfToken()
      });
  
      const hasError = resp && resp.error;
      const ok = resp && resp.ok !== false && !hasError && !resp.error;
  
      if (!ok) {
        const err = hasError ? String(resp.error) : null;
  
        if (err) {
          showToast(err);
        } else {
          showToast(
            deep
              ? tf('storage_deep_delete_folder_failed','Failed to deep delete folder. See logs.')
              : tf('storage_delete_folder_failed','Failed to delete folder. See logs.')
          );
        }
        return;
      }
  
      if (rowEl && rowEl.parentNode) {
        rowEl.parentNode.removeChild(rowEl);
      }
  
      showToast(
        deep
          ? tf('storage_deep_delete_folder_ok','Folder and all contents deleted (snapshot will update after next scan).')
          : tf('storage_delete_folder_ok','Folder deleted (snapshot will update after next scan).')
      );
    } catch (e) {
      console.error('deleteFolderFromInspector error', e);
      showToast(
        deep
          ? tf('storage_deep_delete_folder_failed','Failed to deep delete folder. See logs.')
          : tf('storage_delete_folder_failed','Failed to delete folder. See logs.')
      );
    }
  }

// ---------- Entry point ----------

export function initAdminStorageSection({ isPro, modalEl }) {
  const container = document.getElementById('storageContent');
  if (!container) return;

  isProGlobal = !!isPro;
  defaultScanHint = getDefaultScanHint(isProGlobal);

  // Make it safe to call multiple times
  if (!container.dataset.inited) {
    container.dataset.inited = '1';
    renderBaseLayout(container, { isPro });
    if (isProGlobal) {
      renderProExplorerSkeleton();
      currentFolderKey = 'root';
      currentExplorerTab = 'folders';
      folderMinSizeBytes = 0;
      topFilesMinSizeBytes = 0;
    }
  } else if (isProGlobal) {
    // Re-open admin panel: make sure explorer still has data
    if (!document.getElementById('adminStorageExplorerInner')) {
      renderProExplorerSkeleton();
    }
  }

  wireRescan(isProGlobal);
  setScanStatus(false);

  if (isProGlobal) {
    initStorageSources().then(() => {
      refreshStorageSummary();
      switchExplorerTab(currentExplorerTab || 'folders');
    });
  } else {
    refreshStorageSummary();
  }
}
