// ══════════════════════════════════════════════════════════════
// nterm — Diagnostics panel (stream log, stats, sniffer,
//         prompts, blocks, and performance views)
// ══════════════════════════════════════════════════════════════
// Depends on: state (app.js), perf (perf.js), sniffer classes,
//             escapeHtml (app.js), scheduleGutterRender (gutter.js)

const diag = {
  MAX_LINES: 500,
  lines: [],

  log(tag, tabId, msg) {
    const now = new Date();
    const ts = now.toTimeString().slice(0, 8) + '.' + String(now.getMilliseconds()).padStart(3, '0');
    const entry = { ts, tag, tabId: tabId ? tabId.slice(0, 8) : '', msg };
    this.lines.push(entry);
    if (this.lines.length > this.MAX_LINES) this.lines.shift();
    this.renderStream();
  },

  // ── Sniffer pipeline: per-tab state ─────────────────────
  snifferAccumulators: {},  // tabId → LineAccumulator
  snifferDetectors: {},     // tabId → InterruptionDetector
  snifferPrompts: {},       // tabId → PromptDetector
  snifferBlocks: {},        // tabId → BlockExtractor
  snifferLines: {},         // tabId → [{num, text, cls, isPrompt}]
  MAX_SNIFFER_LINES: 500,

  feedSniffer(tabId, rawBase64) {
  const session = state.sessions[tabId];
  if (session && session.snifferEnabled === false) return;

    perf.start('sniffer_total');

    // Decode base64, strip ANSI sequences
    perf.start('sniffer_decode');
    const raw = atob(rawBase64);
    // NOTE: \x08 (BS) excluded from strip — handled destructively in LineAccumulator
    const filtered = raw.replace(
      /\x1b\[[0-9;?]*[a-zA-Z]|\x1b[()][AB012]|\x07|[\x00-\x07\x0B\x0C\x0E-\x1F]/g, ''
    );
    perf.end('sniffer_decode');

    if (!filtered) { perf.end('sniffer_total'); return; }

    if (!this.snifferAccumulators[tabId]) {
      this.snifferAccumulators[tabId] = new LineAccumulator();
      this.snifferDetectors[tabId] = new InterruptionDetector();
      this.snifferPrompts[tabId] = new PromptDetector(0.3);
      this.snifferBlocks[tabId] = new BlockExtractor();
      this.snifferLines[tabId] = [];
    }

    const acc = this.snifferAccumulators[tabId];
    const det = this.snifferDetectors[tabId];
    const pdet = this.snifferPrompts[tabId];
    const bext = this.snifferBlocks[tabId];

    perf.start('sniffer_accumulate');
    const lines = acc.feed(filtered);
    perf.end('sniffer_accumulate');

    if (lines.length > 0) {
      const store = this.snifferLines[tabId];
      const startNum = acc.lineCount - lines.length + 1;

      perf.start('sniffer_classify');
      for (let i = 0; i < lines.length; i++) {
        const cls = det.classify(lines[i]);
        let isPrompt = false;

        if (cls === 'normal') {
          pdet.observe(lines[i], false);
          isPrompt = pdet.isPrompt(lines[i]);
        } else {
          pdet.observe(lines[i], true);
        }

        // Feed block extractor
        const block = bext.observe(lines[i], startNum + i, cls, isPrompt);
        if (block) {
          scheduleGutterRender(tabId);
          if (state.diagOpen && state.diagView === 'blocks' && state.activeTab === tabId) {
            this.renderBlocks();
          }
        }

        store.push({ num: startNum + i, text: lines[i], cls, isPrompt });
      }
      perf.end('sniffer_classify');

      // Keep bounded
      if (store.length > this.MAX_SNIFFER_LINES) {
        this.snifferLines[tabId] = store.slice(-this.MAX_SNIFFER_LINES);
      }

      // Update badges
      this.updateLineCount(tabId);
      this.updateBlockCount(tabId);

      // Live-render if visible
      if (state.diagOpen && state.activeTab === tabId) {
        if (state.diagView === 'sniffer') this.renderSniffer();
        if (state.diagView === 'prompts') this.renderPrompts();
      }
    }

    perf.end('sniffer_total');
  },

  updateLineCount(tabId) {
    if (state.activeTab === tabId) {
      const acc = this.snifferAccumulators[tabId];
      const badge = document.getElementById('diag-line-count');
      if (badge && acc) badge.textContent = `(${acc.lineCount})`;
    }
  },

  updateBlockCount(tabId) {
    if (state.activeTab === tabId) {
      const bext = this.snifferBlocks[tabId];
      const badge = document.getElementById('diag-block-count');
      if (badge && bext) {
        const count = bext.blocks.length;
        badge.textContent = count > 0 ? `(${count})` : '';
      }
    }
  },

  // ── Renderers ───────────────────────────────────────────

  renderStream() {
    if (!state.diagOpen || state.diagView !== 'stream') return;
    perf.start('render_stream');
    const el = document.getElementById('diag-stream');
    const visible = this.lines.slice(-100);
    el.innerHTML = visible.map(e => {
      const tagClass = e.tag === 'recv' ? 'tag-recv' : e.tag === 'send' ? 'tag-send' :
                       e.tag === 'sniff' ? 'tag-sniff' : 'tag-event';
      return `<div class="log-line">
        <span class="log-time">${e.ts}</span>
        <span class="log-tag ${tagClass}">${e.tag}</span>
        <span style="color:var(--text-dim);width:60px;flex-shrink:0">${e.tabId}</span>
        <span>${escapeHtml(e.msg)}</span>
      </div>`;
    }).join('');
    el.scrollTop = el.scrollHeight;
    perf.end('render_stream');
  },

  // ── Heartbeat helpers ───────────────────────────────────
  _heartbeatInfo(session) {
    const now = Date.now();
    const lastPong = session._lastPong;
    const latency = session._pingLatency;

    if (!lastPong) {
      return { text: '—', color: 'var(--text-dim)' };
    }

    const ageSec = Math.round((now - lastPong) / 1000);

    if (ageSec < 35) {
      // Healthy — show latency
      return { text: `${latency || '?'}ms`, color: 'var(--green)' };
    } else if (ageSec < 90) {
      // Warning — show age
      return { text: `${ageSec}s ago`, color: 'var(--yellow)' };
    } else {
      // Stale
      return { text: `${ageSec}s ago`, color: 'var(--red)' };
    }
  },

  renderStats() {
    if (!state.diagOpen || state.diagView !== 'stats') return;
    perf.start('render_stats');
    const el = document.getElementById('diag-stats');
    const cards = Object.entries(state.sessions).map(([tabId, s]) => {
      const st = s.stats || {};
      const hb = this._heartbeatInfo(s);
      return `<div class="diag-stat-card">
        <div class="stat-host">${escapeHtml(s.config.hostname)} <span style="color:var(--text-dim)">${tabId.slice(0,8)}</span></div>
        <div class="stat-row"><span>Status</span><span class="stat-val" style="color:${s.status === 'connected' ? 'var(--green)' : s.status === 'error' ? 'var(--red)' : 'var(--yellow)'}">${s.status}</span></div>
        <div class="stat-row"><span>Auth</span><span class="stat-val">${s.config.key_content ? 'key(paste)' : s.config.key_file ? 'key(file)' : 'password'}</span></div>
        <div class="stat-row"><span>Bytes recv</span><span class="stat-val">${(st.bytes_received || 0).toLocaleString()}</span></div>
        <div class="stat-row"><span>Messages</span><span class="stat-val">${st.messages_sent || 0}</span></div>
        <div class="stat-row"><span>Bytes/sec</span><span class="stat-val">${st.bytes_per_sec || '—'}</span></div>
        <div class="stat-row"><span>Duration</span><span class="stat-val">${st.duration_sec ? st.duration_sec + 's' : '—'}</span></div>
        <div class="stat-row"><span>Heartbeat</span><span class="stat-val" style="color:${hb.color}">${hb.text}</span></div>
      </div>`;
    });
    el.innerHTML = cards.length
      ? `<div class="diag-stats">${cards.join('')}</div>`
      : '<div style="color:var(--text-dim);padding:12px">No active sessions</div>';
    perf.end('render_stats');
  },

  renderSniffer() {
    if (!state.diagOpen || state.diagView !== 'sniffer') return;
    perf.start('render_sniffer');
    const el = document.getElementById('diag-sniffer');
    const tabId = state.activeTab;
    if (!tabId || !this.snifferLines[tabId]) {
      el.innerHTML = '<div style="color:var(--text-dim);padding:12px">No data — run a command in the active terminal</div>';
      perf.end('render_sniffer');
      return;
    }

    const badgeLabels = { pagination: 'PAGE', confirm: 'CONFIRM', continue: 'CONTINUE', auth: 'AUTH' };
    const lines = this.snifferLines[tabId];
    const acc = this.snifferAccumulators[tabId];
    const det = this.snifferDetectors[tabId];
    const visible = lines.slice(-80);

    let html = visible.map(l => {
      const numStr = String(l.num).padStart(4, '\u00a0');
      const display = l.text === '' ? '\u00a0' : escapeHtml(l.text);
      let badge = '';
      if (l.cls !== 'normal') {
        badge = `<span class="sniffer-badge badge-${l.cls}">${badgeLabels[l.cls]}</span>`;
      } else if (l.isPrompt) {
        badge = `<span class="sniffer-badge" style="background:var(--green-dim);color:var(--green)">PROMPT</span>`;
      }
      return `<div class="sniffer-line"><span class="sniffer-num">${numStr}</span>${badge}<span class="sniffer-text">${display}</span></div>`;
    }).join('');

    if (acc && acc.pending) {
      const nextNum = String(acc.lineCount + 1).padStart(4, '\u00a0');
      const pendingCls = det ? det.classify(acc.pending) : 'normal';
      const pendingBadge = pendingCls !== 'normal' ? `<span class="sniffer-badge badge-${pendingCls}">${badgeLabels[pendingCls]}</span>` : '';
      html += `<div class="sniffer-line sniffer-pending"><span class="sniffer-num">${nextNum}</span>${pendingBadge}<span class="sniffer-text">${escapeHtml(acc.pending)}<span class="sniffer-cursor">▊</span></span></div>`;
    }

    el.innerHTML = html;
    el.scrollTop = el.scrollHeight;
    this.updateLineCount(tabId);
    perf.end('render_sniffer');
  },

  renderPrompts() {
    if (!state.diagOpen || state.diagView !== 'prompts') return;
    perf.start('render_prompts');
    const el = document.getElementById('diag-prompts');
    const tabId = state.activeTab;
    const pdet = tabId ? this.snifferPrompts[tabId] : null;

    if (!pdet) {
      el.innerHTML = '<div style="color:var(--text-dim);padding:12px">No data — connect to a device and run commands</div>';
      perf.end('render_prompts');
      return;
    }

    const stateClass = `state-${pdet.state}`;
    const stateLabel = pdet.state.toUpperCase();
    let infoHtml = '';
    if (pdet.hostname) {
      infoHtml += `<span><span class="prompt-info-label">Hostname:</span> <span class="prompt-info-val">${escapeHtml(pdet.hostname)}</span></span>`;
    }
    if (pdet.prompt) {
      infoHtml += `<span><span class="prompt-info-label">Prompt:</span> <span class="prompt-info-val">${escapeHtml(pdet.prompt)}</span></span>`;
    }
    if (pdet.promptType) {
      infoHtml += `<span><span class="prompt-info-label">Type:</span> <span class="prompt-info-val">${pdet.promptType}</span></span>`;
    }
    if (pdet.matchCount > 0) {
      infoHtml += `<span><span class="prompt-info-label">Matches:</span> <span class="prompt-info-val">${pdet.matchCount}</span></span>`;
    }

    let html = `<div class="prompt-status">
      <span class="prompt-state-badge ${stateClass}">${stateLabel}</span>
      <div class="prompt-info">${infoHtml}</div>
    </div>`;

    const events = pdet.events.slice(-50).reverse();
    if (events.length > 0) {
      html += '<div class="prompt-events">';
      for (const evt of events) {
        const d = new Date(evt.timestamp);
        const ts = d.toTimeString().slice(0, 8) + '.' + String(d.getMilliseconds()).padStart(3, '0');
        html += `<div class="prompt-event">
          <span class="prompt-event-time">${ts}</span>
          <span class="prompt-event-type evt-${evt.eventType}">${evt.eventType}</span>
          <span class="prompt-event-line">${escapeHtml(evt.line)}</span>
          <span class="prompt-event-detail">${escapeHtml(evt.detail)}</span>
        </div>`;
      }
      html += '</div>';
    } else {
      html += '<div style="color:var(--text-dim);padding:8px 0;font-size:10px">Waiting for prompt detection events...</div>';
    }

    el.innerHTML = html;
    perf.end('render_prompts');
  },

  renderBlocks() {
    if (!state.diagOpen || state.diagView !== 'blocks') return;
    perf.start('render_blocks');
    const el = document.getElementById('diag-blocks');
    const tabId = state.activeTab;
    const bext = tabId ? this.snifferBlocks[tabId] : null;

    if (!bext || bext.blocks.length === 0) {
      const stateMsg = bext ? `State: ${bext.state.toUpperCase()}` : '';
      el.innerHTML = `<div class="block-empty">No completed blocks yet. ${stateMsg ? stateMsg + ' — ' : ''}Run commands in the active terminal.</div>`;
      perf.end('render_blocks');
      return;
    }

    const blocks = bext.blocks.slice().reverse();
    let html = '';
    for (const block of blocks) {
      const d = new Date(block.timestamp);
      const ts = d.toTimeString().slice(0, 8);
      const sizeKB = (block.byteCount / 1024).toFixed(1);
      const sizeStr = block.byteCount >= 1024 ? `${sizeKB}KB` : `${block.byteCount}B`;
      const intBadge = block.interrupted ? '<span class="interrupted-badge">interrupted</span>' : '';

      const previewLines = block.outputLines.slice(0, 10);
      let preview = previewLines.map(l => escapeHtml(l)).join('\n');
      if (block.outputLines.length > 10) preview += `\n... (${block.outputLines.length - 10} more lines)`;
      if (block.outputLines.length === 0) preview = '(no output)';

      html += `<div class="block-card" data-block-id="${block.blockId}">
        <div class="block-header" onclick="this.parentElement.classList.toggle('expanded')">
          <span class="block-command">${escapeHtml(block.command)}</span>
          <div class="block-meta">
            ${intBadge}
            <span>${block.lineCount} lines</span>
            <span>${sizeStr}</span>
            <span>${block.durationMs}ms</span>
            <span>${ts}</span>
            <span class="block-expand-hint">▸</span>
          </div>
        </div>
        <div class="block-preview">${preview}</div>
      </div>`;
    }
    el.innerHTML = html;
    perf.end('render_blocks');
  },

  render() {
    if (!state.diagOpen) return;
    if (state.diagView === 'stream') this.renderStream();
    else if (state.diagView === 'stats') this.renderStats();
    else if (state.diagView === 'sniffer') this.renderSniffer();
    else if (state.diagView === 'prompts') this.renderPrompts();
    else if (state.diagView === 'blocks') this.renderBlocks();
    else if (state.diagView === 'perf') perf.renderPerfTab();
  }
};