"use strict";
// Copyright 2022 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from mjolnir
// https://github.com/matrix-org/mjolnir
// </text>
Object.defineProperty(exports, "__esModule", { value: true });
exports.ThrottlingQueue = void 0;
const matrix_bot_sdk_1 = require("@vector-im/matrix-bot-sdk");
const typescript_result_1 = require("@gnuxie/typescript-result");
/**
 * A queue for backgrounding tasks without hammering servers too much.
 */
class ThrottlingQueue {
    managementRoomOutput;
    delayMS;
    /**
     * The pending tasks.
     */
    _tasks = [];
    /**
     * A timeout for the next task to execute.
     */
    timeout;
    /**
     * Construct an empty queue.
     *
     * This queue will start executing whenever `push()` is called and stop
     * whenever it runs out of tasks to execute.
     *
     * @param delayMS The default delay between executing two tasks, in ms.
     */
    constructor(managementRoomOutput, 
    /**
     * How long we should wait between the completion of a tasks and the start of the next task.
     * Any >=0 number is good.
     */
    delayMS) {
        this.managementRoomOutput = managementRoomOutput;
        this.delayMS = delayMS;
        this.timeout = null;
        this.delayMS = delayMS;
        this._tasks = [];
    }
    /**
     * Stop the queue, make sure we can never use it again.
     */
    dispose() {
        this.stop();
        this._tasks = null;
    }
    /**
     * The number of tasks waiting to be executed.
     */
    get length() {
        return this.tasks.length;
    }
    /**
     * Push a new task onto the queue.
     *
     * @param task Some code to execute.
     */
    push(task) {
        this.tasks.push({ isBackgroundTask: true, factory: task });
        this.start();
    }
    /**
     * Block a queue for a number of milliseconds.
     *
     * This method is meant typically to be used by a `Task` that receives a 429 (Too Many Requests) to reschedule
     * itself for later, after giving the server a little room to breathe. If you need this, do not forget to
     * re-`push()` with the failing `Task`. You may call `block()` and `push()` in either order.
     *
     * @param durationMS A number of milliseconds to wait until resuming operations.
     */
    block(durationMS) {
        this.stop();
        this.timeout = setTimeout(() => {
            void this.step();
        }, durationMS);
    }
    /**
     * Start the loop to execute pending tasks.
     *
     * Does nothing if the loop is already started.
     */
    start() {
        if (this.timeout) {
            // Already started.
            return;
        }
        if (!this.tasks.length) {
            // Nothing to do.
            return;
        }
        this.timeout = setTimeout(() => {
            void this.step();
        }, this.delayMS);
    }
    /**
     * Stop the loop to execute pending tasks.
     *
     * Does nothing if the loop is already stopped. A loop stopped with `stop()` may be
     * resumed by calling `push()` or `start()`.
     */
    stop() {
        if (!this.timeout) {
            // Already stopped.
            return;
        }
        clearTimeout(this.timeout);
        this.timeout = null;
    }
    /**
     * Change the delay between completion of an event and the start of the next event.
     *
     * This will be used next time a task is completed.
     */
    setDelayMS(delayMS) {
        if (delayMS < 0) {
            throw new TypeError(`Invalid delay ${delayMS}. Need a non-negative number of ms.`);
        }
        this.delayMS = delayMS;
    }
    /**
     * Execute one step of the loop, then prepare the following step.
     *
     * 1. If there is no task, do nothing and stop.
     * 2. Otherwise, execute task.
     * 3. Once task is complete (whether succeeded or failed), retrigger the loop.
     */
    async step() {
        // Pull task.
        const task = this.tasks.shift();
        if (!task) {
            // Nothing to do.
            // Stop the loop until we have something to do.
            this.stop();
            return;
        }
        const result = await task.factory();
        if ((0, typescript_result_1.isError)(result)) {
            await this.managementRoomOutput.logMessage(matrix_bot_sdk_1.LogLevel.ERROR, "Error while executing task", result.error.toReadableString());
        }
        this.stop();
        this.start();
    }
    /**
     * Return `tasks`, unless the queue has been disposed of.
     */
    get tasks() {
        if (this._tasks === null) {
            throw new TypeError("This Throttling Queue has been disposed of and shouldn't be used anymore");
        }
        return this._tasks;
    }
}
exports.ThrottlingQueue = ThrottlingQueue;
//# sourceMappingURL=ThrottlingQueue.js.map