import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { resolveGatewayLaunchAgentLabel, resolveLaunchAgentPlistPath as resolveLaunchAgentPlistPath$1 } from "./constants.js";
import { existsSync } from "node:fs";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { spawn, spawnSync } from "node:child_process";
//#region src/daemon/launchd.ts
/**
* LaunchAgent Service - macOS user service management
*
* Aligned with OpenClaw launchd implementation:
* - KeepAlive with SuccessfulExit=false
* - ThrottleInterval for restart throttling
* - ExitTimeOut for graceful shutdown
* - launchctl bootstrap/bootout for modern service management
* - launchctl kickstart -k for restart
*/
init_logger();
const log = createLogger("LaunchdService");
function resolveGuiDomain() {
	return `gui/${typeof process.getuid === "function" ? process.getuid() : 501}`;
}
function resolveProfileFromEnv(env) {
	return env?.XOPC_PROFILE?.trim() || void 0;
}
function resolveLaunchAgentPlistPath(env) {
	return resolveLaunchAgentPlistPath$1(resolveProfileFromEnv(env));
}
function resolveLabelFromEnv(env) {
	return resolveGatewayLaunchAgentLabel(resolveProfileFromEnv(env));
}
function resolveServiceTarget(env) {
	return `${resolveGuiDomain()}/${resolveLabelFromEnv(env)}`;
}
function resolveLogDir() {
	return path.join(os.homedir(), ".xopc", "logs");
}
function escapeXml(str) {
	return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}
function buildLaunchAgentPlist(params) {
	const envDict = Object.entries(params.environment).map(([k, v]) => `        <key>${k}</key>\n        <string>${escapeXml(v)}</string>`).join("\n");
	const programArgs = params.programArguments.map((arg) => `        <string>${escapeXml(arg)}</string>`).join("\n");
	let plist = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${escapeXml(params.label)}</string>
    <key>ProgramArguments</key>
    <array>
${programArgs}
    </array>
`;
	if (params.workingDirectory) plist += `    <key>WorkingDirectory</key>
    <string>${escapeXml(params.workingDirectory)}</string>
`;
	if (Object.keys(params.environment).length > 0) plist += `    <key>EnvironmentVariables</key>
    <dict>
${envDict}
    </dict>
`;
	if (params.stdoutPath) plist += `    <key>StandardOutPath</key>
    <string>${escapeXml(params.stdoutPath)}</string>
`;
	if (params.stderrPath) plist += `    <key>StandardErrorPath</key>
    <string>${escapeXml(params.stderrPath)}</string>
`;
	plist += `    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>ThrottleInterval</key>
    <integer>10</integer>
    <key>ExitTimeOut</key>
    <integer>20</integer>
    <key>ProcessType</key>
    <string>Interactive</string>
</dict>
</plist>`;
	return plist;
}
async function launchctl(args) {
	return new Promise((resolve, reject) => {
		const child = spawn("launchctl", args, { stdio: [
			"ignore",
			"pipe",
			"pipe"
		] });
		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) => {
			reject(/* @__PURE__ */ new Error(`launchctl spawn failed: ${err.message}`));
		});
	});
}
async function launchctlExec(args) {
	const result = await launchctl(args);
	if (result.stderr.trim()) log.debug({
		stderr: result.stderr.trim(),
		args
	}, "launchctl stderr");
	return result.stdout;
}
function isLaunchdAvailable() {
	if (process.platform !== "darwin") return false;
	try {
		return spawnSync("launchctl", ["version"], {
			stdio: [
				"ignore",
				"pipe",
				"ignore"
			],
			timeout: 3e3
		}).status === 0;
	} catch {
		return false;
	}
}
function parsePlistProgramArguments(content) {
	const argsMatch = content.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
	if (!argsMatch) return [];
	const programArgs = [];
	const stringMatches = argsMatch[1].matchAll(/<string>([\s\S]*?)<\/string>/g);
	for (const m of stringMatches) programArgs.push(unescapeXml(m[1]));
	return programArgs;
}
function parsePlistEnvironment(content) {
	const envMatch = content.match(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/);
	if (!envMatch) return {};
	const environment = {};
	const pairs = envMatch[1].matchAll(/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/g);
	for (const pair of pairs) environment[unescapeXml(pair[1])] = unescapeXml(pair[2]);
	return environment;
}
function parsePlistStringValue(content, key) {
	const regex = new RegExp(`<key>${key}</key>\\s*<string>([\\s\\S]*?)</string>`);
	const match = content.match(regex);
	return match ? unescapeXml(match[1]) : void 0;
}
function unescapeXml(str) {
	return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'");
}
const launchdService = {
	label: resolveGatewayLaunchAgentLabel(),
	loadedText: "LaunchAgent (loaded)",
	notLoadedText: "LaunchAgent (not loaded)",
	async install(args) {
		const label = resolveLabelFromEnv(args.env);
		const plistPath = resolveLaunchAgentPlistPath(args.env);
		const logDir = resolveLogDir();
		await mkdir(path.dirname(plistPath), { recursive: true });
		await mkdir(logDir, { recursive: true });
		const environment = {};
		if (args.environment) Object.assign(environment, args.environment);
		await writeFile(plistPath, buildLaunchAgentPlist({
			label,
			programArguments: args.programArguments,
			workingDirectory: args.workingDirectory,
			environment,
			stdoutPath: path.join(logDir, "gateway.log"),
			stderrPath: path.join(logDir, "gateway.err.log")
		}), "utf8");
		args.stdout?.write(`Written: ${plistPath}\n`);
		const result = await launchctl([
			"bootstrap",
			resolveGuiDomain(),
			plistPath
		]);
		if (result.exitCode !== 0 && result.exitCode !== 37) {
			const detail = result.stderr.trim() || result.stdout.trim();
			if (detail) log.warn({
				detail,
				exitCode: result.exitCode
			}, "launchctl bootstrap warning");
		}
		log.info({
			label,
			plistPath
		}, "LaunchAgent installed and bootstrapped");
	},
	async uninstall(args) {
		const serviceTarget = resolveServiceTarget(args.env);
		const plistPath = resolveLaunchAgentPlistPath(args.env);
		try {
			await launchctlExec(["bootout", serviceTarget]);
		} catch {}
		if (existsSync(plistPath)) {
			await rm(plistPath);
			args.stdout?.write(`Removed: ${plistPath}\n`);
		}
		log.info({ plistPath }, "LaunchAgent uninstalled");
	},
	async stop(args) {
		const serviceTarget = resolveServiceTarget(args.env);
		if (args.disable) {
			const plistPath = resolveLaunchAgentPlistPath(args.env);
			try {
				await launchctlExec(["bootout", serviceTarget]);
			} catch {}
			if (existsSync(plistPath)) await rm(plistPath);
			log.info("LaunchAgent stopped and disabled (plist removed)");
		} else {
			try {
				await launchctlExec(["bootout", serviceTarget]);
			} catch {
				log.debug("LaunchAgent bootout failed (may not be loaded)");
			}
			log.info("LaunchAgent stopped and unloaded");
		}
	},
	async restart(args) {
		const serviceTarget = resolveServiceTarget(args.env);
		if ((await launchctl([
			"kickstart",
			"-k",
			serviceTarget
		])).exitCode === 0) {
			log.info("LaunchAgent restarted via kickstart");
			return { outcome: "restarted" };
		}
		const plistPath = resolveLaunchAgentPlistPath(args.env);
		const domain = resolveGuiDomain();
		try {
			await launchctlExec(["bootout", serviceTarget]);
		} catch {}
		const bootstrapResult = await launchctl([
			"bootstrap",
			domain,
			plistPath
		]);
		if (bootstrapResult.exitCode === 0 || bootstrapResult.exitCode === 37) {
			log.info("LaunchAgent restarted via bootout+bootstrap");
			return { outcome: "restarted" };
		}
		throw new Error(`Failed to restart LaunchAgent: ${bootstrapResult.stderr.trim() || "unknown error"}`);
	},
	async isLoaded(args) {
		return (await launchctl(["print", resolveServiceTarget(args.env)])).exitCode === 0;
	},
	async readRuntime(env) {
		const serviceTarget = resolveServiceTarget(env);
		try {
			const result = await launchctl(["print", serviceTarget]);
			if (result.exitCode !== 0) return { status: "stopped" };
			const output = result.stdout;
			let pid;
			const pidMatch = output.match(/pid\s*=\s*(\d+)/);
			if (pidMatch) {
				const parsed = parseInt(pidMatch[1], 10);
				if (parsed > 0) pid = parsed;
			}
			let lastExitStatus;
			const exitMatch = output.match(/last exit code\s*=\s*(\d+)/i);
			if (exitMatch) lastExitStatus = parseInt(exitMatch[1], 10);
			return {
				status: pid ? "running" : "stopped",
				pid,
				lastExitStatus
			};
		} catch {
			return { status: "unknown" };
		}
	},
	async readCommand(env) {
		const plistPath = resolveLaunchAgentPlistPath(env);
		if (!existsSync(plistPath)) return null;
		const content = await readFile(plistPath, "utf8");
		const programArguments = parsePlistProgramArguments(content);
		if (programArguments.length === 0) return null;
		const environment = parsePlistEnvironment(content);
		return {
			programArguments,
			workingDirectory: parsePlistStringValue(content, "WorkingDirectory"),
			environment,
			sourcePath: plistPath
		};
	}
};
//#endregion
export { buildLaunchAgentPlist, isLaunchdAvailable, launchdService, resolveLaunchAgentPlistPath };

//# sourceMappingURL=launchd.js.map