import { ErrorFactory, getBullMQSafeQueueName } from '../../../src/index.js';
import { Queue } from 'bullmq';
import { createRedisConnection } from './connection.js';
async function enrichJobsWithState(jobs) {
    await Promise.all(jobs.map(async (job) => {
        try {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            job._state = await job.getState();
        }
        catch {
            // Ignore errors fetching state
        }
    }));
}
async function discoverQueuesFromRedis(redis, inMemoryQueues) {
    const found = new Set(Array.from(inMemoryQueues.keys()));
    try {
        let cursor = '0';
        let shouldContinue = true;
        const prefix = getBullMQSafeQueueName();
        const scanAsync = (cur) => redis.scan(cur, 'MATCH', prefix + ':*', 'COUNT', '100');
        while (shouldContinue) {
            // Redis scan must be sequential as it depends on the cursor from previous result
            // eslint-disable-next-line no-await-in-loop
            const result = await scanAsync(cursor);
            cursor = result[0];
            const keys = result[1] ?? [];
            keys.forEach((k) => {
                const parts = k.split(':');
                if (parts.length >= 2 && k.startsWith(prefix + ':')) {
                    const name = parts[1];
                    if (name)
                        found.add(name);
                }
            });
            shouldContinue = cursor !== '0';
        }
    }
    catch {
        // ignore discovery errors
    }
    return Array.from(found.values());
}
export const createBullMQDriver = (config) => {
    const queues = new Map();
    const redis = createRedisConnection(config);
    const getQueue = (name) => {
        if (!queues.has(name)) {
            const prefix = getBullMQSafeQueueName();
            const connection = createRedisConnection(config);
            const queue = new Queue(name, { prefix, connection: connection });
            queues.set(name, queue);
        }
        const queue = queues.get(name);
        if (!queue) {
            throw ErrorFactory.createTryCatchError(`Queue initialization failed for ${name}`);
        }
        return queue;
    };
    const enqueue = async (name, payload, options) => {
        const queue = getQueue(name);
        const job = await queue.add('default', payload, {
            removeOnComplete: true,
            removeOnFail: false,
            ...options,
        });
        if (job.id === undefined || job.id === null) {
            throw ErrorFactory.createTryCatchError(`Queue job id missing for ${name}`);
        }
        return String(job.id);
    };
    const getJob = async (queueName, jobId) => {
        const queue = getQueue(queueName);
        return (await queue.getJob(jobId)) || undefined;
    };
    const getJobCounts = async (queueName) => {
        const queue = getQueue(queueName);
        return queue.getJobCounts();
    };
    const getRecentJobs = async (queueName, limit = 100) => {
        const queue = getQueue(queueName);
        const jobs = await queue.getJobs(['completed', 'failed', 'active', 'waiting', 'delayed', 'paused'], 0, Math.max(0, limit - 1), true);
        // Fetch state for each job to ensure accurate status detection
        await enrichJobsWithState(jobs);
        return jobs;
    };
    const retryJob = async (queueName, jobId) => {
        const job = await getJob(queueName, jobId);
        if (!job)
            return false;
        try {
            await job.retry();
            return true;
        }
        catch {
            return false;
        }
    };
    const getQueues = async () => {
        return discoverQueuesFromRedis(redis, queues);
    };
    const close = async () => {
        const closes = Array.from(queues.values()).map((q) => q.close());
        await Promise.all(closes);
    };
    return Object.freeze({
        enqueue,
        getJob,
        getJobCounts,
        getRecentJobs,
        retryJob,
        getQueues,
        close,
    });
};
