"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var PollerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PollerService = void 0;
const common_1 = require("@nestjs/common");
let PollerService = PollerService_1 = class PollerService {
    constructor() {
        this.logger = new common_1.Logger(PollerService_1.name);
        this.pollers = new Map();
    }
    start(queueName, processNext, options = {}) {
        if (this.pollers.has(queueName)) {
            this.logger.warn(`Poller "${queueName}" already started; ignoring.`);
            return;
        }
        const opts = {
            baseDelayMs: options.baseDelayMs ?? 1000,
            maxDelayMs: options.maxDelayMs ?? 30_000,
            timeoutPerIterationMs: options.timeoutPerIterationMs ?? 5 * 60_000,
            jitter: options.jitter ?? true,
        };
        const state = {
            queueName,
            processNext,
            opts,
            inFlight: false,
            stopped: false,
            backoff: opts.baseDelayMs,
            nextTimer: undefined,
        };
        this.pollers.set(queueName, state);
        setImmediate(() => this.poll(state).catch(() => { }));
        this.logger.log(`Started poller "${queueName}"`);
    }
    stop(queueName) {
        const state = this.pollers.get(queueName);
        if (!state)
            return;
        state.stopped = true;
        if (state.nextTimer) {
            clearTimeout(state.nextTimer);
            state.nextTimer = undefined;
        }
        this.pollers.delete(queueName);
        this.logger.log(`Stopped poller "${queueName}"`);
    }
    stopAll() {
        for (const name of Array.from(this.pollers.keys())) {
            this.stop(name);
        }
    }
    async onModuleDestroy() {
        this.stopAll();
    }
    async beforeApplicationShutdown() {
        this.stopAll();
    }
    async poll(state) {
        if (state.stopped || state.inFlight)
            return;
        state.inFlight = true;
        try {
            await this.withTimeout(state.processNext(state.queueName), state.opts.timeoutPerIterationMs);
            state.backoff = state.opts.baseDelayMs;
            this.schedule(state, state.opts.baseDelayMs);
        }
        catch (err) {
            const msg = this.errorToString(err);
            this.logger.error(`[${state.queueName}] iteration failed: ${msg}`);
            const wait = this.computeWait(state.backoff, state.opts);
            state.backoff = Math.min(state.backoff * 2, state.opts.maxDelayMs);
            this.schedule(state, wait);
        }
        finally {
            state.inFlight = false;
        }
    }
    schedule(state, delayMs) {
        if (state.stopped)
            return;
        if (state.nextTimer)
            clearTimeout(state.nextTimer);
        state.nextTimer = setTimeout(() => {
            state.nextTimer = undefined;
            this.poll(state).catch(() => { });
        }, delayMs);
    }
    computeWait(currentBackoff, opts) {
        if (!opts.jitter)
            return currentBackoff;
        const doubled = Math.min(currentBackoff * 2, opts.maxDelayMs);
        const jittered = Math.floor(Math.random() * doubled);
        return Math.max(250, jittered);
    }
    async withTimeout(p, ms) {
        let timer;
        try {
            return await Promise.race([
                p,
                new Promise((_, rej) => {
                    timer = setTimeout(() => rej(new Error(`Iteration timed out after ${ms} ms`)), ms);
                }),
            ]);
        }
        finally {
            if (timer)
                clearTimeout(timer);
        }
    }
    errorToString(err) {
        if (err instanceof Error)
            return err.stack ?? err.message;
        try {
            return JSON.stringify(err);
        }
        catch {
            return String(err);
        }
    }
};
exports.PollerService = PollerService;
exports.PollerService = PollerService = PollerService_1 = __decorate([
    (0, common_1.Injectable)()
], PollerService);
//# sourceMappingURL=poller.service.js.map