import { c as normalizeOptionalString } from "./string-coerce-DW4mBlAt.js";
import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import "./number-coercion-EqFmHmOw.js";
import { m as runWithGatewayIndependentRootWorkAdmission } from "./gateway-work-admission-Bix1tnsy.js";
//#region src/infra/heartbeat-reason.ts
/** Normalize a heartbeat wake reason for logs and UI. */
function normalizeHeartbeatWakeReason(reason) {
	return normalizeOptionalString(reason) ?? "requested";
}
//#endregion
//#region src/infra/heartbeat-wake.ts
const HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT = "requests-in-flight";
const HEARTBEAT_SKIP_CRON_IN_PROGRESS = "cron-in-progress";
const HEARTBEAT_SKIP_LANES_BUSY = "lanes-busy";
const RETRYABLE_BUSY_SKIP_REASONS = /* @__PURE__ */ new Set([
	HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT,
	HEARTBEAT_SKIP_CRON_IN_PROGRESS,
	HEARTBEAT_SKIP_LANES_BUSY
]);
function isRetryableHeartbeatBusySkipReason(reason) {
	return RETRYABLE_BUSY_SKIP_REASONS.has(reason);
}
let heartbeatsEnabled = true;
function setHeartbeatsEnabled(enabled) {
	heartbeatsEnabled = enabled;
}
function areHeartbeatsEnabled() {
	return heartbeatsEnabled;
}
let handler = null;
let handlerGeneration = 0;
const pendingWakes = /* @__PURE__ */ new Map();
let scheduled = false;
let running = false;
let timer = null;
let timerDueAt = null;
let timerKind = null;
const DEFAULT_COALESCE_MS = 250;
const DEFAULT_RETRY_MS = 1e3;
const REASON_PRIORITY = {
	RETRY: 0,
	INTERVAL: 1,
	DEFAULT: 2,
	ACTION: 3
};
function resolveWakePriority(params) {
	if (params.intent === "manual" || params.intent === "immediate") return REASON_PRIORITY.ACTION;
	if (params.source === "retry" || params.reason === "retry") return REASON_PRIORITY.RETRY;
	if (params.intent === "scheduled" || params.source === "interval" || params.reason === "interval") return REASON_PRIORITY.INTERVAL;
	return REASON_PRIORITY.DEFAULT;
}
function normalizeWakeReason(reason) {
	return normalizeHeartbeatWakeReason(reason);
}
function normalizeWakeTarget(value) {
	return (normalizeOptionalString(value) ?? "") || void 0;
}
function getWakeTargetKey(params) {
	const agentId = normalizeWakeTarget(params.agentId);
	const sessionKey = normalizeWakeTarget(params.sessionKey);
	return `${agentId ?? ""}::${sessionKey ?? ""}`;
}
function queuePendingWakeReason(params) {
	const requestedAt = params.requestedAt ?? Date.now();
	const normalizedReason = normalizeWakeReason(params.reason);
	const normalizedAgentId = normalizeWakeTarget(params.agentId);
	const normalizedSessionKey = normalizeWakeTarget(params.sessionKey);
	const wakeTargetKey = getWakeTargetKey({
		agentId: normalizedAgentId,
		sessionKey: normalizedSessionKey
	});
	const next = {
		source: params.source,
		intent: params.intent,
		reason: normalizedReason,
		priority: resolveWakePriority({
			source: params.source,
			intent: params.intent,
			reason: normalizedReason
		}),
		requestedAt,
		agentId: normalizedAgentId,
		sessionKey: normalizedSessionKey,
		heartbeat: params.heartbeat
	};
	const previous = pendingWakes.get(wakeTargetKey);
	if (!previous) {
		pendingWakes.set(wakeTargetKey, next);
		return;
	}
	const merged = next.heartbeat ?? previous.heartbeat ? {
		...next,
		heartbeat: next.heartbeat ?? previous.heartbeat
	} : next;
	if (next.priority > previous.priority) {
		pendingWakes.set(wakeTargetKey, merged);
		return;
	}
	if (next.priority === previous.priority && next.requestedAt >= previous.requestedAt) pendingWakes.set(wakeTargetKey, merged);
}
function schedule(coalesceMs, kind = "normal") {
	const delay = resolveTimerTimeoutMs(coalesceMs, DEFAULT_COALESCE_MS, 0);
	const dueAt = Date.now() + delay;
	if (timer) {
		if (timerKind === "retry") return;
		if (typeof timerDueAt === "number" && timerDueAt <= dueAt) return;
		clearTimeout(timer);
		timer = null;
		timerDueAt = null;
		timerKind = null;
	}
	timerDueAt = dueAt;
	timerKind = kind;
	timer = setTimeout(() => {
		(async () => {
			timer = null;
			timerDueAt = null;
			timerKind = null;
			scheduled = false;
			const active = handler;
			if (!active) return;
			if (running) {
				scheduled = true;
				schedule(delay, kind);
				return;
			}
			const pendingBatch = Array.from(pendingWakes.values());
			pendingWakes.clear();
			running = true;
			try {
				for (const pendingWake of pendingBatch) {
					const wakeOpts = {
						source: pendingWake.source,
						intent: pendingWake.intent,
						reason: pendingWake.reason ?? void 0,
						...pendingWake.agentId ? { agentId: pendingWake.agentId } : {},
						...pendingWake.sessionKey ? { sessionKey: pendingWake.sessionKey } : {},
						...pendingWake.heartbeat ? { heartbeat: pendingWake.heartbeat } : {}
					};
					const res = await runWithGatewayIndependentRootWorkAdmission(async () => active(wakeOpts));
					if (res.status === "skipped" && isRetryableHeartbeatBusySkipReason(res.reason)) {
						queuePendingWakeReason({
							source: pendingWake.source,
							intent: pendingWake.intent,
							reason: pendingWake.reason ?? "retry",
							agentId: pendingWake.agentId,
							sessionKey: pendingWake.sessionKey,
							heartbeat: pendingWake.heartbeat
						});
						schedule(DEFAULT_RETRY_MS, "retry");
					}
				}
			} catch {
				for (const pendingWake of pendingBatch) queuePendingWakeReason({
					source: pendingWake.source,
					intent: pendingWake.intent,
					reason: pendingWake.reason ?? "retry",
					agentId: pendingWake.agentId,
					sessionKey: pendingWake.sessionKey,
					heartbeat: pendingWake.heartbeat
				});
				schedule(DEFAULT_RETRY_MS, "retry");
			} finally {
				running = false;
				if (pendingWakes.size > 0 || scheduled) schedule(delay, "normal");
			}
		})();
	}, delay);
	timer.unref?.();
}
/**
* Register (or clear) the heartbeat wake handler.
* Returns a disposer function that clears this specific registration.
* Stale disposers (from previous registrations) are no-ops, preventing
* a race where an old runner's cleanup clears a newer runner's handler.
*/
function setHeartbeatWakeHandler(next) {
	handlerGeneration += 1;
	const generation = handlerGeneration;
	handler = next;
	if (next) {
		if (timer) clearTimeout(timer);
		timer = null;
		timerDueAt = null;
		timerKind = null;
		running = false;
		scheduled = false;
	}
	if (handler && pendingWakes.size > 0) schedule(DEFAULT_COALESCE_MS, "normal");
	return () => {
		if (handlerGeneration !== generation) return;
		if (handler !== next) return;
		handlerGeneration += 1;
		handler = null;
	};
}
function requestHeartbeat(opts) {
	queuePendingWakeReason({
		source: opts.source,
		intent: opts.intent,
		reason: opts.reason,
		agentId: opts.agentId,
		sessionKey: opts.sessionKey,
		heartbeat: opts.heartbeat
	});
	schedule(opts.coalesceMs ?? DEFAULT_COALESCE_MS, "normal");
}
//#endregion
export { isRetryableHeartbeatBusySkipReason as a, setHeartbeatsEnabled as c, areHeartbeatsEnabled as i, HEARTBEAT_SKIP_LANES_BUSY as n, requestHeartbeat as o, HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT as r, setHeartbeatWakeHandler as s, HEARTBEAT_SKIP_CRON_IN_PROGRESS as t };
