"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.exitCode = exports.quitting = void 0;
exports.onProcessExit = onProcessExit;
exports.quit = quit;
exports.onFirstEvent = onFirstEvent;
const lodash_1 = __importDefault(require("lodash"));
const assert_1 = __importDefault(require("assert"));
const cbsOnExit = new Set();
function onProcessExit(cb, order = 10) {
    (0, assert_1.default)(Number.isInteger(order) && order >= 0, 'order must be an integer >= 0');
    const rec = { cb, order };
    cbsOnExit.add(rec);
    return () => cbsOnExit.delete(rec);
}
exports.quitting = false;
exports.exitCode = 0;
// 'exit' event is handled as the last resort, but it's not compatible with async callbacks
onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP', 'beforeExit'], async (signal) => {
    console.log('Quitting with signal:', signal || 'unknown');
    exports.quitting = true;
    const byOrder = lodash_1.default.groupBy(Array.from(cbsOnExit), 'order'); // this will be inherently ordered because keys are positive integers
    for (const recs of Object.values(byOrder)) {
        const ret = Promise.allSettled(recs.map(({ cb }) => {
            try {
                return cb(signal);
            }
            // keep exit moving even when a synchronous cleanup fails after partially shutting down
            catch (e) {
                console.error("Error while quitting", e);
                return Promise.reject(e);
            }
        }));
        if (signal !== 'exit') // exit is sync
            await ret;
    }
    cbsOnExit.clear();
    console.debug('Process exit');
    process.exit(exports.exitCode);
});
function quit(code = 0) {
    exports.exitCode = code;
    process.emit('SIGINT');
}
// keep calling cb in a sync fashion – returning a promise instead would break the code for argv.updating (update.ts)
function onFirstEvent(emitter, events, cb) {
    let already = false;
    const cleanup = () => {
        events.forEach((e, i) => emitter.off(e, handlers[i]));
    };
    const handlers = events.map(e => {
        const handler = (...args) => {
            if (already)
                return;
            already = true;
            cleanup();
            cb(e, ...args);
        };
        emitter.on(e, handler);
        return handler;
    });
}
