import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { access, readdir, unlink } from "node:fs/promises";
import { join } from "node:path";
import { spawn } from "node:child_process";
//#region src/infra/update-runner.ts
init_logger();
const log = createLogger("UpdateRunner");
const AUTO_UPDATE_TIMEOUT_MS = 2700 * 1e3;
function createLineEmitter(onProgress) {
	let bufOut = "";
	let bufErr = "";
	const flush = (buf, source) => {
		const parts = buf.split("\n");
		const rest = parts.pop() ?? "";
		for (const line of parts) if (line.length) onProgress?.(line, source);
		return rest;
	};
	return {
		pushStdout(chunk) {
			bufOut += chunk;
			bufOut = flush(bufOut, "stdout");
		},
		pushStderr(chunk) {
			bufErr += chunk;
			bufErr = flush(bufErr, "stderr");
		},
		flushEnd() {
			if (bufOut.length) onProgress?.(bufOut, "stdout");
			if (bufErr.length) onProgress?.(bufErr, "stderr");
		}
	};
}
async function spawnUpdateCommand(params) {
	const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;
	const argv = await resolveUpdateCommandArgv([
		"update",
		"--yes",
		"--channel",
		params.channel,
		"--json"
	], params.root ?? null);
	return new Promise((resolve) => {
		const child = spawn(argv[0], argv.slice(1), {
			env: {
				...process.env,
				XOPC_AUTO_UPDATE: "1"
			},
			stdio: [
				"ignore",
				"pipe",
				"pipe"
			],
			detached: false
		});
		let stdout = "";
		let stderr = "";
		let stdoutTruncated = false;
		let stderrTruncated = false;
		const lineEmitter = createLineEmitter(params.onProgress);
		const timeoutId = setTimeout(() => {
			child.kill("SIGTERM");
		}, timeoutMs);
		const finish = (result) => {
			clearTimeout(timeoutId);
			lineEmitter.flushEnd();
			resolve(result);
		};
		child.stdout?.on("data", (chunk) => {
			const text = chunk.toString();
			stdout += text;
			lineEmitter.pushStdout(text);
			if (stdout.length > 64e3) {
				if (!stdoutTruncated) {
					log.warn("Update command stdout exceeded 64KB; truncating");
					stdoutTruncated = true;
				}
				stdout = stdout.slice(-32e3);
			}
		});
		child.stderr?.on("data", (chunk) => {
			const text = chunk.toString();
			stderr += text;
			lineEmitter.pushStderr(text);
			if (stderr.length > 64e3) {
				if (!stderrTruncated) {
					log.warn("Update command stderr exceeded 64KB; truncating");
					stderrTruncated = true;
				}
				stderr = stderr.slice(-32e3);
			}
		});
		child.on("error", (err) => {
			log.error({ err }, `Update subprocess spawn error: ${err.message}`);
			finish({
				ok: false,
				exitCode: null,
				reason: err.message,
				stdout,
				stderr
			});
		});
		child.on("exit", (code, signal) => {
			if (signal === "SIGTERM" || code === 143) {
				log.warn({
					code,
					signal
				}, "Update subprocess timed out; attempting lock file cleanup");
				cleanupNpmLockFiles(params.root).catch((cleanupErr) => {
					log.warn({ err: cleanupErr }, "Failed to clean npm lock files after timeout");
				});
				finish({
					ok: false,
					exitCode: code,
					reason: "timeout",
					stdout,
					stderr
				});
				return;
			}
			finish({
				ok: code === 0,
				exitCode: code,
				reason: code === 0 ? void 0 : "non-zero-exit",
				stdout,
				stderr
			});
		});
	});
}
async function runAutoUpdateCommand(params) {
	return spawnUpdateCommand(params);
}
async function runAutoUpdateCommandWithProgress(params) {
	return spawnUpdateCommand(params);
}
async function cleanupNpmLockFiles(root) {
	if (!root) return;
	let entries;
	try {
		entries = await readdir(root);
	} catch {
		return;
	}
	for (const entry of entries) if (entry.startsWith(".package-lock")) try {
		await unlink(join(root, entry));
	} catch {}
}
/**
* Resolve the argv array for spawning the update command.
*
* Priority:
* 1. process.execPath + process.argv[1] (current runtime + entry point)
* 2. process.execPath + known dist entry points in root
* 3. Fallback to bare `xopc` (assumes global install)
*/
async function resolveUpdateCommandArgv(baseArgs, root) {
	const execPath = process.execPath?.trim();
	const argv1 = process.argv[1]?.trim();
	if (execPath && argv1) return [
		execPath,
		argv1,
		...baseArgs
	];
	if (execPath && root) {
		const candidates = [join(root, "dist/src/cli/bin.js"), join(root, "dist/index.js")];
		for (const candidate of candidates) try {
			await access(candidate);
			return [
				execPath,
				candidate,
				...baseArgs
			];
		} catch {}
	}
	log.warn("Falling back to bare `xopc` command — version mismatch possible");
	try {
		const { execSync } = await import("node:child_process");
		const whichResult = execSync(process.platform === "win32" ? "where xopc" : "which xopc", {
			encoding: "utf-8",
			timeout: 3e3
		}).trim();
		if (whichResult) log.info({ resolvedPath: whichResult.split(/\r?\n/)[0]?.trim() }, "Resolved xopc via PATH");
	} catch {
		log.warn("Could not resolve `xopc` in PATH; update command may fail");
	}
	return ["xopc", ...baseArgs];
}
//#endregion
export { runAutoUpdateCommand, runAutoUpdateCommandWithProgress };

//# sourceMappingURL=update-runner.js.map