/*eslint-env node */
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueueGetters = void 0;
const queue_base_1 = require("./queue-base");
const utils_1 = require("../utils");
/**
 * Provides different getters for different aspects of a queue.
 */
class QueueGetters extends queue_base_1.QueueBase {
    getJob(jobId) {
        return this.Job.fromId(this, jobId);
    }
    commandByType(types, count, callback) {
        return types.map((type) => {
            type = type === 'waiting' ? 'wait' : type; // alias
            const key = this.toKey(type);
            switch (type) {
                case 'completed':
                case 'failed':
                case 'delayed':
                case 'prioritized':
                case 'repeat':
                case 'waiting-children':
                    return callback(key, count ? 'zcard' : 'zrange');
                case 'active':
                case 'wait':
                case 'paused':
                    return callback(key, count ? 'llen' : 'lrange');
            }
        });
    }
    sanitizeJobTypes(types) {
        const currentTypes = typeof types === 'string' ? [types] : types;
        if (Array.isArray(currentTypes) && currentTypes.length > 0) {
            const sanitizedTypes = [...currentTypes];
            if (sanitizedTypes.indexOf('waiting') !== -1) {
                sanitizedTypes.push('paused');
            }
            return [...new Set(sanitizedTypes)];
        }
        return [
            'active',
            'completed',
            'delayed',
            'failed',
            'paused',
            'prioritized',
            'waiting',
            'waiting-children',
        ];
    }
    /**
      Returns the number of jobs waiting to be processed. This includes jobs that are
      "waiting" or "delayed" or "prioritized" or "waiting-children".
    */
    async count() {
        const count = await this.getJobCountByTypes('waiting', 'paused', 'delayed', 'prioritized', 'waiting-children');
        return count;
    }
    /**
     * Returns the time to live for a rate limited key in milliseconds.
     * @param maxJobs - max jobs to be considered in rate limit state. If not passed
     * it will return the remaining ttl without considering if max jobs is excedeed.
     * @returns -2 if the key does not exist.
     * -1 if the key exists but has no associated expire.
     * @see {@link https://redis.io/commands/pttl/}
     */
    async getRateLimitTtl(maxJobs) {
        return this.scripts.getRateLimitTtl(maxJobs);
    }
    /**
     * Get jobId that starts debounced state.
     * @deprecated use getDeduplicationJobId method
     *
     * @param id - debounce identifier
     */
    async getDebounceJobId(id) {
        const client = await this.client;
        return client.get(`${this.keys.de}:${id}`);
    }
    /**
     * Get jobId from deduplicated state.
     *
     * @param id - deduplication identifier
     */
    async getDeduplicationJobId(id) {
        const client = await this.client;
        return client.get(`${this.keys.de}:${id}`);
    }
    /**
     * Job counts by type
     *
     * Queue#getJobCountByTypes('completed') =\> completed count
     * Queue#getJobCountByTypes('completed,failed') =\> completed + failed count
     * Queue#getJobCountByTypes('completed', 'failed') =\> completed + failed count
     * Queue#getJobCountByTypes('completed', 'waiting', 'failed') =\> completed + waiting + failed count
     */
    async getJobCountByTypes(...types) {
        const result = await this.getJobCounts(...types);
        return Object.values(result).reduce((sum, count) => sum + count, 0);
    }
    /**
     * Returns the job counts for each type specified or every list/set in the queue by default.
     *
     * @returns An object, key (type) and value (count)
     */
    async getJobCounts(...types) {
        const currentTypes = this.sanitizeJobTypes(types);
        const responses = await this.scripts.getCounts(currentTypes);
        const counts = {};
        responses.forEach((res, index) => {
            counts[currentTypes[index]] = res || 0;
        });
        return counts;
    }
    /**
     * Get current job state.
     *
     * @param jobId - job identifier.
     * @returns Returns one of these values:
     * 'completed', 'failed', 'delayed', 'active', 'waiting', 'waiting-children', 'unknown'.
     */
    getJobState(jobId) {
        return this.scripts.getState(jobId);
    }
    /**
     * Returns the number of jobs in completed status.
     */
    getCompletedCount() {
        return this.getJobCountByTypes('completed');
    }
    /**
     * Returns the number of jobs in failed status.
     */
    getFailedCount() {
        return this.getJobCountByTypes('failed');
    }
    /**
     * Returns the number of jobs in delayed status.
     */
    getDelayedCount() {
        return this.getJobCountByTypes('delayed');
    }
    /**
     * Returns the number of jobs in active status.
     */
    getActiveCount() {
        return this.getJobCountByTypes('active');
    }
    /**
     * Returns the number of jobs in prioritized status.
     */
    getPrioritizedCount() {
        return this.getJobCountByTypes('prioritized');
    }
    /**
     * Returns the number of jobs per priority.
     */
    async getCountsPerPriority(priorities) {
        const uniquePriorities = [...new Set(priorities)];
        const responses = await this.scripts.getCountsPerPriority(uniquePriorities);
        const counts = {};
        responses.forEach((res, index) => {
            counts[`${uniquePriorities[index]}`] = res || 0;
        });
        return counts;
    }
    /**
     * Returns the number of jobs in waiting or paused statuses.
     */
    getWaitingCount() {
        return this.getJobCountByTypes('waiting');
    }
    /**
     * Returns the number of jobs in waiting-children status.
     */
    getWaitingChildrenCount() {
        return this.getJobCountByTypes('waiting-children');
    }
    /**
     * Returns the jobs that are in the "waiting" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getWaiting(start = 0, end = -1) {
        return this.getJobs(['waiting'], start, end, true);
    }
    /**
     * Returns the jobs that are in the "waiting-children" status.
     * I.E. parent jobs that have at least one child that has not completed yet.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getWaitingChildren(start = 0, end = -1) {
        return this.getJobs(['waiting-children'], start, end, true);
    }
    /**
     * Returns the jobs that are in the "active" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getActive(start = 0, end = -1) {
        return this.getJobs(['active'], start, end, true);
    }
    /**
     * Returns the jobs that are in the "delayed" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getDelayed(start = 0, end = -1) {
        return this.getJobs(['delayed'], start, end, true);
    }
    /**
     * Returns the jobs that are in the "prioritized" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getPrioritized(start = 0, end = -1) {
        return this.getJobs(['prioritized'], start, end, true);
    }
    /**
     * Returns the jobs that are in the "completed" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getCompleted(start = 0, end = -1) {
        return this.getJobs(['completed'], start, end, false);
    }
    /**
     * Returns the jobs that are in the "failed" status.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     */
    getFailed(start = 0, end = -1) {
        return this.getJobs(['failed'], start, end, false);
    }
    /**
     * Returns the qualified job ids and the raw job data (if available) of the
     * children jobs of the given parent job.
     * It is possible to get either the already processed children, in this case
     * an array of qualified job ids and their result values will be returned,
     * or the pending children, in this case an array of qualified job ids will
     * be returned.
     * A qualified job id is a string representing the job id in a given queue,
     * for example: "bull:myqueue:jobid".
     *
     * @param parentId - The id of the parent job
     * @param type - "processed" | "pending"
     * @param opts - Options for the query.
     *
     * @returns an object with the following shape:
     * `{ items: { id: string, v?: any, err?: string } [], jobs: JobJsonRaw[], total: number}`
     */
    async getDependencies(parentId, type, start, end) {
        const key = this.toKey(type == 'processed'
            ? `${parentId}:processed`
            : `${parentId}:dependencies`);
        const { items, total, jobs } = await this.scripts.paginate(key, {
            start,
            end,
            fetchJobs: true,
        });
        return {
            items,
            jobs,
            total,
        };
    }
    async getRanges(types, start = 0, end = 1, asc = false) {
        const multiCommands = [];
        this.commandByType(types, false, (key, command) => {
            switch (command) {
                case 'lrange':
                    multiCommands.push('lrange');
                    break;
                case 'zrange':
                    multiCommands.push('zrange');
                    break;
            }
        });
        const responses = await this.scripts.getRanges(types, start, end, asc);
        let results = [];
        responses.forEach((response, index) => {
            const result = response || [];
            if (asc && multiCommands[index] === 'lrange') {
                results = results.concat(result.reverse());
            }
            else {
                results = results.concat(result);
            }
        });
        return [...new Set(results)];
    }
    /**
     * Returns the jobs that are on the given statuses (note that JobType is synonym for job status)
     * @param types - the statuses of the jobs to return.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     * @param asc - if true, the jobs will be returned in ascending order.
     */
    async getJobs(types, start = 0, end = -1, asc = false) {
        const currentTypes = this.sanitizeJobTypes(types);
        const jobIds = await this.getRanges(currentTypes, start, end, asc);
        return Promise.all(jobIds.map(jobId => this.Job.fromId(this, jobId)));
    }
    /**
     * Returns the logs for a given Job.
     * @param jobId - the id of the job to get the logs for.
     * @param start - zero based index from where to start returning jobs.
     * @param end - zero based index where to stop returning jobs.
     * @param asc - if true, the jobs will be returned in ascending order.
     */
    async getJobLogs(jobId, start = 0, end = -1, asc = true) {
        const client = await this.client;
        const multi = client.multi();
        const logsKey = this.toKey(jobId + ':logs');
        if (asc) {
            multi.lrange(logsKey, start, end);
        }
        else {
            multi.lrange(logsKey, -(end + 1), -(start + 1));
        }
        multi.llen(logsKey);
        const result = (await multi.exec());
        if (!asc) {
            result[0][1].reverse();
        }
        return {
            logs: result[0][1],
            count: result[1][1],
        };
    }
    async baseGetClients(matcher) {
        const client = await this.client;
        try {
            const clients = (await client.client('LIST'));
            const list = this.parseClientList(clients, matcher);
            return list;
        }
        catch (err) {
            if (!utils_1.clientCommandMessageReg.test(err.message)) {
                throw err;
            }
            return [{ name: 'GCP does not support client list' }];
        }
    }
    /**
     * Get the worker list related to the queue. i.e. all the known
     * workers that are available to process jobs for this queue.
     * Note: GCP does not support SETNAME, so this call will not work
     *
     * @returns - Returns an array with workers info.
     */
    getWorkers() {
        const unnamedWorkerClientName = `${this.clientName()}`;
        const namedWorkerClientName = `${this.clientName()}:w:`;
        const matcher = (name) => name &&
            (name === unnamedWorkerClientName ||
                name.startsWith(namedWorkerClientName));
        return this.baseGetClients(matcher);
    }
    /**
     * Returns the current count of workers for the queue.
     *
     * getWorkersCount(): Promise<number>
     *
     */
    async getWorkersCount() {
        const workers = await this.getWorkers();
        return workers.length;
    }
    /**
     * Get queue events list related to the queue.
     * Note: GCP does not support SETNAME, so this call will not work
     *
     * @deprecated do not use this method, it will be removed in the future.
     *
     * @returns - Returns an array with queue events info.
     */
    async getQueueEvents() {
        const clientName = `${this.clientName()}${utils_1.QUEUE_EVENT_SUFFIX}`;
        return this.baseGetClients((name) => name === clientName);
    }
    /**
     * Get queue metrics related to the queue.
     *
     * This method returns the gathered metrics for the queue.
     * The metrics are represented as an array of job counts
     * per unit of time (1 minute).
     *
     * @param start - Start point of the metrics, where 0
     * is the newest point to be returned.
     * @param end - End point of the metrics, where -1 is the
     * oldest point to be returned.
     *
     * @returns - Returns an object with queue metrics.
     */
    async getMetrics(type, start = 0, end = -1) {
        const client = await this.client;
        const metricsKey = this.toKey(`metrics:${type}`);
        const dataKey = `${metricsKey}:data`;
        const multi = client.multi();
        multi.hmget(metricsKey, 'count', 'prevTS', 'prevCount');
        multi.lrange(dataKey, start, end);
        multi.llen(dataKey);
        const [hmget, range, len] = (await multi.exec());
        const [err, [count, prevTS, prevCount]] = hmget;
        const [err2, data] = range;
        const [err3, numPoints] = len;
        if (err || err2) {
            throw err || err2 || err3;
        }
        return {
            meta: {
                count: parseInt(count || '0', 10),
                prevTS: parseInt(prevTS || '0', 10),
                prevCount: parseInt(prevCount || '0', 10),
            },
            data,
            count: numPoints,
        };
    }
    parseClientList(list, matcher) {
        const lines = list.split(/\r?\n/);
        const clients = [];
        lines.forEach((line) => {
            const client = {};
            const keyValues = line.split(' ');
            keyValues.forEach(function (keyValue) {
                const index = keyValue.indexOf('=');
                const key = keyValue.substring(0, index);
                const value = keyValue.substring(index + 1);
                client[key] = value;
            });
            const name = client['name'];
            if (matcher(name)) {
                client['name'] = this.name;
                client['rawname'] = name;
                clients.push(client);
            }
        });
        return clients;
    }
    /**
     * Export the metrics for the queue in the Prometheus format.
     * Automatically exports all the counts returned by getJobCounts().
     *
     * @returns - Returns a string with the metrics in the Prometheus format.
     *
     * @see {@link https://prometheus.io/docs/instrumenting/exposition_formats/}
     *
     **/
    async exportPrometheusMetrics(globalVariables) {
        const counts = await this.getJobCounts();
        const metrics = [];
        // Match the test's expected HELP text
        metrics.push('# HELP bullmq_job_count Number of jobs in the queue by state');
        metrics.push('# TYPE bullmq_job_count gauge');
        const variables = !globalVariables
            ? ''
            : Object.keys(globalVariables).reduce((acc, curr) => `${acc}, ${curr}="${globalVariables[curr]}"`, '');
        for (const [state, count] of Object.entries(counts)) {
            metrics.push(`bullmq_job_count{queue="${this.name}", state="${state}"${variables}} ${count}`);
        }
        return metrics.join('\n');
    }
}
exports.QueueGetters = QueueGetters;
//# sourceMappingURL=queue-getters.js.map