import { __read, __spread, __values } from "tslib";
import { createExternallyResolvablePromise, Cancellation, } from "@intelligentgraphics/ig.utilities";
import { IGXE } from "@intelligentgraphics/igd.core";
var OperationPriority;
(function (OperationPriority) {
    OperationPriority[OperationPriority["Normal"] = 0] = "Normal";
    OperationPriority[OperationPriority["Parent"] = 1] = "Parent";
    OperationPriority[OperationPriority["Final"] = 2] = "Final";
})(OperationPriority || (OperationPriority = {}));
var withCleanup = function (func, cleanup) { return function () {
    var args = [];
    for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
    }
    return func.apply(void 0, __spread(args)).then(function (res) {
        cleanup();
        return res;
    })
        .catch(function (err) {
        cleanup();
        return Promise.reject(err);
    });
}; };
export var createScheduler = function (articleQuery) {
    var comIdQueue = new Map();
    var comIdStatus = new Map();
    var next = function (comId) {
        var queue = comIdQueue.get(comId);
        if (queue === undefined || queue.length === 0) {
            comIdQueue.delete(comId);
            comIdStatus.delete(comId);
            return;
        }
        comIdStatus.set(comId, true);
        var nextIndex = queue.findIndex(function (item) { return item.priority === OperationPriority.Final; });
        if (nextIndex === -1) {
            nextIndex = queue.findIndex(function (item) { return item.priority === OperationPriority.Parent; });
        }
        if (nextIndex === -1) {
            nextIndex = 0;
        }
        var nextItem = queue.splice(nextIndex, 1)[0];
        nextItem.callback();
    };
    var addQueueItem = function (comId, type, data, priority, callback) {
        return new Promise(function (resolve, reject) {
            var cleanup = function () {
                ownOperationDone.resolve();
                next(comId);
            };
            var article = articleQuery.getArticleByComId(comId);
            if (article === undefined) {
                return reject(new Error("Refusing to queue an operation for unknown article \"" + comId + "\""));
            }
            var ownOperationDone = createExternallyResolvablePromise();
            var children = articleQuery.getDeepArticleChildren(comId);
            var childReadyPromise = Promise.all(children.map(function (comId) {
                return new Promise(function (resolve, reject) {
                    return addQueueItem(comId, type, undefined, OperationPriority.Parent, function () {
                        resolve();
                        return ownOperationDone.promise;
                    }).then(resolve, resolve);
                });
            }));
            var processItem = withCleanup(function (err) {
                return err !== undefined
                    ? Promise.reject(err).catch(reject)
                    : childReadyPromise
                        .then(callback)
                        .then(resolve, reject);
            }, cleanup);
            var item = {
                comId: comId,
                data: data,
                type: type,
                priority: priority,
                callback: processItem,
            };
            var queue = comIdQueue.get(item.comId);
            if (queue === undefined) {
                queue = [];
                comIdQueue.set(item.comId, queue);
            }
            queue.push(item);
            if (comIdStatus.get(comId)) {
                return;
            }
            next(item.comId);
        });
    };
    var queueOperation = function (comId, callback) {
        return addQueueItem(comId, "any", undefined, OperationPriority.Normal, callback);
    };
    var queueConfigureOperation = function (comId, changes, callback) {
        return addQueueItem(comId, "configuration", changes, OperationPriority.Normal, function () {
            var accumulatedChanges = changes;
            var skippedConfigurations = [];
            var queue = comIdQueue.get(comId);
            if (queue !== undefined && queue.length > 0) {
                var skipped = 0;
                var currentQueueLength = queue.length;
                for (var index = 0; index < currentQueueLength; index++) {
                    var entry = queue[index - skipped];
                    if (entry.type === "configuration") {
                        skippedConfigurations.push(entry.callback);
                        queue.splice(index - skipped, 1);
                        skipped++;
                        accumulatedChanges = IGXE.mergePropertyValueChanges(accumulatedChanges, entry.data);
                    }
                }
            }
            return callback(accumulatedChanges).then(function (result) {
                var e_1, _a;
                try {
                    for (var skippedConfigurations_1 = __values(skippedConfigurations), skippedConfigurations_1_1 = skippedConfigurations_1.next(); !skippedConfigurations_1_1.done; skippedConfigurations_1_1 = skippedConfigurations_1.next()) {
                        var entry = skippedConfigurations_1_1.value;
                        entry(Cancellation.createCancelError("Configuration request was swallowed by another configuration request"));
                    }
                }
                catch (e_1_1) { e_1 = { error: e_1_1 }; }
                finally {
                    try {
                        if (skippedConfigurations_1_1 && !skippedConfigurations_1_1.done && (_a = skippedConfigurations_1.return)) _a.call(skippedConfigurations_1);
                    }
                    finally { if (e_1) throw e_1.error; }
                }
                return result;
            });
        });
    };
    var queueDeleteOperation = function (comId, callback) {
        return addQueueItem(comId, "deletion", undefined, OperationPriority.Final, function () {
            return callback().then(function (res) {
                var e_2, _a;
                var queue = comIdQueue.get(comId);
                if (queue !== undefined) {
                    try {
                        for (var queue_1 = __values(queue), queue_1_1 = queue_1.next(); !queue_1_1.done; queue_1_1 = queue_1.next()) {
                            var entry = queue_1_1.value;
                            entry.callback(Cancellation.createCancelError("Operation has been canceled because of article deletion"));
                        }
                    }
                    catch (e_2_1) { e_2 = { error: e_2_1 }; }
                    finally {
                        try {
                            if (queue_1_1 && !queue_1_1.done && (_a = queue_1.return)) _a.call(queue_1);
                        }
                        finally { if (e_2) throw e_2.error; }
                    }
                }
                comIdQueue.delete(comId);
                return res;
            });
        });
    };
    var queueGfxOperation = function (comId, callback) {
        return addQueueItem(comId, "gfx", undefined, OperationPriority.Normal, callback);
    };
    return {
        queueOperation: queueOperation,
        queueConfigureOperation: queueConfigureOperation,
        queueDeleteOperation: queueDeleteOperation,
        queueGfxOperation: queueGfxOperation,
    };
};
