const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
const DEFAULT_STALL_MULTIPLIER = 3;
export function createNodeMonitorEngine(options = {}) {
    const monitors = new Map();
    const heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
    const stallThresholdMultiplier = options.stallThresholdMultiplier ?? DEFAULT_STALL_MULTIPLIER;
    let checkTimer = null;
    function key(nodeId, runId) {
        return `${runId}::${nodeId}`;
    }
    function startChecker() {
        if (checkTimer)
            return;
        checkTimer = setInterval(() => {
            const stalled = checkAllInternal();
            for (const monitor of stalled) {
                options.onStall?.(monitor);
            }
        }, heartbeatIntervalMs);
    }
    function stopChecker() {
        if (checkTimer) {
            clearInterval(checkTimer);
            checkTimer = null;
        }
    }
    function checkAllInternal() {
        const now = Date.now();
        const stalled = [];
        for (const monitor of monitors.values()) {
            if (monitor.status === "stalled")
                continue;
            const lastHeartbeat = Date.parse(monitor.lastHeartbeatAt);
            const threshold = monitor.stallThresholdMs;
            if (Number.isNaN(lastHeartbeat) || now - lastHeartbeat > threshold) {
                monitor.status = "stalled";
                stalled.push(monitor);
                options.onKill?.(monitor);
            }
        }
        return stalled;
    }
    return {
        register(nodeId, runId) {
            const k = key(nodeId, runId);
            const now = new Date().toISOString();
            const existing = monitors.get(k);
            if (existing) {
                existing.lastHeartbeatAt = now;
                existing.status = "healthy";
                return;
            }
            monitors.set(k, {
                nodeId,
                runId,
                lastHeartbeatAt: now,
                stallThresholdMs: heartbeatIntervalMs * stallThresholdMultiplier,
                status: "healthy",
            });
            startChecker();
        },
        heartbeat(nodeId, runId) {
            const k = key(nodeId, runId);
            const monitor = monitors.get(k);
            if (!monitor)
                return;
            const now = new Date().toISOString();
            monitor.lastHeartbeatAt = now;
            if (monitor.status === "stalled") {
                monitor.status = "recovered";
                options.onRecover?.(monitor);
            }
            else {
                monitor.status = "healthy";
            }
        },
        unregister(nodeId, runId) {
            const k = key(nodeId, runId);
            monitors.delete(k);
            if (monitors.size === 0) {
                stopChecker();
            }
        },
        getStatus(nodeId, runId) {
            return monitors.get(key(nodeId, runId));
        },
        checkAll() {
            return checkAllInternal();
        },
        dispose() {
            stopChecker();
            monitors.clear();
        },
    };
}
export function forciblyRemoveMonitor(engine, nodeId, runId) {
    engine.unregister(nodeId, runId);
}
