"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SERVICE_PROCESS_CAPABILITIES = void 0;
exports.registerServiceProcess = registerServiceProcess;
exports.writeServicePidFile = writeServicePidFile;
exports.readServicePidFile = readServicePidFile;
exports.getLiveServiceState = getLiveServiceState;
exports.removeServicePidFile = removeServicePidFile;
exports.isProcessAlive = isProcessAlive;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const zod_1 = require("zod");
const catalog_1 = require("../i18n/catalog");
const file_readers_1 = require("./file-readers");
const fs_errors_1 = require("./fs-errors");
const paths_1 = require("./paths");
const servicePidSchema = zod_1.z.object({
    pid: zod_1.z.number().int().positive(),
    startedAt: zod_1.z.string().min(1),
    capabilities: zod_1.z.array(zod_1.z.string().min(1)).optional(),
    packageName: zod_1.z.string().min(1).optional(),
    packageVersion: zod_1.z.string().min(1).optional(),
    entrypointPath: zod_1.z.string().min(1).optional(),
});
exports.SERVICE_PROCESS_CAPABILITIES = {
    serviceDrain: "service-drain",
};
async function registerServiceProcess(options = {}) {
    const pidPath = (0, paths_1.getServicePidPath)(options.homeDirectory);
    const pid = options.pid ?? process.pid;
    const startedAt = options.startedAt ?? new Date().toISOString();
    const locale = options.locale ?? catalog_1.DEFAULT_APP_LOCALE;
    await (0, promises_1.mkdir)(node_path_1.default.dirname(pidPath), { recursive: true });
    for (let attempt = 0; attempt < 2; attempt += 1) {
        const state = createServicePidContentsFromOptions({
            pid,
            startedAt,
            options,
        });
        try {
            await createServicePidFile(pidPath, state);
            return createServiceHandle(options.homeDirectory, state);
        }
        catch (error) {
            if (!(0, fs_errors_1.isAlreadyExistsError)(error)) {
                throw error;
            }
            const existing = await readServicePidFile(options.homeDirectory);
            if (existing) {
                if (existing.pid === pid) {
                    const state = createServicePidContentsFromOptions({
                        pid,
                        startedAt,
                        options,
                    });
                    await writeServicePidFile(state, options.homeDirectory);
                    return createServiceHandle(options.homeDirectory, state);
                }
                if (isProcessAlive(existing.pid)) {
                    throw new Error((0, catalog_1.translate)(locale, "serviceState.alreadyRunning", {
                        pid: existing.pid,
                    }));
                }
                await removeServicePidFile(options.homeDirectory, existing.pid);
                continue;
            }
            throw new Error((0, catalog_1.translate)(locale, "serviceState.pidFileExists", { pidPath }));
        }
    }
    throw new Error((0, catalog_1.translate)(locale, "serviceState.failedRegister"));
}
function createServicePidContentsFromOptions(input) {
    return createServicePidContents({
        pid: input.pid,
        startedAt: input.startedAt,
        ...(input.options.capabilities
            ? { capabilities: input.options.capabilities }
            : {}),
        ...(input.options.packageMetadata
            ? { packageMetadata: input.options.packageMetadata }
            : {}),
    });
}
function createServicePidContents(input) {
    return {
        pid: input.pid,
        startedAt: input.startedAt,
        ...(input.capabilities && input.capabilities.length > 0
            ? { capabilities: [...input.capabilities] }
            : {}),
        ...(input.packageMetadata
            ? {
                packageName: input.packageMetadata.packageName,
                packageVersion: input.packageMetadata.packageVersion,
                entrypointPath: input.packageMetadata.entrypointPath,
            }
            : {}),
    };
}
async function writeServicePidFile(state, homeDirectory) {
    const pidPath = (0, paths_1.getServicePidPath)(homeDirectory);
    await replaceServicePidFile(pidPath, state);
    return pidPath;
}
async function createServicePidFile(pidPath, state) {
    const tempPath = await writeServicePidTempFile(pidPath, state);
    try {
        await (0, promises_1.link)(tempPath, pidPath);
    }
    finally {
        await (0, promises_1.rm)(tempPath, { force: true });
    }
}
async function replaceServicePidFile(pidPath, state) {
    const tempPath = await writeServicePidTempFile(pidPath, state);
    try {
        await (0, promises_1.rename)(tempPath, pidPath);
    }
    catch (error) {
        await (0, promises_1.rm)(tempPath, { force: true });
        throw error;
    }
}
async function writeServicePidTempFile(pidPath, state) {
    const contents = `${JSON.stringify(servicePidSchema.parse(state), null, 2)}\n`;
    const directory = node_path_1.default.dirname(pidPath);
    await (0, promises_1.mkdir)(directory, { recursive: true });
    const tempPath = node_path_1.default.join(directory, `.${node_path_1.default.basename(pidPath)}.${process.pid}.${Date.now()}.${Math.random()
        .toString(16)
        .slice(2)}.tmp`);
    const file = await (0, promises_1.open)(tempPath, "wx");
    try {
        await file.writeFile(contents, "utf8");
    }
    finally {
        await file.close();
    }
    return tempPath;
}
async function readServicePidFile(homeDirectory) {
    const pidPath = (0, paths_1.getServicePidPath)(homeDirectory);
    return (0, file_readers_1.readJsonFileOrNull)(pidPath, servicePidSchema);
}
async function getLiveServiceState(homeDirectory) {
    const state = await readServicePidFile(homeDirectory);
    if (!state) {
        return null;
    }
    if (isProcessAlive(state.pid)) {
        return state;
    }
    await removeServicePidFile(homeDirectory, state.pid);
    return null;
}
async function removeServicePidFile(homeDirectory, expectedPid) {
    const pidPath = (0, paths_1.getServicePidPath)(homeDirectory);
    if (expectedPid !== undefined) {
        const current = await readServicePidFile(homeDirectory);
        if (current && current.pid !== expectedPid) {
            return;
        }
    }
    await (0, promises_1.rm)(pidPath, { force: true });
}
function isProcessAlive(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
function createServiceHandle(homeDirectory, state) {
    let released = false;
    return {
        pidPath: (0, paths_1.getServicePidPath)(homeDirectory),
        state,
        async release() {
            if (released) {
                return;
            }
            released = true;
            await removeServicePidFile(homeDirectory, state.pid);
        },
    };
}
