"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IntervalType = void 0;
const Logger_1 = require("@joplin/utils/Logger");
const shim_1 = require("./shim");
var IntervalType;
(function (IntervalType) {
    IntervalType[IntervalType["Debounce"] = 1] = "Debounce";
    IntervalType[IntervalType["Fixed"] = 2] = "Fixed";
})(IntervalType || (exports.IntervalType = IntervalType = {}));
const logger = Logger_1.default.create('AsyncActionQueue');
// The AsyncActionQueue can be used to debounce asynchronous actions, to make sure
// they run in the right order, and also to ensure that if multiple actions are emitted
// only the last one is executed. This is particularly useful to save data in the background.
// Each queue should be associated with a specific entity (a note, resource, etc.)
class AsyncActionQueue {
    constructor(interval = 100, intervalType = IntervalType.Debounce) {
        this.queue_ = [];
        this.scheduleProcessingIID_ = null;
        this.processing_ = false;
        // Determines whether an item can be skipped in the queue. Prevents data loss in the case that
        // tasks that do different things are added to the queue.
        this.canSkipTaskHandler_ = (_current, _next) => true;
        this.interval_ = interval;
        this.intervalType_ = intervalType;
        this.resetFinishProcessingPromise_();
    }
    resetFinishProcessingPromise_() {
        this.processingFinishedPromise_ = new Promise(resolve => {
            this.onProcessingFinished_ = resolve;
        });
    }
    setCanSkipTaskHandler(callback) {
        this.canSkipTaskHandler_ = callback;
    }
    push(action, context = null) {
        this.queue_.push({
            action: action,
            context: context,
        });
        this.scheduleProcessing();
    }
    get isEmpty() {
        return !this.queue_.length;
    }
    scheduleProcessing(interval = null) {
        if (interval === null)
            interval = this.interval_;
        if (this.scheduleProcessingIID_) {
            if (this.intervalType_ === IntervalType.Fixed)
                return;
            shim_1.default.clearTimeout(this.scheduleProcessingIID_);
        }
        this.scheduleProcessingIID_ = shim_1.default.setTimeout(() => {
            this.scheduleProcessingIID_ = null;
            void this.processQueue();
        }, interval);
    }
    async processQueue() {
        if (this.processing_) {
            this.scheduleProcessing();
            return;
        }
        const itemCount = this.queue_.length;
        if (itemCount) {
            this.processing_ = true;
            let i = 0;
            try {
                for (i = 0; i < itemCount; i++) {
                    const current = this.queue_[i];
                    const next = i + 1 < itemCount ? this.queue_[i + 1] : null;
                    if (!next || !this.canSkipTaskHandler_(current, next)) {
                        await current.action(current.context);
                    }
                }
            }
            catch (error) {
                i++; // Don't repeat the failed task.
                logger.warn('Unhandled error:', error);
                throw error;
            }
            finally {
                // Removing processed items in a try {} finally {...} prevents
                // items from being processed twice, even if one throws an Error.
                this.queue_.splice(0, i);
                this.processing_ = false;
            }
        }
        if (this.queue_.length === 0) {
            this.onProcessingFinished_();
            this.resetFinishProcessingPromise_();
        }
    }
    async reset() {
        if (this.scheduleProcessingIID_) {
            shim_1.default.clearTimeout(this.scheduleProcessingIID_);
            this.scheduleProcessingIID_ = null;
        }
        this.queue_ = [];
        return this.processAllNow();
    }
    async processAllNow() {
        this.scheduleProcessing(1);
        return this.waitForAllDone();
    }
    async waitForAllDone() {
        if (!this.queue_.length)
            return Promise.resolve();
        return this.processingFinishedPromise_;
    }
}
exports.default = AsyncActionQueue;
//# sourceMappingURL=AsyncActionQueue.js.map