import { Env } from '../../config/env.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
const sleep = async (ms) => {
    if (ms <= 0)
        return;
    await new Promise((resolve) => {
        const timeoutRef = globalThis.setTimeout(() => {
            clearTimeout(timeoutRef);
            resolve();
        }, ms);
    });
};
const createTimeoutError = (operationName, timeoutMs) => ErrorFactory.createTryCatchError(`Operation timed out: ${operationName}`, {
    code: 'QUEUE_TIMEOUT',
    operationName,
    timeoutMs,
});
const isTimeoutError = (error) => {
    if (error === null || error === undefined || typeof error !== 'object')
        return false;
    const code = error.code;
    if (code === 'QUEUE_TIMEOUT')
        return true;
    const details = error.details;
    if (details !== null && details !== undefined && typeof details === 'object') {
        return details.code === 'QUEUE_TIMEOUT';
    }
    return false;
};
export const TimeoutManager = Object.freeze({
    getQueueJobTimeoutMs() {
        const timeoutSeconds = Env.getInt('QUEUE_JOB_TIMEOUT', 60);
        return Math.max(1000, timeoutSeconds * 1000);
    },
    async withTimeout(operation, timeoutMs, operationName, timeoutHandler) {
        let timeoutId;
        const timeoutPromise = new Promise((_, reject) => {
            timeoutId = globalThis.setTimeout(() => {
                reject(createTimeoutError(operationName, timeoutMs));
            }, Math.max(1, timeoutMs));
        });
        try {
            return await Promise.race([operation(), timeoutPromise]);
        }
        catch (error) {
            if (timeoutHandler !== undefined && isTimeoutError(error)) {
                return await timeoutHandler();
            }
            throw error;
        }
        finally {
            if (timeoutId !== undefined) {
                clearTimeout(timeoutId);
            }
        }
    },
    async withTimeoutRetry(operation, config) {
        const retries = Math.max(0, Math.floor(config.maxRetries));
        const attemptRun = async (attempt) => {
            try {
                return await this.withTimeout(operation, config.timeoutMs, config.operationName ?? 'queue-operation');
            }
            catch (error) {
                const shouldRetry = isTimeoutError(error) && attempt < retries;
                if (!shouldRetry) {
                    throw error;
                }
                const delay = Math.max(0, Math.floor(config.retryDelayMs * 2 ** attempt));
                await sleep(delay);
                return attemptRun(attempt + 1);
            }
        };
        return attemptRun(0);
    },
    isTimeoutError,
});
export default TimeoutManager;
