"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LeaderService = exports.__ΩLeaderServiceOptions = void 0;
const __ΩReturnType = ['T', 'args', '', 'ReturnType', 'l>e"!R"RPde#!P"@2"h"!/#qk#\'QRb!Pde"!p)w$y'];
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const type_1 = require("@deepkit/type");
const lodash_1 = require("lodash");
const resolver_1 = require("../app/resolver");
const package_1 = require("../helpers/io/package");
const redis_1 = require("../helpers/redis/redis");
const logger_1 = require("./logger");
const ACQUIRE_SCRIPT = `
if redis.call("exists", KEYS[1]) == 1 then
    return 0
end
redis.call("set", KEYS[1], ARGV[1], "px", ARGV[2])
return 1
`;
const RENEW_SCRIPT = `
if redis.call("get", KEYS[1]) ~= ARGV[1] then
    return 0
end
redis.call("pexpire", KEYS[1], ARGV[2])
return 1
`;
const RELEASE_SCRIPT = `
if redis.call("get", KEYS[1]) ~= ARGV[1] then
    return 0
end
redis.call("del", KEYS[1])
return 1
`;
const __ΩLeaderRedisClient = [() => __ΩReturnType, () => redis_1.createRedis, "client", 'key', 'value', 'ttl', '', 'ACQUIRE', 'RENEW', 'RELEASE', 'LeaderRedisClient', 'Pi"o!".#fPP&2$&2%\'2&\'`/\'4(P&2$&2%\'2&\'`/\'4)P&2$&2%\'`/\'4*MKw+y'];
const getRedisClient = (0, lodash_1.memoize)(__assignType(() => {
    const { client } = (0, redis_1.createRedis)('MUTEX');
    client.defineCommand('ACQUIRE', { lua: ACQUIRE_SCRIPT, numberOfKeys: 1 });
    client.defineCommand('RENEW', { lua: RENEW_SCRIPT, numberOfKeys: 1 });
    client.defineCommand('RELEASE', { lua: RELEASE_SCRIPT, numberOfKeys: 1 });
    return client;
}, [() => __ΩLeaderRedisClient, '', 'Pn!/"']));
const __ΩLeaderCallback = ['', 'LeaderCallback', 'PP$$`J/!w"y'];
const __ΩLeaderServiceOptions = ['ttlMs', 'renewalIntervalMs', 'retryDelayMs', 'LeaderServiceOptions', 'P\'4!8\'4"8\'4#8Mw$y'];
exports.__ΩLeaderServiceOptions = __ΩLeaderServiceOptions;
class LeaderService {
    constructor(key, options) {
        this.lockId = (0, type_1.uuid)();
        this._isLeader = false;
        this.running = false;
        this.renewTimer = null;
        this.retryTimer = null;
        this.becameLeaderCallback = null;
        this.lostLeaderCallback = null;
        this.logger = (0, logger_1.createLogger)(this);
        const config = (0, resolver_1.getAppConfig)();
        const prefix = config.MUTEX_REDIS_PREFIX ?? config.REDIS_PREFIX ?? (0, package_1.getPackageName)() ?? 'app';
        this.key = `${prefix}:leader:${key}`;
        this.ttlMs = options?.ttlMs ?? 30000;
        this.renewalIntervalMs = options?.renewalIntervalMs ?? 10000;
        this.retryDelayMs = options?.retryDelayMs ?? 5000;
    }
    get isLeader() {
        return this._isLeader;
    }
    setBecameLeaderCallback(callback) {
        this.becameLeaderCallback = callback;
    }
    setLostLeaderCallback(callback) {
        this.lostLeaderCallback = callback;
    }
    start() {
        if (this.running) {
            throw new Error('LeaderService is already running');
        }
        this.running = true;
        this.logger.info('starting leader election', { key: this.key });
        this.tryAcquire();
    }
    async stop() {
        this.running = false;
        if (this.renewTimer) {
            clearInterval(this.renewTimer);
            this.renewTimer = null;
        }
        if (this.retryTimer) {
            clearTimeout(this.retryTimer);
            this.retryTimer = null;
        }
        if (this._isLeader) {
            try {
                await getRedisClient().RELEASE(this.key, this.lockId);
            }
            catch (err) {
                this.logger.warn('failed to release leader lock during stop', { err });
            }
            this._isLeader = false;
        }
    }
    async tryAcquire() {
        if (!this.running)
            return;
        try {
            const result = await getRedisClient().ACQUIRE(this.key, this.lockId, this.ttlMs);
            if (result === 1) {
                // If stop() was called while ACQUIRE was in-flight, release immediately
                if (!this.running) {
                    try {
                        await getRedisClient().RELEASE(this.key, this.lockId);
                    }
                    catch {
                        // ignore — lock will expire via TTL
                    }
                    return;
                }
                this._isLeader = true;
                this.logger.info('became leader', { key: this.key });
                this.startRenewal();
                try {
                    await this.becameLeaderCallback?.();
                }
                catch (err) {
                    this.logger.warn('becameLeader callback error', { err });
                }
                return;
            }
        }
        catch (err) {
            this.logger.warn('error during leader acquisition', { err });
        }
        if (this.running) {
            this.retryTimer = setTimeout(() => this.tryAcquire(), this.retryDelayMs);
        }
    }
    startRenewal() {
        this.renewTimer = setInterval(async () => {
            if (!this.running)
                return;
            try {
                const result = await getRedisClient().RENEW(this.key, this.lockId, this.ttlMs);
                if (result === 0) {
                    this.handleLostLeadership();
                }
            }
            catch (err) {
                this.logger.warn('error during leader renewal', { err });
                this.handleLostLeadership();
            }
        }, this.renewalIntervalMs);
    }
    handleLostLeadership() {
        if (!this._isLeader)
            return;
        this._isLeader = false;
        this.logger.info('lost leadership', { key: this.key });
        if (this.renewTimer) {
            clearInterval(this.renewTimer);
            this.renewTimer = null;
        }
        Promise.resolve()
            .then(() => this.lostLeaderCallback?.())
            .catch(__assignType(err => {
            this.logger.warn('lostLeader callback error', { err });
        }, ['err', '', 'P"2!"/"']));
        this.lockId = (0, type_1.uuid)();
        if (this.running) {
            this.retryTimer = setTimeout(() => this.tryAcquire(), this.retryDelayMs);
        }
    }
}
exports.LeaderService = LeaderService;
LeaderService.__type = ['lockId', function () { return (0, type_1.uuid)(); }, 'key', '_isLeader', function () { return false; }, 'running', function () { return false; }, () => __ΩReturnType, () => setInterval, 'renewTimer', function () { return null; }, () => __ΩReturnType, () => setTimeout, 'retryTimer', function () { return null; }, () => __ΩLeaderCallback, 'becameLeaderCallback', function () { return null; }, () => __ΩLeaderCallback, 'lostLeaderCallback', function () { return null; }, 'ttlMs', 'renewalIntervalMs', 'retryDelayMs', 'logger', function () { return (0, logger_1.createLogger)(this); }, () => __ΩLeaderServiceOptions, 'options', 'constructor', () => __ΩLeaderCallback, 'callback', 'setBecameLeaderCallback', () => __ΩLeaderCallback, 'setLostLeaderCallback', 'start', 'stop', 'tryAcquire', 'startRenewal', 'handleLostLeadership', 'LeaderService', '!3!;>"&3#;)3$;>%)3&;>\'Pi)o(",J3*;>+Pi-o,",J3.;>/Pn0,J31;>2Pn3,J34;>5\'36;\'37;\'38;!39;>:P&2#n;2<8"0=!Pn>2?$0@PnA2?$0BP$0CP$`0DP$`0E;P$0F;P$0G;5wH'];
//# sourceMappingURL=leader.js.map