import { Env } from '../config/env.js';
import { Logger } from '../config/logger.js';
import { ZintrustLang } from '../lang/lang.js';
import { createAdvancedQueue } from '../tools/queue/AdvancedQueue.js';
import { getLockProvider } from '../tools/queue/LockProvider.js';
const toIntervalMs = (ms) => {
    if (!Number.isFinite(ms) || ms <= 0)
        return 0;
    return Math.floor(ms);
};
const normalizeOptionalString = (value) => {
    const s = typeof value === 'string' ? value.trim() : '';
    return s.length > 0 ? s : undefined;
};
const toPositiveInt = (value) => {
    const n = typeof value === 'number' ? value : Number(value);
    if (!Number.isFinite(n) || n <= 0)
        return undefined;
    return Math.floor(n);
};
const resolveLockProvider = (providerName) => {
    const name = providerName.trim().toLowerCase();
    if (name.length === 0)
        return undefined;
    // Ensure provider is registered. createAdvancedQueue triggers lock-provider registration.
    createAdvancedQueue({
        name: ZintrustLang.CLI_LOCKS,
        connection: undefined,
        defaultDedupTtl: Env.getInt('SCHEDULE_OVERLAP_LOCK_TTL_MS', 300000),
        lockProvider: name,
    });
    return getLockProvider(name);
};
const normalizeBackoffFactor = (factor) => {
    if (factor === undefined)
        return undefined;
    return Number.isFinite(factor) ? factor : undefined;
};
const wrapWithoutOverlapping = (scheduleName, handler, options) => {
    const providerName = (options.provider ?? 'memory').trim();
    const lockKey = (options.key ?? `schedule:${scheduleName}`).trim();
    const ttlMs = Env.getInt('SCHEDULE_OVERLAP_LOCK_TTL_MS', options.ttlMs ?? 300000);
    const acquireTimeoutMs = Env.getInt('SCHEDULE_OVERLAP_LOCK_ACQUIRE_TIMEOUT_MS', 2000);
    const withTimeout = async (promise, timeoutMs) => {
        if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
            return promise;
        let timeoutId;
        try {
            return await Promise.race([
                promise,
                new Promise((_, reject) => {
                    timeoutId = globalThis.setTimeout(() => {
                        // eslint-disable-next-line no-restricted-syntax
                        reject(new Error('Lock acquire timed out'));
                    }, timeoutMs);
                }),
            ]);
        }
        finally {
            if (timeoutId !== undefined)
                globalThis.clearTimeout(timeoutId);
        }
    };
    return async (kernel) => {
        const provider = resolveLockProvider(providerName);
        if (!provider) {
            Logger.warn('Schedule withoutOverlapping requested but lock provider not available; running anyway', {
                schedule: scheduleName,
                provider: providerName,
            });
            await handler(kernel);
            return;
        }
        let lock;
        try {
            lock = await withTimeout(provider.acquire(lockKey, { ttl: ttlMs }), acquireTimeoutMs);
        }
        catch (error) {
            Logger.warn('Schedule lock acquire failed; running anyway', {
                schedule: scheduleName,
                provider: providerName,
                timeoutMs: acquireTimeoutMs,
                message: error instanceof Error ? error.message : String(error),
            });
            await handler(kernel);
            return;
        }
        if (!lock.acquired) {
            Logger.info(`Skipping overlapping run for schedule: ${scheduleName}`);
            return;
        }
        try {
            await handler(kernel);
        }
        finally {
            try {
                await provider.release(lock);
            }
            catch {
                // best-effort
            }
        }
    };
};
export const Schedule = Object.freeze({
    define(name, handler) {
        return ScheduleBuilder.create({ name, handler });
    },
});
const createScheduleApi = (state) => {
    const api = Object.freeze({
        everyMinute: () => api.everyMinutes(1),
        everyMinutes: (minutes) => {
            const resolved = Math.max(1, Math.floor(minutes));
            state.intervalMs = resolved * 60_000;
            return api;
        },
        everyHour: () => api.everyHours(1),
        everyHours: (hours) => {
            const resolved = Math.max(1, Math.floor(hours));
            state.intervalMs = resolved * 3_600_000;
            return api;
        },
        intervalMs: (ms) => {
            state.intervalMs = toIntervalMs(ms);
            return api;
        },
        cron: (expr, options) => {
            state.cron = normalizeOptionalString(expr);
            if (options?.timezone !== undefined) {
                state.timezone = normalizeOptionalString(options.timezone);
            }
            return api;
        },
        timezone: (tz) => {
            state.timezone = normalizeOptionalString(tz);
            return api;
        },
        jitterMs: (ms) => {
            state.jitterMs = toPositiveInt(ms);
            return api;
        },
        backoff: (policy) => {
            state.backoff = {
                initialMs: toPositiveInt(policy.initialMs) ?? 0,
                maxMs: toPositiveInt(policy.maxMs) ?? 0,
                factor: normalizeBackoffFactor(policy.factor),
            };
            return api;
        },
        leaderOnly: () => {
            state.leaderOnly = true;
            return api;
        },
        runOnStart: () => {
            state.runOnStart = true;
            return api;
        },
        enabledWhen: (value) => {
            state.enabled = value;
            return api;
        },
        withoutOverlapping: (options) => {
            state.overlap = options ?? {};
            return api;
        },
        build: () => {
            const handler = state.overlap === undefined
                ? state.handler
                : wrapWithoutOverlapping(state.name, state.handler, state.overlap);
            const schedule = {
                name: state.name,
                intervalMs: state.intervalMs,
                cron: state.cron,
                timezone: state.timezone,
                jitterMs: state.jitterMs,
                backoff: state.backoff,
                leaderOnly: state.leaderOnly,
                handler,
                enabled: state.enabled,
                runOnStart: state.runOnStart,
            };
            return schedule;
        },
    });
    return api;
};
export const ScheduleBuilder = Object.freeze({
    create(input) {
        const state = {
            name: input.name,
            handler: input.handler,
        };
        return createScheduleApi(state);
    },
});
export default Schedule;
