import { Logger, NodeSingletons } from '../../../src/index.js';
// Internal state
const emitter = new NodeSingletons.EventEmitter();
emitter.setMaxListeners(Infinity);
let interval = null;
let subscribers = 0;
let currentConfig = null;
const broadcastSnapshot = async () => {
    try {
        if (subscribers <= 0 || !currentConfig)
            return;
        const { getSnapshot, getLocks, metrics, driver, queue: initialQueue, pattern } = currentConfig;
        const snapshot = await getSnapshot();
        let queue = initialQueue;
        if (!queue && snapshot.queues.length > 0) {
            queue = snapshot.queues[0].name;
        }
        const jobs = queue ? await getRecentJobsForQueue(queue, metrics, driver) : [];
        const locks = await getLocks(pattern);
        const payload = {
            type: 'snapshot',
            ts: new Date().toISOString(),
            queue: queue || null,
            snapshot,
            jobs,
            locks,
        };
        emitter.emit('snapshot', payload);
    }
    catch (err) {
        Logger.error('QueueMonitoringService.broadcastSnapshot failed', err);
        if (emitter.listenerCount('error') > 0) {
            emitter.emit('error', err);
        }
    }
};
const startPolling = () => {
    if (interval || !currentConfig)
        return;
    Logger.debug('Starting QueueMonitoringService polling');
    // Initial fetch
    void broadcastSnapshot();
    interval = setInterval(() => {
        void broadcastSnapshot();
    }, currentConfig.intervalMs);
};
const stopPolling = () => {
    if (interval) {
        Logger.debug('Stopping QueueMonitoringService polling');
        clearInterval(interval);
        interval = null;
    }
};
export const QueueMonitoringService = Object.freeze({
    subscribe(callback) {
        emitter.on('snapshot', callback);
        subscribers++;
    },
    unsubscribe(callback) {
        emitter.off('snapshot', callback);
        subscribers--;
        if (subscribers <= 0) {
            stopPolling();
            currentConfig = null;
        }
    },
    startPollingForClient(config) {
        if (subscribers === 1) {
            currentConfig = config;
            startPolling();
        }
    },
    stopPollingForClient() {
        if (subscribers <= 0) {
            stopPolling();
            currentConfig = null;
        }
    },
});
//  settings: {
//     basePath: string;
//     refreshIntervalMs: number;
//   },
//   routeOptions: unknown,
//   getSnapshot: () => Promise<QueueMonitorSnapshot>,
export const QueueMonitoringStream = (res, req, getSnapshot, getLocks, metrics, driver, settings) => {
    const raw = res.getRaw();
    raw.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache, no-transform',
        Connection: 'keep-alive',
        'X-Accel-Buffering': 'no',
    });
    let closed = false;
    const send = (payload) => {
        if (closed)
            return;
        try {
            const data = JSON.stringify(payload);
            raw.write(`data: ${data}\n\n`);
        }
        catch (err) {
            Logger.error('QueueMonitor SSE send failed', err);
        }
    };
    // Send hello immediately
    send({ type: 'hello', ts: new Date().toISOString() });
    // Get query parameters
    const getQuery = () => typeof req.getQuery === 'function'
        ? req.getQuery()
        : {};
    const query = getQuery();
    const queue = query['queue'] ?? '';
    const pattern = query['pattern'] ?? '*';
    // Define subscription callback
    const onSnapshot = (data) => {
        send(data);
    };
    // Subscribe to centralized service
    QueueMonitoringService.subscribe(onSnapshot);
    // Start polling for this client
    QueueMonitoringService.startPollingForClient({
        getSnapshot,
        getLocks,
        getRecentJobsForQueue,
        metrics,
        driver,
        queue,
        pattern,
        intervalMs: settings.refreshIntervalMs,
    });
    // Heartbeat to keep connection alive
    const hb = setInterval(() => {
        if (!closed)
            raw.write(': ping\n\n');
    }, 15000);
    raw.on('close', () => {
        closed = true;
        clearInterval(hb);
        QueueMonitoringService.unsubscribe(onSnapshot);
        QueueMonitoringService.stopPollingForClient();
    });
};
export async function getRecentJobsForQueue(queueName, metrics, driver) {
    const recent = await metrics.getRecentJobs(queueName);
    const failed = await metrics.getFailedJobs(queueName);
    const all = [...recent, ...failed].sort((a, b) => b.timestamp - a.timestamp).slice(0, 100);
    if (all.length > 0) {
        return all;
    }
    const jobs = await driver.getRecentJobs(queueName, 100);
    const now = Date.now();
    return jobs.map((job) => {
        // Use the actual state from BullMQ if available
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const jobState = job._state;
        // Fallback detection if state is not available
        const isFailed = Boolean(job.failedReason) || jobState === 'failed';
        const isCompleted = Boolean(job.finishedOn) || jobState === 'completed';
        const isActive = Boolean(job.processedOn && !job.finishedOn && !job.failedReason) || jobState === 'active';
        const isDelayed = jobState === 'delayed';
        const isPaused = jobState === 'paused';
        let status;
        if (isFailed) {
            status = 'failed';
        }
        else if (isCompleted) {
            status = 'completed';
        }
        else if (isActive) {
            status = 'active';
        }
        else if (isDelayed) {
            status = 'delayed';
        }
        else if (isPaused) {
            status = 'paused';
        }
        else {
            status = 'waiting';
        }
        return {
            id: job.id,
            name: job.name,
            data: job.data,
            attempts: job.attemptsMade,
            status,
            failedReason: job.failedReason || undefined,
            timestamp: job.timestamp ?? now,
            processedOn: job.processedOn ?? undefined,
            finishedOn: job.finishedOn ?? undefined,
        };
    });
}
