"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serviceManager = void 0;
const pm2_1 = __importDefault(require("pm2"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const stateManager_1 = require("./stateManager");
const get_port_1 = __importStar(require("./utils/get-port"));
class ServiceManager {
    constructor() {
        this.isConnected = false;
    }
    async connect() {
        if (this.isConnected)
            return;
        try {
            await new Promise((resolve, reject) => {
                pm2_1.default.connect((err) => {
                    if (err) {
                        return reject(err);
                    }
                    this.isConnected = true;
                    console.log('PM2 daemon connected.');
                    resolve();
                });
            });
            await this.reconcileState();
        }
        catch (error) {
            console.error('Could not connect to PM2 daemon:', error);
            process.exit(1);
        }
    }
    async reconcileState() {
        console.log('Reconciling state between PM2 and state file...');
        const pm2Processes = await this.listPm2();
        const managedServices = stateManager_1.stateManager.getServices();
        for (const service of managedServices) {
            const pm2Process = pm2Processes.find(p => p.name === service.name);
            const newStatus = pm2Process?.pm2_env?.status === 'online' ? 'running' : 'stopped';
            const newPid = pm2Process?.pid ?? null;
            if (service.status !== newStatus || service.pid !== newPid) {
                stateManager_1.stateManager.updateService(service.name, { status: newStatus, pid: newPid });
                console.log(`Updated service ${service.name} to status: ${newStatus}`);
            }
        }
    }
    listPm2() {
        return new Promise((resolve, reject) => {
            pm2_1.default.list((err, list) => {
                if (err)
                    return reject(err);
                resolve(list);
            });
        });
    }
    startPm2(options) {
        return new Promise((resolve, reject) => {
            pm2_1.default.start(options, (err, proc) => {
                if (err)
                    return reject(err);
                // pm2.start returns an array of procs, but we start one at a time.
                const processDescription = (Array.isArray(proc) ? proc[0] : proc);
                resolve(processDescription);
            });
        });
    }
    stopPm2(name) {
        return new Promise((resolve, reject) => {
            pm2_1.default.stop(name, (err, proc) => {
                if (err)
                    return reject(err);
                resolve(proc);
            });
        });
    }
    restartPm2(name) {
        return new Promise((resolve, reject) => {
            pm2_1.default.restart(name, (err, proc) => {
                if (err)
                    return reject(err);
                const processDescription = (Array.isArray(proc) ? proc[0] : proc);
                resolve(processDescription);
            });
        });
    }
    deletePm2(name) {
        return new Promise((resolve, reject) => {
            pm2_1.default.delete(name, (err, proc) => {
                if (err)
                    return reject(err);
                resolve(proc);
            });
        });
    }
    _getLogPathsForName(name) {
        const pm2Home = process.env.PM2_HOME || path_1.default.join(os_1.default.homedir(), '.pm2');
        return {
            out: path_1.default.join(pm2Home, 'logs', `${name}-out.log`),
            err: path_1.default.join(pm2Home, 'logs', `${name}-error.log`),
        };
    }
    async start(projectPath, customName) {
        const absolutePath = path_1.default.resolve(projectPath);
        console.log(`Starting service absolutePath ${projectPath}`);
        const name = customName || path_1.default.basename(absolutePath);
        if (stateManager_1.stateManager.getService(name)) {
            console.log(`Service '${name}' already exists. Removing it before starting a new one.`);
            await this.remove(name);
        }
        const port = await (0, get_port_1.default)({ port: (0, get_port_1.portNumbers)(3400, 3999) });
        const isDevelopment = process.env.NODE_ENV === 'development';
        let script;
        let interpreter = undefined;
        if (isDevelopment) {
            interpreter = 'node';
            // In development, resolve relative to __dirname of this file
            script = path_1.default.resolve(__dirname, '../../backend/dist/main.js');
            console.log(`[ServiceManager] Development mode detected (NODE_ENV=${process.env.NODE_ENV}).`);
        }
        else {
            // In production, we use the command exposed by the npm package's bin field.
            // pm2 will resolve this from the system's PATH.
            script = 'repoburg-backend';
            console.log(`[ServiceManager] Production mode detected (NODE_ENV=${process.env.NODE_ENV}).`);
        }
        console.log(`[ServiceManager] Starting backend service using script: ${script}`);
        const logPaths = this._getLogPathsForName(name);
        console.log(`logPaths:`, logPaths);
        const options = {
            name,
            script,
            interpreter,
            exec_mode: 'fork',
            instances: 1,
            env: {
                ...process.env,
                NODE_ENV: 'production',
                PORT: String(port),
                REPOBURG_PROJECT_PATH: absolutePath,
                REPOBURG_INSTANCE_NAME: name,
            },
            output: logPaths.out,
            error: logPaths.err,
            log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
            merge_logs: true,
        };
        try {
            const pm2Process = await this.startPm2(options);
            const pid = pm2Process?.pid ?? null;
            const status = pm2Process?.pm2_env?.status === 'online' ? 'running' : 'errored';
            const newService = stateManager_1.stateManager.addService({ name, path: absolutePath, port, status, pid });
            console.log(`Started service '${name}' on port ${port} with PID ${pid}`);
            return newService;
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'An unknown error occurred';
            console.error(`Failed to start service for ${projectPath}:`, error);
            throw new Error(`Failed to start service: ${message}`);
        }
    }
    async stop(name) {
        const service = stateManager_1.stateManager.getService(name);
        if (!service)
            throw new Error(`Service '${name}' not found.`);
        try {
            await this.stopPm2(name);
            stateManager_1.stateManager.updateService(name, { status: 'stopped', pid: null });
            console.log(`Stopped service '${name}'.`);
            return stateManager_1.stateManager.getService(name);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'An unknown error occurred';
            console.error(`Failed to stop service '${name}':`, error);
            throw new Error(`Failed to stop service: ${message}`);
        }
    }
    async restart(name) {
        const service = stateManager_1.stateManager.getService(name);
        if (!service)
            throw new Error(`Service '${name}' not found.`);
        try {
            const pm2Process = await this.restartPm2(name);
            const pid = pm2Process?.pid ?? null;
            const status = pm2Process?.pm2_env?.status === 'online' ? 'running' : 'errored';
            stateManager_1.stateManager.updateService(name, { status, pid });
            console.log(`Restarted service '${name}' with new PID ${pid}`);
            return stateManager_1.stateManager.getService(name);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'An unknown error occurred';
            console.error(`Failed to restart service '${name}':`, error);
            throw new Error(`Failed to restart service: ${message}`);
        }
    }
    async remove(name) {
        const service = stateManager_1.stateManager.getService(name);
        if (!service)
            throw new Error(`Service '${name}' not found.`);
        try {
            await this.deletePm2(name);
            stateManager_1.stateManager.removeService(name);
            console.log(`Removed service '${name}'.`);
            return { message: `Service '${name}' removed successfully.` };
        }
        catch (error) {
            // If pm2 fails (e.g. process not found), still remove from our state
            stateManager_1.stateManager.removeService(name);
            const message = error instanceof Error ? error.message : 'An unknown error occurred';
            console.warn(`Could not delete service '${name}' from PM2, but removed from state: ${message}`);
            return { message: `Service '${name}' removed from state. Could not remove from PM2.` };
        }
    }
    async removeAll() {
        console.log('Attempting to remove all services.');
        const services = this.list();
        const results = [];
        if (services.length === 0) {
            console.log('No services to remove.');
            return { results: [] };
        }
        for (const service of services) {
            try {
                const result = await this.remove(service.name);
                results.push({ name: service.name, message: result.message });
            }
            catch (error) {
                const message = error instanceof Error ? error.message : 'An unknown error occurred';
                results.push({ name: service.name, message: `Failed to remove: ${message}` });
                console.error(`Failed to remove service ${service.name} during removeAll:`, error);
            }
        }
        console.log('Finished removing all services.');
        return { results };
    }
    list() {
        return stateManager_1.stateManager.getServices();
    }
    getLogPaths(name) {
        const service = stateManager_1.stateManager.getService(name);
        if (!service)
            throw new Error(`Service '${name}' not found.`);
        return this._getLogPathsForName(name);
    }
}
exports.serviceManager = new ServiceManager();
