/**
 * Daemon IPC — start, stop, and query the cron daemon process.
 *
 * The daemon is a detached child process that outlives the parent.
 * Communication is via PID file + signals (no sockets needed).
 */
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { resolveOwnBinary } from '../../config.js';
import { getCronDir } from './store.js';
export function getDaemonPidPath() {
    return path.join(getCronDir(), 'daemon.pid');
}
function getDaemonLogPath() {
    return path.join(getCronDir(), 'daemon.log');
}
/** Check if the daemon is alive by reading the PID file and pinging the process. */
export function getDaemonStatus() {
    let pid;
    try {
        const raw = fs.readFileSync(getDaemonPidPath(), 'utf-8').trim();
        pid = parseInt(raw, 10);
        if (!Number.isFinite(pid) || pid <= 0)
            return { running: false };
    }
    catch {
        return { running: false };
    }
    try {
        process.kill(pid, 0); // signal 0 = existence check
        return { running: true, pid };
    }
    catch {
        // Stale PID file — process is gone
        cleanupPidFile();
        return { running: false };
    }
}
export function isDaemonRunning() {
    return getDaemonStatus().running;
}
// ---------------------------------------------------------------------------
// Start
// ---------------------------------------------------------------------------
/** Spawn the daemon as a detached background process. Returns the PID. */
export async function startDaemon() {
    const status = getDaemonStatus();
    if (status.running && status.pid) {
        return { pid: status.pid, alreadyRunning: true };
    }
    await fs.promises.mkdir(getCronDir(), { recursive: true, mode: 0o700 });
    const [cmd, ...args] = resolveOwnBinary();
    // Wrap fd in try/finally to prevent leak on spawn error
    const logFd = fs.openSync(getDaemonLogPath(), 'a');
    let child;
    try {
        child = spawn(cmd, [...args, 'cron-daemon'], {
            detached: true,
            stdio: ['ignore', logFd, logFd],
            cwd: process.env.HOME ?? process.cwd()
        });
    }
    finally {
        fs.closeSync(logFd);
    }
    // Guard against undefined pid (spawn failure)
    if (!child.pid) {
        throw new Error('Failed to spawn cron daemon process');
    }
    const pid = child.pid;
    child.unref();
    // PID file is written by the daemon itself (daemon.ts startDaemonLoop).
    // Poll briefly for it to appear so callers get an accurate status.
    for (let i = 0; i < 20; i++) {
        await new Promise((r) => setTimeout(r, 50));
        try {
            fs.accessSync(getDaemonPidPath());
            break;
        }
        catch {
            // Not yet
        }
    }
    return { pid, alreadyRunning: false };
}
// ---------------------------------------------------------------------------
// Stop
// ---------------------------------------------------------------------------
/** Stop the daemon by sending SIGTERM. Returns true if the daemon was running. */
export async function stopDaemon() {
    const status = getDaemonStatus();
    if (!status.running || !status.pid)
        return false;
    try {
        process.kill(status.pid, 'SIGTERM');
    }
    catch {
        // Process already gone
    }
    // Wait for process to exit before cleaning up PID file
    const pid = status.pid;
    let exited = false;
    for (let i = 0; i < 20; i++) {
        await new Promise((r) => setTimeout(r, 50));
        try {
            process.kill(pid, 0);
        }
        catch {
            exited = true;
            break;
        }
    }
    if (exited) {
        cleanupPidFile();
    }
    // If not exited after 1s, leave PID file so isDaemonRunning() still reports true
    return true;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function cleanupPidFile() {
    try {
        fs.unlinkSync(getDaemonPidPath());
    }
    catch {
        // Best effort
    }
}
//# sourceMappingURL=daemon-ipc.js.map