'use strict';

// Event-loop delay monitor.
//
// Wall-E is single-threaded and uses synchronous better-sqlite3, so a heavy query can
// block the whole event loop (HTTP included) for seconds. When that happens the daemon
// looks "running" to a supervisor while every request times out — exactly the failure
// that hid a multi-minute backfill stall. There was no signal for it.
//
// This wraps perf_hooks.monitorEventLoopDelay (a libuv-level histogram of how late timers
// fire vs. schedule — i.e. how long the loop was blocked) and:
//   - logs a warning whenever a single blocking spell in the window exceeds a threshold,
//   - tracks the worst spell seen since boot,
//   - exposes a cheap stats snapshot for a health endpoint.
//
// All timers are unref'd so the monitor never keeps the process alive.

const { monitorEventLoopDelay } = require('perf_hooks');

let _histogram = null;
let _timer = null;
let _peakMs = 0;
let _lastWarn = null;
let _onWarn = null;

const NS_PER_MS = 1e6;
const _ms = (ns) => Math.round((Number(ns) || 0) / NS_PER_MS);

function isRunning() {
  return _histogram != null;
}

/**
 * Start monitoring. Idempotent.
 * @param {object} opts
 * @param {number} [opts.resolutionMs=20]      sampling resolution
 * @param {number} [opts.sampleMs=10000]       how often to evaluate + reset the window
 * @param {number} [opts.warnThresholdMs=1000] warn if the worst spell in a window exceeds this
 * @param {function} [opts.onWarn]             called as ({ maxMs, meanMs, p99Ms }) on a warning
 * @param {function} [opts.log=console.warn]   where warnings are written
 */
function start(opts = {}) {
  if (_histogram) return; // already running
  const resolution = Math.max(1, Number(opts.resolutionMs) || 20);
  const sampleMs = Math.max(1000, Number(opts.sampleMs) || 10000);
  const warnThresholdMs = Math.max(50, Number(opts.warnThresholdMs) || 1000);
  const log = typeof opts.log === 'function' ? opts.log : console.warn;
  _onWarn = typeof opts.onWarn === 'function' ? opts.onWarn : null;

  _histogram = monitorEventLoopDelay({ resolution });
  _histogram.enable();

  _timer = setInterval(() => {
    const maxMs = _ms(_histogram.max);
    const meanMs = _ms(_histogram.mean);
    let p99Ms = 0;
    try { p99Ms = _ms(_histogram.percentile(99)); } catch { /* histogram may be empty */ }
    if (maxMs > _peakMs) _peakMs = maxMs;

    if (maxMs >= warnThresholdMs) {
      _lastWarn = { at: new Date().toISOString(), maxMs, meanMs, p99Ms };
      log(`[wall-e] event loop blocked up to ${maxMs}ms in the last ${Math.round(sampleMs / 1000)}s (mean ${meanMs}ms, p99 ${p99Ms}ms) — a synchronous DB query or sync I/O is starving the loop`);
      if (_onWarn) { try { _onWarn({ maxMs, meanMs, p99Ms }); } catch { /* never let a hook break the monitor */ } }
    }
    _histogram.reset();
  }, sampleMs);
  _timer.unref?.();
}

/** Cheap snapshot for a health endpoint. Safe to call when not running. */
function stats() {
  if (!_histogram) return { enabled: false };
  let p99Ms = 0;
  try { p99Ms = _ms(_histogram.percentile(99)); } catch { /* empty */ }
  return {
    enabled: true,
    // Current window (reset every sampleMs):
    mean_ms: _ms(_histogram.mean),
    max_ms: _ms(_histogram.max),
    p99_ms: p99Ms,
    // Worst spell since boot, and the last threshold breach:
    peak_ms: _peakMs,
    last_warning: _lastWarn,
  };
}

function stop() {
  if (_timer) { clearInterval(_timer); _timer = null; }
  if (_histogram) { try { _histogram.disable(); } catch {} _histogram = null; }
  _onWarn = null;
}

module.exports = { start, stop, stats, isRunning };
