// engine/system-resources/ram-checker.ts — SPEC-705: Cross-platform RAM probe.
// Probes available host memory and memory pressure level.
// Results are cached for SNAPSHOT_CACHE_TTL_MS to avoid repeated subprocess forks.
import { execFile } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import os from 'node:os';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
// ---------------------------------------------------------------------------
// Constants (exported for use by spawn-policy and tests)
// ---------------------------------------------------------------------------
export const SNAPSHOT_CACHE_TTL_MS = 10_000;
// ---------------------------------------------------------------------------
// Module-level cache
// ---------------------------------------------------------------------------
let lastSnapshot = null;
// ---------------------------------------------------------------------------
// Platform-specific probes
// ---------------------------------------------------------------------------
/** Parse vm_stat stdout into freeBytes. Uses 16384 page size for Apple Silicon (hw.pagesize). */
async function darwinFreeBytes() {
    // Attempt to detect actual page size via sysctl; fall back to 16384 (Apple Silicon default).
    let pageSize = 16384;
    try {
        const { stdout: psOut } = await execFileAsync('sysctl', ['-n', 'hw.pagesize']);
        const parsed = parseInt(psOut.trim(), 10);
        if (Number.isFinite(parsed) && parsed > 0) {
            pageSize = parsed;
        }
    }
    catch {
        // Ignore — use default 16384
    }
    const { stdout } = await execFileAsync('vm_stat', []);
    let freePages = 0;
    let speculativePages = 0;
    let purgeable = 0;
    for (const line of stdout.split('\n')) {
        const freeMatch = /^Pages free:\s+(\d+)\./.exec(line);
        if (freeMatch?.[1] !== undefined) {
            freePages = parseInt(freeMatch[1], 10);
        }
        const specMatch = /^Pages speculative:\s+(\d+)\./.exec(line);
        if (specMatch?.[1] !== undefined) {
            speculativePages = parseInt(specMatch[1], 10);
        }
        const purgeMatch = /^Pages purgeable:\s+(\d+)\./.exec(line);
        if (purgeMatch?.[1] !== undefined) {
            purgeable = parseInt(purgeMatch[1], 10);
        }
    }
    if (freePages === 0 && speculativePages === 0 && purgeable === 0) {
        throw new Error('vm_stat parse failed: no page counts found');
    }
    return (freePages + speculativePages + purgeable) * pageSize;
}
async function darwinTotalBytes() {
    const { stdout } = await execFileAsync('sysctl', ['-n', 'hw.memsize']);
    const bytes = parseInt(stdout.trim(), 10);
    if (!Number.isFinite(bytes) || bytes <= 0) {
        throw new Error('hw.memsize parse failed');
    }
    return bytes;
}
async function darwinPressureLevel() {
    try {
        const { stdout } = await execFileAsync('memory_pressure', ['-Q']);
        const lower = stdout.toLowerCase();
        if (lower.includes('critical')) {
            return 'critical';
        }
        if (lower.includes('warn')) {
            return 'moderate';
        }
        return 'ok';
    }
    catch {
        return 'ok';
    }
}
async function linuxSnapshot() {
    const content = await readFile('/proc/meminfo', 'utf8');
    let memTotal = null;
    let memAvailable = null;
    for (const line of content.split('\n')) {
        const totalMatch = /^MemTotal:\s+(\d+)\s+kB/.exec(line);
        if (totalMatch?.[1] !== undefined) {
            memTotal = parseInt(totalMatch[1], 10) * 1024;
        }
        const availMatch = /^MemAvailable:\s+(\d+)\s+kB/.exec(line);
        if (availMatch?.[1] !== undefined) {
            memAvailable = parseInt(availMatch[1], 10) * 1024;
        }
    }
    if (memTotal === null || memAvailable === null) {
        throw new Error('/proc/meminfo parse failed: missing MemTotal or MemAvailable');
    }
    return { totalBytes: memTotal, freeBytes: memAvailable };
}
function heuristicPressure(freeGB, totalGB) {
    const usedRatio = 1 - freeGB / totalGB;
    if (usedRatio > 0.85) {
        return 'critical';
    }
    if (usedRatio > 0.7) {
        return 'moderate';
    }
    return 'ok';
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Returns a cross-platform snapshot of available host RAM and memory pressure.
 * Results are cached for SNAPSHOT_CACHE_TTL_MS (10 s) to avoid repeated forks.
 */
export async function getHostMemorySnapshot(opts) {
    const now = Date.now();
    if (!opts?.bypassCache && lastSnapshot !== null) {
        if (now - lastSnapshot.capturedAt < SNAPSHOT_CACHE_TTL_MS) {
            return lastSnapshot;
        }
    }
    try {
        const platform = process.platform;
        let totalGB;
        let freeGB;
        let pressureLevel;
        if (platform === 'darwin') {
            const [freeBytes, totalBytes, pressure] = await Promise.all([
                darwinFreeBytes(),
                darwinTotalBytes(),
                darwinPressureLevel(),
            ]);
            totalGB = totalBytes / 1e9;
            freeGB = freeBytes / 1e9;
            pressureLevel = pressure;
        }
        else if (platform === 'linux') {
            const { totalBytes, freeBytes } = await linuxSnapshot();
            totalGB = totalBytes / 1e9;
            freeGB = freeBytes / 1e9;
            pressureLevel = heuristicPressure(freeGB, totalGB);
        }
        else {
            // win32 and everything else — use os module (no subprocess)
            totalGB = os.totalmem() / 1e9;
            freeGB = os.freemem() / 1e9;
            pressureLevel = heuristicPressure(freeGB, totalGB);
        }
        const snapshot = {
            totalGB,
            freeGB,
            pressureLevel,
            capturedAt: now,
        };
        lastSnapshot = snapshot;
        return snapshot;
    }
    catch (err) {
        const probeError = err instanceof Error ? err.message : String(err);
        const snapshot = {
            totalGB: null,
            freeGB: null,
            pressureLevel: 'unknown',
            probeError,
            capturedAt: now,
        };
        lastSnapshot = snapshot;
        return snapshot;
    }
}
/** Clear the module-level snapshot cache (for testing). */
export function clearSnapshotCache() {
    lastSnapshot = null;
}
//# sourceMappingURL=ram-checker.js.map