// ========== ERROR LOG VIEWER (DC-052) ==========
// DC-052: Adds Level / Context / Search / Time-range filters, server-side
// pagination with Load More, click-to-expand stack frames, and a distinct
// contexts dropdown backed by /api/v1/error-logs/contexts. Mirrors the
// audit-log UX (DC-050) so operators can drill into a subsystem as easily
// as they can audit who-did-what.
(function() {
  // Inject modal HTML. Same weather-modal shell as audit-log so styles
  // are shared; wider min-width because error stacks need room to breathe.
  injectModal('error-log-modal', `<div id="error-log-modal" class="weather-modal">
    <div class="weather-modal-content" style="min-width: 950px; max-width: 1150px;">
      <h3>📋 Error Logs</h3>
      <p class="modal-subtitle">
        Errors and warnings from the DashCaddy API. Click a row to see the full stack trace.
      </p>

      <div style="display: flex; gap: 12px; margin-bottom: 12px; align-items: center; flex-wrap: wrap;">
        <label class="text-muted-sm">Level:</label>
        <select id="error-log-level" style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.85rem;">
          <option value="">All</option>
          <option value="ERR">Errors</option>
          <option value="WARN">Warnings</option>
          <option value="INFO">Info</option>
          <option value="DEBUG">Debug</option>
        </select>
        <label class="text-muted-sm">Context:</label>
        <select id="error-log-context" style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.85rem; max-width: 220px;">
          <option value="">All</option>
        </select>
        <label class="text-muted-sm" style="margin-left: 8px;">Search:</label>
        <input id="error-log-search" type="search" placeholder="message / stack / ip" style="padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem; min-width: 180px;">
        <label class="text-muted-sm" style="margin-left: 8px;">Since:</label>
        <input id="error-log-since" type="datetime-local" style="padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
        <label class="text-muted-sm">Until:</label>
        <input id="error-log-until" type="datetime-local" style="padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
        <button id="error-log-refresh" class="btn-sm">🔄 Refresh</button>
        <span style="flex: 1;"></span>
        <button id="error-log-clear" style="padding: 6px 12px; font-size: 0.8rem; color: var(--bad-fg); border-color: var(--bad-fg);">🗑️ Clear Log</button>
      </div>

      <div id="error-log-container" class="scroll-container">
        <div class="panel-empty"><span class="brand-spinner"></span> Loading error logs...</div>
      </div>

      <div style="margin-top: 12px; text-align: center;">
        <button id="error-log-load-more" style="display: none; padding: 6px 16px; font-size: 0.8rem;">Load More</button>
      </div>

      <div style="margin-top: 8px; font-size: 0.78rem; color: var(--muted); text-align: right;">
        <span id="error-log-total"></span>
      </div>

      <div class="weather-modal-buttons modal-footer-bar">
        <button id="error-log-close">Close</button>
      </div>
    </div>
  </div>`);

  const modal = document.getElementById('error-log-modal');
  const viewBtn = document.getElementById('view-error-logs');
  const refreshBtn = document.getElementById('error-log-refresh');
  const clearBtn = document.getElementById('error-log-clear');
  const closeBtn = document.getElementById('error-log-close');
  const levelSel = document.getElementById('error-log-level');
  const contextSel = document.getElementById('error-log-context');
  const searchInput = document.getElementById('error-log-search');
  const sinceInput = document.getElementById('error-log-since');
  const untilInput = document.getElementById('error-log-until');
  const container = document.getElementById('error-log-container');
  const loadMoreBtn = document.getElementById('error-log-load-more');
  const totalSpan = document.getElementById('error-log-total');

  const PAGE_SIZE = 50;
  let currentOffset = 0;
  let inflight = null;
  let filterNonce = 0;
  // Cached distinct contexts so the dropdown is populated once per open and
  // re-populated after a clear (which removes all contexts) or a refresh
  // that surfaces a new subsystem for the first time.
  let knownContexts = [];

  // datetime-local fields are naive local time — convert to UTC ISO so the
  // server compares correctly. Same shape as audit-log.js so the operator
  // sees consistent behaviour between the two modals.
  function toIso(localDtValue) {
    if (!localDtValue) return null;
    const d = new Date(localDtValue);
    if (isNaN(d.getTime())) return null;
    return d.toISOString();
  }

  // Pull the distinct contexts list once per open. Failures are silent
  // (the dropdown will just show "All" only) so a transient backend hiccup
  // doesn't block the operator from seeing the actual error rows.
  async function refreshContexts() {
    try {
      const res = await fetch('/api/v1/error-logs/contexts');
      if (!res.ok) return;
      const data = await res.json();
      if (!data.success || !Array.isArray(data.contexts)) return;
      knownContexts = data.contexts;
      const currentValue = contextSel.value;
      contextSel.innerHTML = '<option value="">All</option>';
      for (const c of data.contexts) {
        const opt = document.createElement('option');
        opt.value = c.name;
        opt.textContent = `${c.name} (${c.count})`;
        contextSel.appendChild(opt);
      }
      // Restore previous selection if still present.
      if (currentValue && data.contexts.some((c) => c.name === currentValue)) {
        contextSel.value = currentValue;
      }
    } catch { /* ignore */ }
  }

  function buildQuery() {
    const params = new URLSearchParams();
    params.set('limit', String(PAGE_SIZE));
    params.set('offset', String(currentOffset));
    if (levelSel.value) params.set('level', levelSel.value);
    if (contextSel.value) params.set('context', contextSel.value);
    const since = toIso(sinceInput.value);
    const until = toIso(untilInput.value);
    if (since) params.set('since', since);
    if (until) params.set('until', until);
    const search = (searchInput.value || '').trim();
    if (search) params.set('search', search);
    return params;
  }

  async function loadLogs(append) {
    try {
      if (!append) {
        if (inflight) inflight.abort();
        inflight = new AbortController();
        currentOffset = 0;
        filterNonce++;
        container.innerHTML = '<div class="panel-empty"><span class="brand-spinner"></span> Loading...</div>';
      } else {
        if (inflight) inflight.abort();
        inflight = new AbortController();
      }
      const myNonce = filterNonce;
      const params = buildQuery();

      const res = await fetch('/api/v1/error-logs?' + params.toString(), {
        signal: inflight.signal,
      });
      // Mirror audit-log: surface 4xx/5xx explicitly instead of falling
      // through to a misleading "no entries yet" empty state.
      if (!res.ok) {
        container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: HTTP ${res.status}</div>`;
        loadMoreBtn.style.display = 'none';
        totalSpan.textContent = '';
        return;
      }
      const data = await res.json();
      if (!data.success) {
        container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(data.error || 'unknown')}</div>`;
        loadMoreBtn.style.display = 'none';
        totalSpan.textContent = '';
        return;
      }
      // Stale-response guard: a non-append load happened after this fetch,
      // discard so we don't splice into the wrong DOM.
      if (!append && myNonce !== filterNonce) return;

      const logs = Array.isArray(data.logs) ? data.logs : [];
      if (logs.length === 0 && !append) {
        const reason = (data.filters && (data.filters.level || data.filters.context || data.filters.search || data.filters.since || data.filters.until))
          ? 'No error log entries match your filters.'
          : '✅ No errors logged! Everything is working smoothly.';
        container.innerHTML = `<div class="panel-empty"><span class="empty-icon">📋</span>${escapeHtml(reason)}</div>`;
        loadMoreBtn.style.display = 'none';
        totalSpan.textContent = data.total ? `${data.total} total` : '';
        return;
      }

      let html = '';
      if (!append) {
        html = '<table style="width: 100%; border-collapse: collapse; font-size: 0.82rem;">';
        html += '<tr style="border-bottom: 1px solid var(--border); color: var(--muted);">';
        html += '<th style="padding: 6px; text-align: left; width: 160px;">When</th>';
        html += '<th style="padding: 6px; text-align: left; width: 80px;">Level</th>';
        html += '<th style="padding: 6px; text-align: left; width: 140px;">Context</th>';
        html += '<th style="padding: 6px; text-align: left;">Message</th>';
        html += '<th style="padding: 6px; text-align: left; width: 110px;">IP</th>';
        html += '</tr>';
      }

      for (const log of logs) {
        const level = (log.level || '?').toUpperCase();
        const levelColor = level === 'ERR' ? 'var(--bad-fg)' : (level === 'WARN' ? 'var(--warn-fg, #f0c674)' : 'var(--muted)');
        const ts = log.timestamp ? new Date(log.timestamp).toLocaleString() : '—';
        const ctx = log.context || '—';
        const msg = (log.error || '').split('\n')[0];
        const ip = (log.request && log.request.ip) || '';
        html += `<tr style="border-bottom: 1px solid var(--border); cursor: pointer;" class="error-log-row">`;
        html += `<td style="padding: 6px; color: var(--muted);" title="${escapeHtml(log.timestamp || '')}">${escapeHtml(ts)}</td>`;
        html += `<td style="padding: 6px;"><span style="color: ${levelColor}; font-weight: 600;">${escapeHtml(level)}</span></td>`;
        html += `<td style="padding: 6px; font-family: monospace; font-size: 0.78rem;">${escapeHtml(ctx)}</td>`;
        html += `<td style="padding: 6px;">${escapeHtml(msg)}</td>`;
        html += `<td style="padding: 6px; font-family: monospace; font-size: 0.78rem;">${escapeHtml(ip)}</td>`;
        html += '</tr>';
        if (log.detail) {
          html += `<tr class="error-log-detail" style="display: none;"><td colspan="5" style="padding: 6px 6px 10px; font-size: 0.78rem; color: var(--muted);"><pre style="margin: 0; white-space: pre-wrap; font-family: monospace; max-height: 320px; overflow: auto;">${escapeHtml(log.detail)}</pre></td></tr>`;
        }
      }

      if (!append) {
        html += '</table>';
        container.innerHTML = html;
      } else {
        const table = container.querySelector('table');
        if (table) table.insertAdjacentHTML('beforeend', html);
      }

      currentOffset += logs.length;
      loadMoreBtn.style.display = data.hasMore ? '' : 'none';
      totalSpan.textContent = `${data.total} total${data.hasMore ? ' (showing ' + currentOffset + ')' : ''}`;

      // Toggle detail rows on click — same pattern as audit-log.js
      container.querySelectorAll('.error-log-row').forEach((row) => {
        if (row.dataset.wired) return;
        row.dataset.wired = 'true';
        row.addEventListener('click', () => {
          const detail = row.nextElementSibling;
          if (detail && detail.classList.contains('error-log-detail')) {
            detail.style.display = detail.style.display === 'none' ? '' : 'none';
          }
        });
      });
    } catch (e) {
      if (e && e.name === 'AbortError') return;
      container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(e.message)}</div>`;
      totalSpan.textContent = '';
    }
  }

  async function clearLogs() {
    if (!confirm('Clear the entire error log? This cannot be undone.')) return;
    try {
      const res = await secureFetch('/api/v1/error-logs', {
        method: 'DELETE',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ confirm: 'CLEAR' }),
      });
      const data = await res.json();
      if (data.success) {
        // After a clear, the contexts list will be empty — re-fetch so the
        // dropdown reflects reality. Load the now-empty page in parallel.
        await refreshContexts();
        loadLogs(false);
        showNotification('✅ Error logs cleared', 'success', 3000);
      } else {
        showNotification('❌ ' + (data.error || 'Clear failed'), 'error', 4000);
      }
    } catch (e) {
      showNotification('❌ ' + e.message, 'error', 4000);
    }
  }

  // Debounce text-input changes so we don't refetch on every keystroke.
  let searchDebounce;
  function wireFilters() {
    levelSel?.addEventListener('change', () => loadLogs(false));
    contextSel?.addEventListener('change', () => loadLogs(false));
    searchInput?.addEventListener('input', () => {
      clearTimeout(searchDebounce);
      searchDebounce = setTimeout(() => loadLogs(false), 250);
    });
    let dateDebounce;
    [sinceInput, untilInput].forEach((el) => {
      el?.addEventListener('change', () => {
        clearTimeout(dateDebounce);
        dateDebounce = setTimeout(() => loadLogs(false), 250);
      });
    });
    refreshBtn?.addEventListener('click', () => loadLogs(false));
    loadMoreBtn?.addEventListener('click', () => loadLogs(true));
    clearBtn?.addEventListener('click', clearLogs);
    wireModal(modal, closeBtn);
  }

  viewBtn?.addEventListener('click', async () => {
    modal?.classList.add('show');
    await refreshContexts();
    loadLogs(false);
  });
  wireFilters();
})();
