import { createServer } from "node:net";
import { spawn } from "node:child_process";
//#region src/infra/ports.ts
/**
* Port Inspection - Cross-platform port usage detection
*
* Provides utilities to:
* - Check if a port is in use
* - Identify which process is listening
* - Classify whether a listener is the gateway
*/
/** Inspect port usage: check if busy and identify listeners */
async function inspectPortUsage(port) {
	const errors = [];
	let listeners = [];
	try {
		listeners = await detectListeners(port);
	} catch (err) {
		errors.push(err instanceof Error ? err.message : String(err));
	}
	let status;
	if (listeners.length > 0) status = "busy";
	else status = await probePortAvailability(port);
	if (status !== "busy") listeners = [];
	const hints = buildPortHints(listeners, port);
	if (status === "busy" && listeners.length === 0) hints.push("Port is in use but process details unavailable (try running with elevated permissions).");
	return {
		port,
		status,
		listeners,
		hints,
		errors: errors.length > 0 ? errors : void 0
	};
}
/** Classify whether a port listener belongs to the gateway */
function classifyPortListener(listener, runtimePid) {
	if (runtimePid && listener.pid === runtimePid) return "gateway";
	const cmd = (listener.command || listener.commandLine || "").toLowerCase();
	if (cmd.includes("xopc") || cmd.includes("gateway")) return "gateway";
	if (listener.pid) return "other";
	return "unknown";
}
/** Format port diagnostics for display */
function formatPortDiagnostics(usage) {
	const lines = [];
	lines.push(`Port ${usage.port}: ${usage.status}`);
	for (const listener of usage.listeners) {
		const parts = [];
		if (listener.pid) parts.push(`pid=${listener.pid}`);
		if (listener.command) parts.push(`cmd=${listener.command}`);
		if (listener.address) parts.push(`addr=${listener.address}`);
		lines.push(`  Listener: ${parts.join(" ")}`);
	}
	for (const hint of usage.hints) lines.push(`  💡 ${hint}`);
	return lines;
}
async function detectListeners(port) {
	if (process.platform === "win32") return detectWindowsListeners(port);
	return detectUnixListeners(port);
}
async function detectUnixListeners(port) {
	const lsofResult = await runCommand([
		"lsof",
		"-nP",
		`-iTCP:${port}`,
		"-sTCP:LISTEN",
		"-F",
		"pcn"
	]);
	if (lsofResult.exitCode === 0 && lsofResult.stdout.trim()) return parseLsofOutput(lsofResult.stdout);
	const ssResult = await runCommand([
		"ss",
		"-tlnp",
		`sport = :${port}`
	]);
	if (ssResult.exitCode === 0 && ssResult.stdout.trim()) return parseSsOutput(ssResult.stdout, port);
	return [];
}
async function detectWindowsListeners(port) {
	const result = await runCommand([
		"netstat",
		"-ano",
		"-p",
		"tcp"
	]);
	if (result.exitCode !== 0) return [];
	const listeners = [];
	const portToken = `:${port}`;
	for (const line of result.stdout.split(/\r?\n/)) {
		const trimmed = line.trim();
		if (!trimmed.includes("LISTENING") || !trimmed.includes(portToken)) continue;
		const parts = trimmed.split(/\s+/);
		const pidStr = parts[parts.length - 1];
		const pid = parseInt(pidStr, 10);
		if (Number.isFinite(pid) && pid > 0) listeners.push({
			pid,
			address: parts[1]
		});
	}
	return listeners;
}
function parseLsofOutput(output) {
	const listeners = [];
	let current = {};
	for (const line of output.split("\n")) {
		if (!line.trim()) continue;
		const type = line[0];
		const value = line.slice(1);
		switch (type) {
			case "p":
				if (current.pid) listeners.push(current);
				current = { pid: parseInt(value, 10) };
				break;
			case "c":
				current.command = value;
				break;
			case "n":
				current.address = value;
				break;
		}
	}
	if (current.pid) listeners.push(current);
	return listeners;
}
function parseSsOutput(output, port) {
	const listeners = [];
	for (const line of output.split("\n")) {
		const trimmed = line.trim();
		if (!trimmed || !trimmed.includes("LISTEN")) continue;
		if (!trimmed.includes(`:${port}`)) continue;
		const listener = {};
		const pidMatch = trimmed.match(/pid=(\d+)/);
		if (pidMatch) listener.pid = parseInt(pidMatch[1], 10);
		const cmdMatch = trimmed.match(/users:\(\("([^"]+)"/);
		if (cmdMatch) listener.command = cmdMatch[1];
		const addrPart = trimmed.split(/\s+/).find((p) => p.includes(`:${port}`));
		if (addrPart) listener.address = addrPart;
		listeners.push(listener);
	}
	return listeners;
}
async function probePortAvailability(port) {
	for (const host of ["127.0.0.1", "0.0.0.0"]) if (await tryListenOnPort(port, host) === "busy") return "busy";
	return "free";
}
function tryListenOnPort(port, host) {
	return new Promise((resolve) => {
		const server = createServer();
		server.once("error", (err) => {
			if (err.code === "EADDRINUSE") resolve("busy");
			else resolve("unknown");
		});
		server.listen(port, host, () => {
			server.close(() => resolve("free"));
		});
	});
}
function buildPortHints(listeners, port) {
	const hints = [];
	for (const listener of listeners) {
		const cmd = (listener.command || listener.commandLine || "").toLowerCase();
		if (cmd.includes("xopc") || cmd.includes("gateway")) hints.push(`xopc gateway is already running (pid ${listener.pid}).`);
	}
	if (hints.length === 0 && listeners.length > 0) {
		const pids = listeners.map((l) => l.pid).filter(Boolean);
		if (pids.length > 0) hints.push(`Port ${port} is used by pid ${pids.join(", ")}.`);
	}
	return hints;
}
function runCommand(args) {
	return new Promise((resolve) => {
		try {
			const [cmd, ...cmdArgs] = args;
			const child = spawn(cmd, cmdArgs, {
				stdio: [
					"ignore",
					"pipe",
					"pipe"
				],
				timeout: 5e3
			});
			let stdout = "";
			let stderr = "";
			child.stdout?.on("data", (data) => {
				stdout += data.toString();
			});
			child.stderr?.on("data", (data) => {
				stderr += data.toString();
			});
			child.on("close", (code) => {
				resolve({
					stdout,
					stderr,
					exitCode: code
				});
			});
			child.on("error", (err) => {
				resolve({
					stdout: "",
					stderr: "",
					exitCode: null,
					error: err.message
				});
			});
		} catch (err) {
			resolve({
				stdout: "",
				stderr: "",
				exitCode: null,
				error: String(err)
			});
		}
	});
}
//#endregion
export { classifyPortListener, formatPortDiagnostics, inspectPortUsage };

//# sourceMappingURL=ports.js.map