// C4 — macOS panic-log counter.
//
// Tracker drift note: phase-c-tasks.md lists path as
// packages/control-plane/app/src/lib/panic-counter.ts (a browser SPA
// surface). The SPA can't shell out — `child_process` is Node-only.
// Real home is host-cp (Node) which already brokers operator-machine
// state through host-stream. host-cp exposes a typed event consumers
// can subscribe to.
//
// Implementation:
//   `log show --predicate 'eventMessage CONTAINS "panic"' --last <N>d`
//   pipes to stdout; we count newlines (each panic event = 1 line).
//
// Platform guard:
//   On non-darwin platforms, getPanicCount returns null + emits a
//   `[panic-counter]` warning to stderr. Callers branch on null →
//   skip the delta + don't emit the Slack message.
//
// Sampling cadence:
//   Baseline: at olam-cli startup OR on first /plan/new visit
//   Per-session: at plan completion (cloud-mode only)
//
// Cost note:
//   `log show` is expensive (~200ms-2s depending on system log size).
//   Cache the baseline + only re-sample on demand. Don't poll.

import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { platform } from 'node:os';

const execFileP = promisify(execFile);

const PANIC_PREDICATE = 'eventMessage CONTAINS "panic"';
const DEFAULT_TIMEOUT_MS = 30_000;

/**
 * Return the count of `panic`-containing log entries over the last N
 * days. Returns null on non-darwin platforms OR on `log` command
 * failure (caller treats null as "no signal; skip the delta").
 */
export async function getPanicCount(last_n_days = 7, opts = {}) {
  if (platform() !== 'darwin') {
    if (!opts.silent) {
      process.stderr.write(
        `[panic-counter] platform=${platform()} is not darwin; returning null\n`,
      );
    }
    return null;
  }
  const execImpl = opts.execFileFn ?? execFileP;
  try {
    const { stdout } = await execImpl(
      'log',
      ['show', '--predicate', PANIC_PREDICATE, '--last', `${last_n_days}d`],
      { timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 },
    );
    // `log show` prepends a header + may emit an "is empty" sentinel.
    // Count lines that look like log entries: start with a timestamp.
    const lines = stdout.split('\n').filter((line) => /^\d{4}-\d{2}-\d{2}/.test(line));
    return lines.length;
  } catch (err) {
    if (!opts.silent) {
      const msg = err instanceof Error ? err.message : String(err);
      process.stderr.write(`[panic-counter] log command failed: ${msg}\n`);
    }
    return null;
  }
}

/**
 * Pure delta math. Returns null if either input is null (no signal).
 * Negative deltas (panics increased) are valid — caller frames the
 * Slack message appropriately.
 */
export function computePanicDelta(before, after) {
  if (before === null || after === null) return null;
  if (typeof before !== 'number' || typeof after !== 'number') return null;
  return after - before;
}

/** Format the delta for a Slack message body. Plain English; no jargon. */
export function formatDeltaSummary(before, after) {
  const delta = computePanicDelta(before, after);
  if (delta === null) {
    return 'Panic delta: n/a (counter unavailable this session).';
  }
  if (delta === 0) {
    return `Panic count steady: ${before} → ${after} (no change this session).`;
  }
  if (delta < 0) {
    return `Panic count down ${Math.abs(delta)}: ${before} → ${after}.`;
  }
  return `Panic count up ${delta}: ${before} → ${after}.`;
}
