// ========== DC-055: HOST JOURNALD LOG VIEWER ==========
// Streams host service logs (caddy, dashcaddy-api, docker, ssh, …) via the
// journalctl bind-mount added in start.sh. Server-Sent Events for live
// tailing; bounded non-streaming read for historical views.
(function() {
  'use strict';

  // Allow-list mirrors the backend's ALLOWED_UNITS so the dropdown stays
  // honest when the bind-mount isn't available. The server is still the
  // source of truth — anything not in its allow-list returns 400.
  const UNIT_PRESETS = [
    { unit: 'caddy',          label: 'Caddy (reverse proxy)' },
    { unit: 'dashcaddy-api',  label: 'DashCaddy API (host systemd unit, not this container)' },
    { unit: 'docker',         label: 'Docker daemon' },
    { unit: 'ssh',            label: 'SSH server' },
    { unit: 'systemd-journald', label: 'systemd-journald' },
    { unit: 'tailscaled',     label: 'Tailscale' },
    { unit: 'networkd-dispatcher', label: 'Networkd dispatcher' },
  ];

  injectModal('journald-modal', `
    <div id="journald-modal" class="weather-modal" style="z-index: 1002;">
      <div class="weather-modal-content" style="min-width: 900px; max-width: 95vw; height: 80vh; display: flex; flex-direction: column;">
        <div class="logs-header" style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 12px; border-bottom: 1px solid var(--border); margin-bottom: 12px;">
          <div>
            <h3 style="margin: 0;">🛰️ Host Logs (journald)</h3>
            <p class="modal-subtitle" style="margin: 4px 0 0 0; font-size: 0.85rem; opacity: 0.7;">
              Stream host service logs from <code>journalctl</code> (read-only mount). Docker container logs are still in the <em>Container Logs</em> modal.
            </p>
          </div>
          <div class="logs-controls" style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end;">
            <select id="jd-unit-select" style="padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); min-width: 220px;"></select>
            <input type="text" id="jd-search" placeholder="Search logs..." style="padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); width: 160px;" />
            <input type="number" id="jd-tail" min="1" max="5000" value="200" title="Lines to load (historical view)" style="padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); width: 90px;" />
            <button id="jd-refresh" class="btn-accent-solid" style="padding: 6px 14px; font-size: 0.85rem;">🔄 Load tail</button>
            <button id="jd-stream" class="btn-accent-solid" style="padding: 6px 14px; font-size: 0.85rem;">▶ Stream</button>
            <button id="jd-clear-search" style="padding: 6px 10px; font-size: 0.85rem;" title="Clear search">✕</button>
            <button id="jd-close" class="close-btn" style="padding: 6px 10px;">✕</button>
          </div>
        </div>

        <div id="jd-meta" style="display: flex; gap: 16px; margin-bottom: 12px; padding: 8px 12px; background: var(--bg-secondary, var(--bg)); border-radius: 6px; font-size: 0.82rem; flex-wrap: wrap;">
          <span><strong>Source:</strong> <span id="jd-source">journald</span></span>
          <span><strong>Unit:</strong> <span id="jd-unit-display">-</span></span>
          <span><strong>Stream:</strong> <span id="jd-stream-state">disconnected</span></span>
        </div>

        <div id="jd-content" class="logs-content scroll-container" style="flex: 1; min-height: 0; background: #1a1a1a; border-radius: 6px; padding: 12px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.78rem; overflow: auto;">
          <div class="logs-loading" style="color: var(--muted); text-align: center; padding: 40px;">Select a unit and click <em>Load tail</em> or <em>Stream</em>.</div>
        </div>

        <div class="weather-modal-buttons modal-footer-bar" style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);">
          <div style="display: flex; gap: 8px; font-size: 0.78rem; color: var(--muted);">
            <span id="jd-line-count">0 lines</span>
            <span>|</span>
            <span id="jd-filter-count">0 shown</span>
            <span>|</span>
            <span id="jd-overflow" style="display: none; color: var(--warn-fg, #fbbf24);">⚠ stream overflow — re-load with narrower window</span>
          </div>
          <button id="jd-close-btn" class="btn-secondary">Close</button>
        </div>
      </div>
    </div>
  `);

  const modal        = document.getElementById('journald-modal');
  const unitSelect   = document.getElementById('jd-unit-select');
  const searchInput  = document.getElementById('jd-search');
  const tailInput    = document.getElementById('jd-tail');
  const refreshBtn   = document.getElementById('jd-refresh');
  const streamBtn    = document.getElementById('jd-stream');
  const clearSearch  = document.getElementById('jd-clear-search');
  const closeBtn     = document.getElementById('jd-close');
  const closeBtn2    = document.getElementById('jd-close-btn');
  const content      = document.getElementById('jd-content');
  const lineCount    = document.getElementById('jd-line-count');
  const filterCount  = document.getElementById('jd-filter-count');
  const overflowHint = document.getElementById('jd-overflow');
  const unitDisplay  = document.getElementById('jd-unit-display');
  const streamState  = document.getElementById('jd-stream-state');

  let available = false;     // /var/log/journal mounted?
  let lines = [];            // current buffer (array of {timestamp, unit, text})
  let streaming = false;
  let eventSource = null;
  let searchTimer = null;

  function escapeHtml(s) {
    // Local re-declaration so we don't depend on a global; same semantics
    // as the helper used by container-logs.js and error-logs.js.
    const div = document.createElement('div');
    div.textContent = String(s);
    return div.innerHTML;
  }

  function setAvailable(isAvailable) {
    available = isAvailable;
    unitSelect.innerHTML = '';
    UNIT_PRESETS.forEach(p => {
      const opt = document.createElement('option');
      opt.value = p.unit;
      opt.textContent = p.label + ' (' + p.unit + ')';
      unitSelect.appendChild(opt);
    });
    unitSelect.disabled = !isAvailable;
    if (!isAvailable) {
      content.innerHTML = '<div class="logs-loading" style="color: var(--muted); text-align: center; padding: 40px;">journald bind-mount not available in this container.<br/><small>Requires <code>/var/log/journal</code> + <code>/usr/bin/journalctl</code> mounted (start.sh).</small></div>';
      refreshBtn.disabled = true;
      streamBtn.disabled = true;
    } else {
      refreshBtn.disabled = false;
      streamBtn.disabled = false;
    }
  }

  async function probeAvailable() {
    try {
      const resp = await fetch('/api/v1/logs/journal/units');
      if (!resp.ok) { setAvailable(false); return; }
      const data = await resp.json();
      setAvailable(!!data.available);
    } catch (e) {
      setAvailable(false);
    }
  }

  function renderLines() {
    const term = (searchInput.value || '').trim().toLowerCase();
    const filtered = term ? lines.filter(l => (l.textContent || '').toLowerCase().includes(term)) : lines;
    if (filtered.length === 0) {
      content.innerHTML = '<div class="logs-loading" style="color: var(--muted); text-align: center; padding: 40px;">No entries' + (term ? ` matching &quot;${escapeHtml(term)}&quot;` : '') + '</div>';
    } else {
      const html = filtered.map(line => {
        const ts = line.timestamp ? escapeHtml(line.timestamp) : '—';
        const t = escapeHtml(line.textContent);
        return `<div class="jd-line" style="padding: 1px 0; line-height: 1.4; color: #d4d4d4;"><span style="color: var(--muted); margin-right: 8px;">${ts}</span>${t}</div>`;
      }).join('');
      content.innerHTML = html;
      // Auto-scroll only if user is already at the bottom (don't fight them).
      const nearBottom = content.scrollHeight - content.scrollTop - content.clientHeight < 80;
      if (nearBottom) content.scrollTop = content.scrollHeight;
    }
    lineCount.textContent = `${lines.length} entries`;
    filterCount.textContent = term ? `${filtered.length} of ${lines.length} shown` : `${lines.length} shown`;
  }

  async function loadTail() {
    if (!available) return;
    stopStream();
    const unit = unitSelect.value;
    if (!unit) return;
    const tail = Math.max(1, Math.min(5000, Number(tailInput.value) || 200));
    const term = (searchInput.value || '').trim();
    content.innerHTML = '<div class="logs-loading" style="color: var(--muted); text-align: center; padding: 40px;">Loading…</div>';
    try {
      const url = new URL('/api/v1/logs/journal', window.location.origin);
      url.searchParams.set('unit', unit);
      url.searchParams.set('tail', String(tail));
      if (term) url.searchParams.set('search', term);
      const resp = await fetch(url.toString());
      const data = await resp.json();
      if (!resp.ok || !data.success) {
        content.innerHTML = '<div class="logs-loading" style="color: var(--bad-fg, #ef4444); text-align: center; padding: 40px;">Failed: ' + escapeHtml((data && data.error) || ('HTTP ' + resp.status)) + '</div>';
        return;
      }
      unitDisplay.textContent = unit;
      lines = (data.entries || []).map(e => ({
        timestamp: e.timestamp,
        unit: e.unit,
        textContent: e.text || '',
      }));
      overflowHint.style.display = 'none';
      renderLines();
    } catch (e) {
      content.innerHTML = '<div class="logs-loading" style="color: var(--bad-fg, #ef4444); text-align: center; padding: 40px;">Error: ' + escapeHtml(e.message) + '</div>';
    }
  }

  function startStream() {
    if (!available) return;
    stopStream();
    const unit = unitSelect.value;
    if (!unit) return;
    const term = (searchInput.value || '').trim();
    unitDisplay.textContent = unit;
    streamBtn.textContent = '⏸ Stop';
    streamBtn.classList.add('streaming');
    streamState.textContent = 'streaming';
    streamState.style.color = 'var(--ok-fg, #4ade80)';
    lines = [];
    renderLines();
    overflowHint.style.display = 'none';
    const url = new URL('/api/v1/logs/journal/stream', window.location.origin);
    url.searchParams.set('unit', unit);
    if (term) url.searchParams.set('search', term);
    eventSource = new EventSource(url.toString());
    eventSource.onmessage = (ev) => {
      try {
        const entry = JSON.parse(ev.data);
        if (entry.error) {
          // Overflow / validation / bind-mount errors
          if (/stream (exceeded|line cap)/.test(entry.error)) {
            overflowHint.style.display = '';
            stopStream();
          }
          content.innerHTML += '<div class="jd-line" style="color: var(--bad-fg, #ef4444); padding: 4px 0;">⚠ ' + escapeHtml(entry.error) + '</div>';
          content.scrollTop = content.scrollHeight;
          return;
        }
        lines.push({
          timestamp: entry.timestamp,
          unit: entry.unit || unit,
          textContent: entry.text || '',
        });
        // Hard cap to keep memory bounded if operator streams forever.
        if (lines.length > 5000) {
          lines = lines.slice(lines.length - 5000);
          overflowHint.style.display = '';
        }
        renderLines();
      } catch (_) {
        // Ignore malformed events; the server is authoritative.
      }
    };
    eventSource.onerror = () => {
      // EventSource auto-reconnects; mark transient if we were expecting
      // more, otherwise we closed it deliberately.
      if (!streaming) return;
    };
    streaming = true;
  }

  function stopStream() {
    streaming = false;
    if (eventSource) {
      try { eventSource.close(); } catch (_) { /* ignore */ }
      eventSource = null;
    }
    streamBtn.textContent = '▶ Stream';
    streamBtn.classList.remove('streaming');
    streamState.textContent = 'disconnected';
    streamState.style.color = 'var(--muted)';
  }

  function close() {
    stopStream();
    modal.classList.remove('show');
  }

  // Wire events
  refreshBtn.addEventListener('click', loadTail);
  streamBtn.addEventListener('click', () => streaming ? stopStream() : startStream());
  clearSearch.addEventListener('click', () => { searchInput.value = ''; renderLines(); });
  searchInput.addEventListener('input', () => {
    clearTimeout(searchTimer);
    searchTimer = setTimeout(renderLines, 200);
  });
  searchInput.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { searchInput.value = ''; renderLines(); }
  });
  closeBtn.addEventListener('click', close);
  closeBtn2.addEventListener('click', close);
  modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && modal.classList.contains('show')) close();
  });
  // Reload tail automatically when the unit dropdown changes (if we have
  // data already — saves a click).
  unitSelect.addEventListener('change', () => {
    if (lines.length > 0) loadTail();
  });

  // Hook into the existing "Container Logs" modal button so operators get a
  // separate entry point; mirror the openContainerLogsModal pattern.
  function openJournaldModal() {
    modal.classList.add('show');
    probeAvailable();
  }
  window.openJournaldModal = openJournaldModal;

  document.getElementById('view-journald-logs')?.addEventListener('click', openJournaldModal);
})();