import { createHash } from "node:crypto";
import fs from "node:fs";
import fs$1 from "node:fs/promises";
import path from "node:path";
import net from "node:net";
import { homedir } from "os";
//#region src/gateway/lock.ts
/**
* Gateway Lock - Prevents multiple gateway instances from running simultaneously
*/
const DEFAULT_TIMEOUT_MS = 5e3;
const DEFAULT_POLL_INTERVAL_MS = 100;
const DEFAULT_STALE_MS = 3e4;
const DEFAULT_PORT_PROBE_TIMEOUT_MS = 1e3;
var GatewayLockError = class extends Error {
	constructor(message, cause) {
		super(message);
		this.cause = cause;
		this.name = "GatewayLockError";
	}
};
function resolveLockDir() {
	return path.join(homedir(), ".xopc", "locks");
}
function resolveLockPath(configPath) {
	const hash = createHash("sha256").update(configPath).digest("hex").slice(0, 8);
	return path.join(resolveLockDir(), `gateway.${hash}.lock`);
}
async function checkPortFree(port, host = "127.0.0.1") {
	return new Promise((resolve) => {
		const socket = net.createConnection({
			port,
			host
		});
		let settled = false;
		const finish = (result) => {
			if (settled) return;
			settled = true;
			clearTimeout(timer);
			socket.removeAllListeners();
			socket.destroy();
			resolve(result);
		};
		const timer = setTimeout(() => finish(true), DEFAULT_PORT_PROBE_TIMEOUT_MS);
		socket.once("connect", () => finish(false));
		socket.once("error", () => finish(true));
	});
}
function isPidAlive(pid) {
	try {
		process.kill(pid, 0);
		return true;
	} catch {
		return false;
	}
}
function readLinuxStartTime(pid) {
	if (process.platform !== "linux") return null;
	try {
		const raw = fs.readFileSync(`/proc/${pid}/stat`, "utf8").trim();
		const closeParen = raw.lastIndexOf(")");
		if (closeParen < 0) return null;
		const fields = raw.slice(closeParen + 1).trim().split(/\s+/);
		const startTime = parseInt(fields[19] ?? "", 10);
		return Number.isFinite(startTime) ? startTime : null;
	} catch {
		return null;
	}
}
async function readLockPayload(lockPath) {
	try {
		const raw = await fs$1.readFile(lockPath, "utf8");
		const parsed = JSON.parse(raw);
		if (typeof parsed.pid !== "number") return null;
		if (typeof parsed.createdAt !== "string") return null;
		if (typeof parsed.configPath !== "string") return null;
		return {
			pid: parsed.pid,
			createdAt: parsed.createdAt,
			configPath: parsed.configPath,
			startTime: typeof parsed.startTime === "number" ? parsed.startTime : void 0
		};
	} catch {
		return null;
	}
}
async function resolveOwnerStatus(pid, payload, port) {
	if (port != null) {
		if (await checkPortFree(port)) return "dead";
	}
	if (!isPidAlive(pid)) return "dead";
	if (process.platform === "linux") {
		const payloadStartTime = payload?.startTime;
		if (Number.isFinite(payloadStartTime)) {
			const currentStartTime = readLinuxStartTime(pid);
			if (currentStartTime == null) return "unknown";
			return currentStartTime === payloadStartTime ? "alive" : "dead";
		}
	}
	return "alive";
}
async function acquireGatewayLock(configPath, opts = {}) {
	const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
	const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
	const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
	const port = opts.port;
	const lockPath = resolveLockPath(configPath);
	await fs$1.mkdir(path.dirname(lockPath), { recursive: true });
	const startedAt = Date.now();
	let lastPayload = null;
	while (Date.now() - startedAt < timeoutMs) try {
		const handle = await fs$1.open(lockPath, "wx");
		const startTime = process.platform === "linux" ? readLinuxStartTime(process.pid) : null;
		const payload = {
			pid: process.pid,
			createdAt: (/* @__PURE__ */ new Date()).toISOString(),
			configPath,
			...Number.isFinite(startTime) ? { startTime } : {}
		};
		await handle.writeFile(JSON.stringify(payload), "utf8");
		console.log(`[GatewayLock] Lock acquired: pid=${process.pid}, lockPath=${lockPath}`);
		return {
			lockPath,
			configPath,
			release: async () => {
				await handle.close().catch(() => void 0);
				await fs$1.rm(lockPath, { force: true });
				console.log(`[GatewayLock] Lock released: lockPath=${lockPath}`);
			}
		};
	} catch (err) {
		if (err.code !== "EEXIST") throw new GatewayLockError(`Failed to acquire lock at ${lockPath}`, err);
		lastPayload = await readLockPayload(lockPath);
		const ownerPid = lastPayload?.pid;
		const ownerStatus = ownerPid ? await resolveOwnerStatus(ownerPid, lastPayload, port) : "unknown";
		if (ownerStatus === "dead" && ownerPid) {
			console.warn(`[GatewayLock] Cleaning up stale gateway lock: pid=${ownerPid}`);
			await fs$1.rm(lockPath, { force: true });
			continue;
		}
		if (ownerStatus !== "alive") {
			let stale = false;
			if (lastPayload?.createdAt) {
				const createdAt = Date.parse(lastPayload.createdAt);
				stale = Number.isFinite(createdAt) ? Date.now() - createdAt > staleMs : false;
			}
			if (!stale) try {
				const st = await fs$1.stat(lockPath);
				stale = Date.now() - st.mtimeMs > staleMs;
			} catch {
				stale = false;
			}
			if (stale) {
				console.warn(`[GatewayLock] Removing stale lock file: lockPath=${lockPath}`);
				await fs$1.rm(lockPath, { force: true });
				continue;
			}
		}
		await new Promise((r) => setTimeout(r, pollIntervalMs));
	}
	throw new GatewayLockError(`Gateway already running${lastPayload?.pid ? ` (pid ${lastPayload.pid})` : ""}; lock timeout after ${timeoutMs}ms`);
}
//#endregion
export { GatewayLockError, acquireGatewayLock };

//# sourceMappingURL=lock.js.map