import { r as __exportAll } from "./chunk-D_gEzPfs.js";
import { u as resolveGatewayPort } from "./paths-VdmjGGyc.js";
import { o as loadConfig, u as writeConfigFile } from "./config-BOTdzMIB.js";
import { h as resolveControlUiLinks, m as randomToken, p as probeGatewayReachable, v as waitForGatewayReachable } from "./onboard-helpers-DXmuVr9a.js";
import { i as buildGatewayInstallPlan, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-EHtDn8NC.js";
import { o as resolveGatewayLogPaths, t as resolveGatewayService } from "./service-C6lyBW0F.js";
import { t as readLastGatewayErrorLine } from "./diagnostics-B6_fjn4Z.js";
import process from "node:process";
import { mkdirSync, openSync } from "node:fs";
import { spawn } from "node:child_process";
import { Writable } from "node:stream";

//#region src/commands/ensure-local-gateway.ts
/**
* @module commands/ensure-local-gateway
*
* Make sure ONE authoritative local gateway is RUNNING and serving the current
* token, so the TUI/koi/nodes can connect.
*
* The hard-won rule here: never run two gateways at once. The previous version
* would, whenever the already-running gateway answered the probe with a
* different token (stale token, stale build, or mid-restart), spawn a *second*
* detached gateway that fought the first for port 18789 — producing the
* recurring "Port 18789 is already in use" + "gateway token mismatch" loop that
* left the device unreachable and the node-host crash-looping.
*
* Now: if a SUPERVISED service (launchd / systemd / Scheduled Task) is
* installed, we re-point it at this config + token and reload it (a full
* bootout → bootstrap → kickstart via the service's own install path), so the
* supervised gateway stays the single source of truth. We only ever spawn a
* detached standalone gateway when there is NO supervised service — and even
* then we pass `--force` so we evict any stale listener instead of colliding.
*/
var ensure_local_gateway_exports = /* @__PURE__ */ __exportAll({ ensureLocalGatewayRunning: () => ensureLocalGatewayRunning });
const REACH_DEADLINE_MS = process.platform === "win32" ? 45e3 : 3e4;
/** Build a reachability-failure message that includes the REAL cause when we can
* find it — the gateway's own last error log line — instead of an opaque
* "1006 abnormal closure" with no detail. */
async function reachabilityError(prefix, detail) {
	const logLine = await readLastGatewayErrorLine(process.env).catch(() => null);
	let msg = prefix;
	if (detail) msg += ` (${detail})`;
	if (logLine) msg += ` — gateway log: ${logLine}`;
	return msg;
}
const silentStdout = () => new Writable({ write(_chunk, _enc, cb) {
	cb();
} });
async function ensureLocalGatewayRunning() {
	const config = loadConfig();
	const gateway = config.gateway ??= {};
	if (gateway.mode !== "local") gateway.mode = "local";
	if (!gateway.bind) gateway.bind = "loopback";
	const auth = gateway.auth ??= {};
	if (!auth.mode) auth.mode = "token";
	if (!auth.token || !auth.token.trim()) auth.token = randomToken();
	const token = auth.token;
	await writeConfigFile(config);
	const port = resolveGatewayPort(config);
	const { wsUrl } = resolveControlUiLinks({
		bind: "loopback",
		port
	});
	if ((await probeGatewayReachable({
		url: wsUrl,
		token
	})).ok) return {
		ok: true,
		wsUrl,
		token,
		started: false
	};
	let plan;
	try {
		plan = await buildGatewayInstallPlan({
			env: process.env,
			port,
			token,
			runtime: DEFAULT_GATEWAY_DAEMON_RUNTIME,
			config
		});
	} catch (err) {
		return {
			ok: false,
			error: `Could not prepare the gateway (${errMsg(err)})`
		};
	}
	const service = resolveGatewayService();
	let supervised = false;
	try {
		supervised = await service.isLoaded({ env: process.env });
	} catch {
		supervised = false;
	}
	if (supervised) {
		try {
			await service.install({
				env: process.env,
				stdout: silentStdout(),
				programArguments: plan.programArguments,
				workingDirectory: plan.workingDirectory,
				environment: plan.environment
			});
		} catch (err) {
			return {
				ok: false,
				error: `Could not refresh the gateway service (${errMsg(err)})`
			};
		}
		const reachable = await waitForGatewayReachable({
			url: wsUrl,
			token,
			deadlineMs: REACH_DEADLINE_MS
		});
		if (reachable.ok) return {
			ok: true,
			wsUrl,
			token,
			started: true
		};
		return {
			ok: false,
			error: await reachabilityError("Refreshed the gateway service but it didn't come back reachable in time", reachable.detail),
			wsUrl,
			token
		};
	}
	const [cmd, ...args] = plan.programArguments;
	if (!cmd) return {
		ok: false,
		error: "Gateway program arguments were unavailable"
	};
	let stdio = [
		"ignore",
		"ignore",
		"ignore"
	];
	try {
		const { logDir, stdoutPath, stderrPath } = resolveGatewayLogPaths(process.env);
		mkdirSync(logDir, { recursive: true });
		stdio = [
			"ignore",
			openSync(stdoutPath, "a"),
			openSync(stderrPath, "a")
		];
	} catch {}
	try {
		spawn(cmd, [...args, "--force"], {
			cwd: plan.workingDirectory,
			env: {
				...process.env,
				...plan.environment
			},
			detached: true,
			stdio
		}).unref();
	} catch (err) {
		return {
			ok: false,
			error: `Could not start the gateway (${errMsg(err)})`
		};
	}
	const reachable = await waitForGatewayReachable({
		url: wsUrl,
		token,
		deadlineMs: REACH_DEADLINE_MS
	});
	if (!reachable.ok) return {
		ok: false,
		error: await reachabilityError("Gateway started but isn't reachable yet", reachable.detail),
		wsUrl,
		token
	};
	return {
		ok: true,
		wsUrl,
		token,
		started: true
	};
}
function errMsg(err) {
	return err?.message ?? String(err);
}

//#endregion
export { ensure_local_gateway_exports as n, ensureLocalGatewayRunning as t };