import { execFile } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { promisify } from "node:util";
import treeKill from "tree-kill";
import { readConfigListen, resolvePaseoHome } from "./paseo-home.js";
import { resolveSupervisorInvocation } from "./resolve-supervisor.js";
import { spawnHidden } from "./spawn-hidden.js";
const DETACHED_STARTUP_GRACE_MS = 1200;
const STOP_TIMEOUT_MS = 15_000;
function readPidFile(pidPath) {
    try {
        const parsed = JSON.parse(readFileSync(pidPath, "utf8"));
        const pid = parsed.pid;
        if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
            return null;
        }
        return {
            pid,
            startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : undefined,
            hostname: typeof parsed.hostname === "string" ? parsed.hostname : undefined,
            listen: typeof parsed.listen === "string" ? parsed.listen : undefined,
            desktopManaged: parsed.desktopManaged === true,
        };
    }
    catch {
        return null;
    }
}
function isProcessRunning(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch (error) {
        const code = typeof error === "object" && error !== null && "code" in error ? error.code : null;
        if (code === "EPERM") {
            return true;
        }
        return false;
    }
}
function readServerId(home) {
    const serverIdPath = path.join(home, "server-id");
    if (!existsSync(serverIdPath)) {
        return null;
    }
    const value = readFileSync(serverIdPath, "utf8").trim();
    return value || null;
}
function readRelayEnabled(home) {
    const configPath = path.join(home, "config.json");
    if (!existsSync(configPath)) {
        return true;
    }
    try {
        const parsed = JSON.parse(readFileSync(configPath, "utf8"));
        return parsed.daemon?.relay?.enabled !== false;
    }
    catch {
        return true;
    }
}
function resolveSupervisorInvocationForStart() {
    return resolveSupervisorInvocation();
}
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForPidExit(pid, timeoutMs) {
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
        if (!isProcessRunning(pid)) {
            return true;
        }
        await sleep(100);
    }
    return !isProcessRunning(pid);
}
const execFileAsync = promisify(execFile);
async function killProcessTree(pid, signal) {
    if (process.platform === "win32") {
        if (signal !== "SIGKILL" && signal !== "SIGTERM") {
            return false;
        }
        try {
            await execFileAsync("taskkill", ["/pid", String(pid), "/T", "/F"], {
                windowsHide: true,
            });
            return true;
        }
        catch (error) {
            const code = typeof error === "object" && error !== null && "code" in error ? error.code : null;
            if (code === 128 || code === 1) {
                return false;
            }
            throw error;
        }
    }
    return new Promise((resolve, reject) => {
        treeKill(pid, signal, (error) => {
            if (!error) {
                resolve(true);
                return;
            }
            const code = typeof error === "object" && error !== null && "code" in error ? error.code : null;
            if (code === "ESRCH") {
                resolve(false);
                return;
            }
            if (code === "EPERM") {
                resolve(true);
                return;
            }
            reject(error);
        });
    });
}
export function resolveLocalDaemonSnapshot() {
    const home = resolvePaseoHome();
    const configListen = readConfigListen(home) ?? process.env.PASEO_LISTEN?.trim() ?? "0.0.0.0:6769";
    const pidPath = path.join(home, "paseo.pid");
    const logPath = path.join(home, "daemon.log");
    const pidInfo = existsSync(pidPath) ? readPidFile(pidPath) : null;
    const running = pidInfo ? isProcessRunning(pidInfo.pid) : false;
    return {
        home,
        listen: pidInfo?.listen ?? configListen,
        logPath,
        pidInfo,
        running,
        stalePidFile: Boolean(pidInfo) && !running,
        serverId: readServerId(home),
        relayEnabled: readRelayEnabled(home),
    };
}
export async function startLocalDaemon() {
    if (!process.env.PASEO_LISTEN?.trim()) {
        process.env.PASEO_LISTEN = "0.0.0.0:6769";
    }
    const snapshot = resolveLocalDaemonSnapshot();
    if (snapshot.running) {
        return { message: `Daemon already running (PID ${snapshot.pidInfo?.pid})` };
    }
    const { command, args } = resolveSupervisorInvocationForStart();
    const child = spawnHidden(command, args, { env: process.env });
    child.unref();
    await sleep(DETACHED_STARTUP_GRACE_MS);
    const after = resolveLocalDaemonSnapshot();
    if (!after.running) {
        throw new Error("Daemon failed to start. Check daemon.log for details.");
    }
    return {
        message: `Daemon started (PID ${after.pidInfo?.pid ?? child.pid ?? "unknown"})`,
    };
}
export async function stopLocalDaemon() {
    const snapshot = resolveLocalDaemonSnapshot();
    if (!snapshot.pidInfo?.pid || !snapshot.running) {
        return { message: "Daemon is not running" };
    }
    const pid = snapshot.pidInfo.pid;
    await killProcessTree(pid, "SIGTERM");
    const stopped = await waitForPidExit(pid, STOP_TIMEOUT_MS);
    if (!stopped) {
        await killProcessTree(pid, "SIGKILL");
        await waitForPidExit(pid, 3000);
    }
    const after = resolveLocalDaemonSnapshot();
    if (after.running) {
        throw new Error(`Failed to stop daemon (PID ${pid})`);
    }
    return { message: `Daemon stopped (PID ${pid})` };
}
export async function restartLocalDaemon() {
    const stopResult = await stopLocalDaemon();
    if (!stopResult.message.includes("not running")) {
        await sleep(500);
    }
    const startResult = await startLocalDaemon();
    return { message: `${stopResult.message}. ${startResult.message}` };
}
