/**
 * Daemon Manager
 *
 * Manages the Code Buddy daemon process lifecycle:
 * - Start/stop/restart daemon as a detached background process
 * - PID file management (~/.codebuddy/daemon/codebuddy.pid)
 * - Log file management (~/.codebuddy/daemon/codebuddy.log)
 * - Auto-restart on crash (max 3 attempts)
 */
import { fork } from 'child_process';
import * as fs from 'fs/promises';
import * as fsSync from 'fs';
import * as path from 'path';
import { homedir } from 'os';
import { EventEmitter } from 'events';
import { logger } from '../utils/logger.js';
export const DEFAULT_DAEMON_CONFIG = {
    pidFile: path.join(homedir(), '.codebuddy', 'daemon', 'codebuddy.pid'),
    logFile: path.join(homedir(), '.codebuddy', 'daemon', 'codebuddy.log'),
    maxRestarts: 3,
    autoStart: ['server', 'scheduler'],
    port: 3000,
    healthCheckIntervalMs: 30000,
};
// ============================================================================
// Daemon Manager
// ============================================================================
export class DaemonManager extends EventEmitter {
    config;
    childProcess = null;
    restartCount = 0;
    startedAt = null;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_DAEMON_CONFIG, ...config };
    }
    /**
     * Start the daemon process
     */
    async start(detach = false) {
        // Check if already running
        const existingPid = await this.readPid();
        if (existingPid && this.isProcessRunning(existingPid)) {
            throw new Error(`Daemon already running (PID: ${existingPid})`);
        }
        // Ensure directories exist
        await fs.mkdir(path.dirname(this.config.pidFile), { recursive: true });
        await fs.mkdir(path.dirname(this.config.logFile), { recursive: true });
        if (detach) {
            await this.startDetached();
        }
        else {
            await this.startForeground();
        }
    }
    /**
     * Start daemon as a detached background process
     */
    async startDetached() {
        const logFd = fsSync.openSync(this.config.logFile, 'a');
        try {
            // Resolve the entry point for the forked daemon process
            const entryPoint = path.resolve(__dirname, '..', 'index.js');
            const child = fork(entryPoint, ['daemon', '__run__', '--port', String(this.config.port)], {
                detached: true,
                stdio: ['ignore', logFd, logFd, 'ipc'],
                env: {
                    ...process.env,
                    CODEBUDDY_DAEMON: 'true',
                    CODEBUDDY_DAEMON_CONFIG: JSON.stringify(this.config),
                },
            });
            if (child.pid) {
                await this.writePid(child.pid);
                this.startedAt = new Date();
                child.unref();
                logger.info(`Daemon started (PID: ${child.pid})`);
                this.emit('started', { pid: child.pid, detached: true });
            }
        }
        finally {
            fsSync.closeSync(logFd);
        }
    }
    /**
     * Start daemon in the foreground (for __run__ mode)
     */
    async startForeground() {
        const pid = process.pid;
        await this.writePid(pid);
        this.startedAt = new Date();
        logger.info(`Daemon started in foreground (PID: ${pid})`);
        this.emit('started', { pid, detached: false });
    }
    /**
     * Stop the daemon process
     */
    async stop() {
        const pid = await this.readPid();
        if (!pid) {
            throw new Error('Daemon is not running (no PID file)');
        }
        if (!this.isProcessRunning(pid)) {
            await this.removePid();
            throw new Error('Daemon process not found (stale PID file removed)');
        }
        try {
            process.kill(pid, 'SIGTERM');
            // Wait for process to exit
            await this.waitForExit(pid, 10000);
        }
        catch {
            // Force kill if graceful shutdown fails
            try {
                process.kill(pid, 'SIGKILL');
            }
            catch {
                // Process already gone
            }
        }
        await this.removePid();
        logger.info(`Daemon stopped (PID: ${pid})`);
        this.emit('stopped', { pid });
    }
    /**
     * Restart the daemon
     */
    async restart() {
        try {
            await this.stop();
        }
        catch {
            // Ignore stop errors during restart
        }
        await new Promise(resolve => setTimeout(resolve, 1000));
        await this.start(true);
    }
    /**
     * Get daemon status
     */
    async status() {
        const pid = await this.readPid();
        if (!pid || !this.isProcessRunning(pid)) {
            return {
                running: false,
                services: [],
                restartCount: this.restartCount,
            };
        }
        const uptime = this.startedAt
            ? Date.now() - this.startedAt.getTime()
            : undefined;
        return {
            running: true,
            pid,
            uptime,
            startedAt: this.startedAt || undefined,
            services: [], // Populated by DaemonLifecycle
            restartCount: this.restartCount,
        };
    }
    /**
     * Get recent log lines
     */
    async logs(lines = 50) {
        try {
            const content = await fs.readFile(this.config.logFile, 'utf-8');
            const allLines = content.trim().split('\n');
            return allLines.slice(-lines).join('\n');
        }
        catch {
            return '(no logs available)';
        }
    }
    /**
     * Attempt auto-restart if crashes
     */
    async attemptAutoRestart() {
        if (this.restartCount >= this.config.maxRestarts) {
            logger.error(`Max restart attempts reached (${this.config.maxRestarts})`);
            this.emit('max-restarts', { count: this.restartCount });
            return false;
        }
        this.restartCount++;
        logger.warn(`Auto-restart attempt ${this.restartCount}/${this.config.maxRestarts}`);
        try {
            await this.start(true);
            return true;
        }
        catch (error) {
            logger.error('Auto-restart failed', error);
            return false;
        }
    }
    // ==========================================================================
    // PID management
    // ==========================================================================
    async writePid(pid) {
        await fs.writeFile(this.config.pidFile, String(pid), 'utf-8');
    }
    async readPid() {
        try {
            const content = await fs.readFile(this.config.pidFile, 'utf-8');
            const pid = parseInt(content.trim(), 10);
            return Number.isFinite(pid) ? pid : null;
        }
        catch {
            return null;
        }
    }
    async removePid() {
        try {
            await fs.unlink(this.config.pidFile);
        }
        catch {
            // Ignore if already removed
        }
    }
    isProcessRunning(pid) {
        try {
            process.kill(pid, 0);
            return true;
        }
        catch {
            return false;
        }
    }
    async waitForExit(pid, timeoutMs) {
        const start = Date.now();
        while (Date.now() - start < timeoutMs) {
            if (!this.isProcessRunning(pid))
                return;
            await new Promise(resolve => setTimeout(resolve, 200));
        }
        throw new Error('Process did not exit in time');
    }
    getConfig() {
        return { ...this.config };
    }
}
// ============================================================================
// Singleton
// ============================================================================
let instance = null;
export function getDaemonManager(config) {
    if (!instance) {
        instance = new DaemonManager(config);
    }
    return instance;
}
export function resetDaemonManager() {
    instance = null;
}
//# sourceMappingURL=daemon-manager.js.map