// ══════════════════════════════════════════════════════════════
// nterm — Performance instrumentation
// ══════════════════════════════════════════════════════════════
//
// Lightweight performance tracker for diagnosing latency.
// Instruments hot paths: WebSocket message handling, sniffer
// pipeline, DOM rendering, gutter updates, and parse API calls.
//
// Usage:
//   perf.start('ws_message');
//   ... do work ...
//   perf.end('ws_message');
//
// Console:
//   window.ntermPerf()          — summary of all buckets
//   window.ntermPerf('detail')  — per-bucket histograms
//   window.ntermPerf('reset')   — clear all data
//
// Diag tab:
//   perf.renderPerfTab()        — renders into diag-perf view

const perf = {
  _enabled: true,
  _buckets: {},     // name → { samples: Float64Array, idx, count, total, max, overBudget }
  _CAPACITY: 500,   // ring buffer size per bucket
  _FRAME_BUDGET_MS: 16.67,  // 60fps threshold
  _longFrames: 0,
  _totalFrames: 0,

  // ── Core API ─────────────────────────────────────────
  start(name) {
    if (!this._enabled) return;
    if (!this._buckets[name]) this._initBucket(name);
    this._buckets[name]._startTime = performance.now();
  },

  end(name) {
    if (!this._enabled) return;
    const bucket = this._buckets[name];
    if (!bucket || bucket._startTime === 0) return;

    const elapsed = performance.now() - bucket._startTime;
    bucket._startTime = 0;

    // Ring buffer insert
    bucket.samples[bucket.idx] = elapsed;
    bucket.idx = (bucket.idx + 1) % this._CAPACITY;
    bucket.count++;
    bucket.total += elapsed;
    if (elapsed > bucket.max) bucket.max = elapsed;
    if (elapsed > this._FRAME_BUDGET_MS) bucket.overBudget++;
  },

  // Measure a synchronous function
  measure(name, fn) {
    this.start(name);
    const result = fn();
    this.end(name);
    return result;
  },

  // Measure an async function
  async measureAsync(name, fn) {
    this.start(name);
    try {
      const result = await fn();
      return result;
    } finally {
      this.end(name);
    }
  },

  // Mark a frame boundary (called from RAF or render callbacks)
  markFrame(durationMs) {
    this._totalFrames++;
    if (durationMs > this._FRAME_BUDGET_MS) this._longFrames++;
  },

  // ── Bucket initialization ────────────────────────────
  _initBucket(name) {
    this._buckets[name] = {
      samples: new Float64Array(this._CAPACITY),
      idx: 0,
      count: 0,
      total: 0,
      max: 0,
      overBudget: 0,
      _startTime: 0,
    };
  },

  // ── Statistics ───────────────────────────────────────
  getStats(name) {
    const bucket = this._buckets[name];
    if (!bucket || bucket.count === 0) return null;

    const n = Math.min(bucket.count, this._CAPACITY);
    const sorted = Array.from(bucket.samples.subarray(0, n)).sort((a, b) => a - b);

    return {
      name,
      count: bucket.count,
      avg: bucket.total / bucket.count,
      min: sorted[0],
      max: bucket.max,
      p50: sorted[Math.floor(n * 0.5)],
      p95: sorted[Math.floor(n * 0.95)],
      p99: sorted[Math.floor(n * 0.99)],
      overBudget: bucket.overBudget,
      overBudgetPct: ((bucket.overBudget / bucket.count) * 100),
    };
  },

  getAllStats() {
    const stats = {};
    for (const name of Object.keys(this._buckets)) {
      const s = this.getStats(name);
      if (s) stats[name] = s;
    }
    return stats;
  },

  reset() {
    this._buckets = {};
    this._longFrames = 0;
    this._totalFrames = 0;
  },

  // ── Diag panel rendering ─────────────────────────────
  renderPerfTab() {
    const el = document.getElementById('diag-perf');
    if (!el) return;

    const allStats = this.getAllStats();
    const names = Object.keys(allStats).sort();

    if (names.length === 0) {
      el.innerHTML = '<div style="color:var(--text-dim);padding:12px">No performance data yet — interact with a terminal session.</div>';
      return;
    }

    // Frame budget summary
    const framePct = this._totalFrames > 0
      ? ((this._longFrames / this._totalFrames) * 100).toFixed(1)
      : '0.0';

    let html = `<div style="margin-bottom:8px;font-size:10px;color:var(--text-muted)">
      Frame budget (>16.7ms): <span style="color:${parseFloat(framePct) > 10 ? 'var(--red)' : 'var(--green)'}">${framePct}%</span> over budget
      · ${this._longFrames}/${this._totalFrames} frames
    </div>`;

    // Table
    html += '<table style="width:100%;border-collapse:collapse;font-family:\'JetBrains Mono\',monospace;font-size:10px">';
    html += '<tr style="border-bottom:1px solid var(--border)">';
    html += '<th style="text-align:left;padding:3px 6px;color:var(--cyan)">Bucket</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">Count</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">Avg</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">P50</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">P95</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">Max</th>';
    html += '<th style="text-align:right;padding:3px 6px;color:var(--cyan)">Over</th>';
    html += '</tr>';

    for (const name of names) {
      const s = allStats[name];
      const overColor = s.overBudgetPct > 10 ? 'var(--red)' : s.overBudgetPct > 2 ? 'var(--yellow)' : 'var(--text-dim)';
      const avgColor = s.avg > 16.67 ? 'var(--red)' : s.avg > 8 ? 'var(--yellow)' : 'var(--green)';

      html += `<tr style="border-bottom:1px solid var(--border)">`;
      html += `<td style="padding:3px 6px;color:var(--text)">${name}</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:var(--text-muted)">${s.count}</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:${avgColor}">${s.avg.toFixed(2)}ms</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:var(--text-muted)">${s.p50.toFixed(2)}ms</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:var(--text-muted)">${s.p95.toFixed(2)}ms</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:var(--text-muted)">${s.max.toFixed(2)}ms</td>`;
      html += `<td style="text-align:right;padding:3px 6px;color:${overColor}">${s.overBudgetPct.toFixed(1)}%</td>`;
      html += `</tr>`;
    }
    html += '</table>';

    el.innerHTML = html;
  },
};

// ── Console helper ─────────────────────────────────────────
window.ntermPerf = function(mode) {
  if (mode === 'reset') {
    perf.reset();
    console.log('[perf] Reset all buckets');
    return;
  }

  const stats = perf.getAllStats();
  const names = Object.keys(stats).sort();

  if (names.length === 0) {
    console.log('[perf] No data collected yet');
    return;
  }

  console.log('[perf] ═══════════════════════════════════════════');
  console.log(`[perf] Frame budget: ${perf._longFrames}/${perf._totalFrames} over 16.7ms`);
  console.log('[perf] ───────────────────────────────────────────');

  for (const name of names) {
    const s = stats[name];
    const bar = s.avg > 16.67 ? '🔴' : s.avg > 8 ? '🟡' : '🟢';
    console.log(
      `${bar} ${name.padEnd(24)} ` +
      `n=${String(s.count).padStart(5)} ` +
      `avg=${s.avg.toFixed(2).padStart(8)}ms ` +
      `p95=${s.p95.toFixed(2).padStart(8)}ms ` +
      `max=${s.max.toFixed(2).padStart(8)}ms ` +
      `over=${s.overBudgetPct.toFixed(1).padStart(5)}%`
    );
  }

  if (mode === 'detail') {
    console.log('[perf] ───────────────────────────────────────────');
    console.log('[perf] Raw stats:', stats);
  }
};
