"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var lodash_1 = require("lodash");
var Core = require("@intelligentgraphics/ig.gfx.core");
var ig_utilities_1 = require("@intelligentgraphics/ig.utilities");
var ig_gfx_planning_1 = require("@intelligentgraphics/ig.gfx.planning");
var MultiArticle = require("@intelligentgraphics/ig.gfx.multiarticle");
var Logger_1 = require("./Logger");
var Updater_1 = require("./Updater");
var Scheduler_1 = require("./Scheduler");
var APIUtil_1 = require("./APIUtil");
var Snapshot_1 = require("./Snapshot");
var ArticleInserter_1 = require("./ArticleInserter");
var LifecycleLogger = Logger_1.Logger.createSubLogger("Lifecycle");
var NOTIFICATION_ID = "PlannerManager";
var addItemToQueue = function (queue, article, update) {
    var updateObject = queue.get(article.comId) || {
        article: article,
        animationPositionChange: false,
        configurationOrArticleIdChange: false,
        force: false,
    };
    var newUpdate = tslib_1.__assign(tslib_1.__assign({}, updateObject), { article: article, animationPositionChange: updateObject.animationPositionChange ||
            update.animationPositionChange === true, configurationOrArticleIdChange: updateObject.configurationOrArticleIdChange ||
            update.configurationOrArticleIdChange === true, force: updateObject.force || update.force === true });
    queue.set(article.comId, newUpdate);
};
/**
 * Manages the lifecycle of a commercial article inside the gfx context
 *
 * @export
 * @class PlannerManager
 */
var PlannerManager = /** @class */ (function () {
    function PlannerManager(session, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        this.session = session;
        this.loadScheduler = new Scheduler_1.LoadScheduler();
        this.setUpdateQueue = new Set();
        this.articleUpdateQueue = new Map();
        var defaultsInjectedOptions = {
            parallelUpdates: options.parallelUpdates === undefined
                ? true
                : options.parallelUpdates,
            loadMode: options.loadMode,
            gfxType: options.gfxType === undefined ? "3D" : options.gfxType,
            executeArticlePlacement: options.executeArticlePlacement,
            scheduleSceneUpdate: options.scheduleSceneUpdate !== undefined
                ? options.scheduleSceneUpdate
                : function (cb) { return ig_utilities_1.delay().then(cb); },
            generatePreUpdateSnapshot: options.generatePreUpdateSnapshot !== undefined
                ? options.generatePreUpdateSnapshot
                : function (articles) { return _this.createSceneSnapshot(articles); },
            disableHashOptimizations: ig_utilities_1.valueOrDefault(options.disableHashOptimizations, false),
        };
        this.options = defaultsInjectedOptions;
        this.articleInserter = new ArticleInserter_1.ArticleInserter(session, Core.State.API, this.loadScheduler, {
            executeArticlePlacement: options.executeArticlePlacement,
            gfxType: defaultsInjectedOptions.gfxType,
            loadMode: defaultsInjectedOptions.loadMode,
            disableHashOptimizations: defaultsInjectedOptions.disableHashOptimizations,
        });
        this.hooks = tslib_1.__assign({ prepareForRemove: new ig_utilities_1.AsyncHook(), articleRemoved: new ig_utilities_1.AsyncHook(), sceneUpdateStart: new ig_utilities_1.AsyncHook(), sceneUpdateDone: new ig_utilities_1.AsyncHook() }, this.articleInserter.hooks);
        this.articleLoadLimiter = new ig_utilities_1.Limiter(this.options.parallelUpdates ? Infinity : 1);
        this.apiUtil = APIUtil_1.createAPIUtil(session, this.api);
        if (options.onArticleUpdateStart !== undefined) {
            this.hooks.prepareForUpdate.tap("PlannerManagerOptionCallbacks", function (_a) {
                var comId = _a.comId;
                ig_utilities_1.warnOnce("The \"onArticleUpdateStart\" option is deprecated. Please use the \"prepareForUpdate\" hook instead");
                options.onArticleUpdateStart(comId);
            });
        }
        if (options.onArticleUpdateEnd !== undefined) {
            this.hooks.articleUpdateDone.tap("PlannerManagerOptionCallbacks", function (_a, _b) {
                var comId = _a.comId;
                var initial = _b.initial;
                ig_utilities_1.warnOnce("The \"onArticleUpdateEnd\" option is deprecated. Please use the \"articleUpdateDone\" hook instead");
                options.onArticleUpdateEnd(comId, initial);
            });
        }
        if (options.onSceneUpdateStart !== undefined) {
            this.hooks.sceneUpdateStart.tap("PlannerManagerOptionCallbacks", function () {
                ig_utilities_1.warnOnce("The \"onSceneUpdateStart\" option is deprecated. Please use the \"sceneUpdateStart\" hook instead");
                options.onSceneUpdateStart();
            });
        }
        if (options.onSceneUpdateEnd !== undefined) {
            this.hooks.sceneUpdateDone.tap("PlannerManagerOptionCallbacks", function () {
                ig_utilities_1.warnOnce("The \"onSceneUpdateEnd\" option is deprecated. Please use the \"sceneUpdateDone\" hook instead");
                options.onSceneUpdateEnd();
            });
        }
    }
    Object.defineProperty(PlannerManager.prototype, "api", {
        get: function () {
            return Core.State.API;
        },
        enumerable: true,
        configurable: true
    });
    PlannerManager.prototype.createSceneSnapshot = function (articles) {
        return new Snapshot_1.SceneSnapshot({
            apiUtil: this.apiUtil,
            articles: articles,
        });
    };
    PlannerManager.prototype.hasPendingUpdates = function () {
        return this.articleUpdateQueue.size > 0 || this.setUpdateQueue.size > 0;
    };
    /**
     * Determines the loading status of the whole scene. Returns loadingFull if all loading articles are transitioning from preview to actual geometries
     *
     * @returns {LoadingStatus}
     */
    PlannerManager.prototype.getLoadingStatus = function () {
        var e_1, _a;
        if (this.loadInfo === undefined) {
            return "idle";
        }
        if (this.options.loadMode !== Core.ELoadGeometryPreviews.Staged) {
            return "loading";
        }
        var inPreviewState = true;
        try {
            for (var _b = tslib_1.__values(this.loadInfo.articles), _c = _b.next(); !_c.done; _c = _b.next()) {
                var comId = _c.value;
                var status_1 = this.articleInserter.getArticleLoadingStatus(comId);
                if (status_1 === undefined) {
                    inPreviewState = false;
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return inPreviewState ? "loadingFull" : "loading";
    };
    /**
     * Determines the status of the whole scene. Returns "preview" if at least one article is in a preview state
     *
     * @returns {SceneStatus}
     */
    PlannerManager.prototype.getSceneStatus = function () {
        var e_2, _a;
        try {
            for (var _b = tslib_1.__values(this.session.getArticlesOfType("graphical")), _c = _b.next(); !_c.done; _c = _b.next()) {
                var article = _c.value;
                if (article.gfxId === undefined) {
                    continue;
                }
                var component = this.api.GetComponent(article.gfxId);
                if (ig_utilities_1.isNullOrUndefined(component)) {
                    continue;
                }
                var data = this.api.GetComponentData(article.gfxId);
                if (data.geometryStatus === "preview") {
                    return "preview";
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return "normal";
    };
    /**
     * Subscribes the PlannerManager to the supplied session and insert/updates or removes articles from the gfx scene on session notifications
     *
     * @returns
     */
    PlannerManager.prototype.subscribe = function () {
        var _this = this;
        if (this.subscription !== undefined) {
            return;
        }
        this.subscription = this.session.subscribe({
            ItemRemoved: function (item) {
                if (item.type !== "set") {
                    _this.processItems([item]);
                }
            },
            ItemCreated: function (item) { return _this.processItems([item]); },
            ItemConfigured: function (item) { return _this.processItems([item]); },
        });
        this.session.hooks.beforeSave.tap("PlannerManager", function () {
            _this.session
                .getGraphicalArticles()
                .forEach(function (article) {
                return _this.synchronizeArticleSessionPlacement(article.comId);
            });
        });
        this.api.Hooks.afterAnimationProgressChange.tap("PlannerManager", function (componentId, progress) {
            if (progress !== 0 && progress !== 1) {
                return;
            }
            var comId = _this.session.getComIdByGfxId(componentId);
            if (comId === undefined) {
                return;
            }
            var article = _this.session.getArticleByComId(comId);
            var _a = _this.api.GetComponentData(article.gfxId), animationProgress = _a.animationProgress, plannerManager = _a.plannerManager;
            var loadedPosition = plannerManager === undefined
                ? 0
                : plannerManager.animationPosition || 0;
            var activePosition = animationProgress || 0;
            if (loadedPosition === activePosition) {
                return;
            }
            _this.addQueueItem(article, {
                animationPositionChange: true,
            });
            _this.startProcessingQueueIfNecessary();
        });
    };
    /**
     * Removes the subscription to the session
     *
     */
    PlannerManager.prototype.unsubscribe = function () {
        if (this.subscription !== undefined) {
            this.subscription.destroy();
        }
        this.session.hooks.beforeSave.untap("PlannerManager");
        this.api.Hooks.afterAnimationProgressChange.untap("PlannerManager");
    };
    /**
     * Deletes the gfx representation of an article instance or of a comId
     *
     * Should not be used manually when the PlannerManager has been subscribed to the session.
     *
     * @param {(Article | string)} item
     * @returns
     */
    PlannerManager.prototype.deleteArticle = function (item) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var instance, superRootId, neighbors;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        instance = typeof item === "object"
                            ? item
                            : this.session.getArticleByComId(item);
                        if (instance === undefined ||
                            instance.type !== "graphical" ||
                            instance.gfxId === undefined) {
                            return [2 /*return*/];
                        }
                        superRootId = Core.Hierarchy.isSuperRootGfxId(instance.gfxId)
                            ? instance.gfxId
                            : Core.Hierarchy.getSuperRootGfxId(instance.gfxId);
                        if (ig_utilities_1.isNullOrUndefined(superRootId) ||
                            ig_utilities_1.isNullOrUndefined(this.api.GetComponent(superRootId))) {
                            return [2 /*return*/];
                        }
                        neighbors = ig_gfx_planning_1.Associates.getNeighbors([instance.gfxId], false);
                        return [4 /*yield*/, this.hooks.prepareForRemove.emit(instance)];
                    case 1:
                        _a.sent();
                        this.api.RemoveComponent(superRootId);
                        return [4 /*yield*/, this.hooks.articleRemoved.emit(instance)];
                    case 2:
                        _a.sent();
                        this.session.sendNotification(NOTIFICATION_ID, "ItemGfxRemoved", instance);
                        neighbors.forEach(function (neighbor) {
                            if (!_this.api.GetComponent(neighbor) ||
                                _this.session.getArticleByGfxId(neighbor) === undefined) {
                                return;
                            }
                            _this.executeNeighborActions(neighbor);
                        });
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Loads or updates all available session articles inside the scene
     *
     * Should not be used manually when the PlannerManager has been subscribed to the session.
     *
     * @returns
     */
    PlannerManager.prototype.loadAllArticles = function () {
        return this.processItems(this.session.getAllArticles());
    };
    /**
     * Loads or updates a spefic session article inside the scene.
     *
     * Should not be used manually when the PlannerManager has been subscribed to the session.
     *
     * @param {string} comId
     * @returns {Promise<void>}
     */
    PlannerManager.prototype.loadArticle = function (comId) {
        return this.processItems([this.session.getArticleByComId(comId)]);
    };
    /**
     * Loads or updates spefic session articles inside the scene.
     *
     * Should not be used manually when the PlannerManager has been subscribed to the session.
     *
     * @param {string[]} comIds
     * @returns {Promise<void>}
     */
    PlannerManager.prototype.loadArticles = function (comIds) {
        var _this = this;
        return this.processItems(comIds.map(function (comId) { return _this.session.getArticleByComId(comId); }));
    };
    PlannerManager.prototype.processItems = function (items) {
        var e_3, _a;
        try {
            for (var items_1 = tslib_1.__values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
                var item = items_1_1.value;
                if (item === undefined) {
                    continue;
                }
                if (item.type === "graphical") {
                    this.addQueueItem(item, { force: true });
                }
                if (item.type === "set") {
                    this.setUpdateQueue.add(item.comId);
                    continue;
                }
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
            }
            finally { if (e_3) throw e_3.error; }
        }
        return this.startProcessingQueueIfNecessary();
    };
    PlannerManager.prototype.startProcessingQueueIfNecessary = function () {
        var _this = this;
        if (this.updatePromise !== undefined) {
            return this.updatePromise;
        }
        this.updatePromise = this.executeAllPendingAndCascadingUpdates().then(function () {
            _this.updatePromise = undefined;
        });
        return this.updatePromise;
    };
    PlannerManager.prototype.addQueueItem = function (article, update) {
        addItemToQueue(this.articleUpdateQueue, article, update);
    };
    /**
     * Executes all pending article updates and keeps updating as long as the queue is not empty after an update
     *
     * @private
     */
    PlannerManager.prototype.executeAllPendingAndCascadingUpdates = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.options
                            .scheduleSceneUpdate(function () { return _this.updateSceneFromCurrentQueue(); })
                            .catch(function (err) { return Logger_1.Logger.error(err); })];
                    case 1:
                        _a.sent();
                        if (!(this.articleUpdateQueue.size > 0)) return [3 /*break*/, 3];
                        return [4 /*yield*/, this.executeAllPendingAndCascadingUpdates()];
                    case 2:
                        _a.sent();
                        _a.label = 3;
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Updates all articles and sets inside of the current queues
     *
     * @private
     * @returns {Promise<void>}
     */
    PlannerManager.prototype.updateSceneFromCurrentQueue = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var articleQueue, setQueue, allArticles, setQueue_1, setQueue_1_1, comId, children, children_1, children_1_1, childComId, articleQueue_1, articleQueue_1_1, _a, comId, queuedArticle, article, e_4_1, setQueue_2, setQueue_2_1, comId, remainingArticles, set, remainingArticles_1, remainingArticles_1_1, article, err_1, e_5_1, articles, _b, newPlacedArticles, newUnplacedArticles, existingArticles, newUnplacedArticles_1, newUnplacedArticles_1_1, comId, e_6_1, err_2;
            var e_7, _c, e_8, _d, e_4, _e, e_5, _f, e_9, _g, e_6, _h;
            var _this = this;
            return tslib_1.__generator(this, function (_j) {
                switch (_j.label) {
                    case 0:
                        if (this.articleUpdateQueue.size === 0 &&
                            this.setUpdateQueue.size === 0) {
                            return [2 /*return*/];
                        }
                        Logger_1.Logger.info("Updating scene");
                        articleQueue = this.articleUpdateQueue;
                        this.articleUpdateQueue = new Map();
                        setQueue = this.setUpdateQueue;
                        this.setUpdateQueue = new Set();
                        this.loadInfo = {
                            articles: Array.from(articleQueue.keys()),
                            sets: Array.from(setQueue),
                        };
                        allArticles = new Set();
                        try {
                            for (setQueue_1 = tslib_1.__values(setQueue), setQueue_1_1 = setQueue_1.next(); !setQueue_1_1.done; setQueue_1_1 = setQueue_1.next()) {
                                comId = setQueue_1_1.value;
                                children = this.session.getArticleChildren(comId);
                                try {
                                    for (children_1 = (e_8 = void 0, tslib_1.__values(children)), children_1_1 = children_1.next(); !children_1_1.done; children_1_1 = children_1.next()) {
                                        childComId = children_1_1.value;
                                        allArticles.add(childComId);
                                    }
                                }
                                catch (e_8_1) { e_8 = { error: e_8_1 }; }
                                finally {
                                    try {
                                        if (children_1_1 && !children_1_1.done && (_d = children_1.return)) _d.call(children_1);
                                    }
                                    finally { if (e_8) throw e_8.error; }
                                }
                            }
                        }
                        catch (e_7_1) { e_7 = { error: e_7_1 }; }
                        finally {
                            try {
                                if (setQueue_1_1 && !setQueue_1_1.done && (_c = setQueue_1.return)) _c.call(setQueue_1);
                            }
                            finally { if (e_7) throw e_7.error; }
                        }
                        _j.label = 1;
                    case 1:
                        _j.trys.push([1, 34, , 35]);
                        _j.label = 2;
                    case 2:
                        _j.trys.push([2, 8, 9, 10]);
                        articleQueue_1 = tslib_1.__values(articleQueue), articleQueue_1_1 = articleQueue_1.next();
                        _j.label = 3;
                    case 3:
                        if (!!articleQueue_1_1.done) return [3 /*break*/, 7];
                        _a = tslib_1.__read(articleQueue_1_1.value, 2), comId = _a[0], queuedArticle = _a[1];
                        article = this.session.getArticleByComId(comId);
                        if (!(article === undefined)) return [3 /*break*/, 5];
                        return [4 /*yield*/, this.deleteArticle(queuedArticle.article)];
                    case 4:
                        _j.sent();
                        articleQueue.delete(queuedArticle.article.comId);
                        return [3 /*break*/, 6];
                    case 5:
                        allArticles.add(comId);
                        _j.label = 6;
                    case 6:
                        articleQueue_1_1 = articleQueue_1.next();
                        return [3 /*break*/, 3];
                    case 7: return [3 /*break*/, 10];
                    case 8:
                        e_4_1 = _j.sent();
                        e_4 = { error: e_4_1 };
                        return [3 /*break*/, 10];
                    case 9:
                        try {
                            if (articleQueue_1_1 && !articleQueue_1_1.done && (_e = articleQueue_1.return)) _e.call(articleQueue_1);
                        }
                        finally { if (e_4) throw e_4.error; }
                        return [7 /*endfinally*/];
                    case 10: return [4 /*yield*/, this.hooks.sceneUpdateStart.emit(Array.from(allArticles))];
                    case 11:
                        _j.sent();
                        _j.label = 12;
                    case 12:
                        _j.trys.push([12, 19, 20, 21]);
                        setQueue_2 = tslib_1.__values(setQueue), setQueue_2_1 = setQueue_2.next();
                        _j.label = 13;
                    case 13:
                        if (!!setQueue_2_1.done) return [3 /*break*/, 18];
                        comId = setQueue_2_1.value;
                        _j.label = 14;
                    case 14:
                        _j.trys.push([14, 16, , 17]);
                        return [4 /*yield*/, this.updateSet(comId)];
                    case 15:
                        remainingArticles = _j.sent();
                        set = this.session.getArticleByComId(comId);
                        set.children.forEach(function (child) { return articleQueue.delete(child); });
                        try {
                            for (remainingArticles_1 = (e_9 = void 0, tslib_1.__values(remainingArticles)), remainingArticles_1_1 = remainingArticles_1.next(); !remainingArticles_1_1.done; remainingArticles_1_1 = remainingArticles_1.next()) {
                                article = remainingArticles_1_1.value;
                                addItemToQueue(articleQueue, article, {
                                    configurationOrArticleIdChange: true,
                                });
                            }
                        }
                        catch (e_9_1) { e_9 = { error: e_9_1 }; }
                        finally {
                            try {
                                if (remainingArticles_1_1 && !remainingArticles_1_1.done && (_g = remainingArticles_1.return)) _g.call(remainingArticles_1);
                            }
                            finally { if (e_9) throw e_9.error; }
                        }
                        return [3 /*break*/, 17];
                    case 16:
                        err_1 = _j.sent();
                        Logger_1.Logger.error(err_1);
                        return [3 /*break*/, 17];
                    case 17:
                        setQueue_2_1 = setQueue_2.next();
                        return [3 /*break*/, 13];
                    case 18: return [3 /*break*/, 21];
                    case 19:
                        e_5_1 = _j.sent();
                        e_5 = { error: e_5_1 };
                        return [3 /*break*/, 21];
                    case 20:
                        try {
                            if (setQueue_2_1 && !setQueue_2_1.done && (_f = setQueue_2.return)) _f.call(setQueue_2);
                        }
                        finally { if (e_5) throw e_5.error; }
                        return [7 /*endfinally*/];
                    case 21:
                        articles = Array.from(articleQueue.keys());
                        _b = this.filterArticles(articles), newPlacedArticles = _b.newPlacedArticles, newUnplacedArticles = _b.newUnplacedArticles, existingArticles = _b.existingArticles;
                        if (!(newPlacedArticles.length > 0)) return [3 /*break*/, 23];
                        return [4 /*yield*/, Promise.all(newPlacedArticles.map(function (comId) {
                                return _this.queueArticleInsert(articleQueue.get(comId)).catch(Logger_1.Logger.error);
                            }))];
                    case 22:
                        _j.sent();
                        _j.label = 23;
                    case 23:
                        if (!(existingArticles.length > 0)) return [3 /*break*/, 25];
                        return [4 /*yield*/, Updater_1.updateArticles({
                                snapshot: this.options.generatePreUpdateSnapshot(existingArticles),
                                articles: existingArticles,
                                loadGeometry: function (comId) {
                                    return _this.queueArticleInsert(articleQueue.get(comId));
                                },
                                executeParentActions: function (actionHolder, comId) {
                                    return _this.executeParentActions(actionHolder, _this.session.getArticleGfxId(comId));
                                },
                                executeNeighborActions: function (comId) {
                                    return _this.executeNeighborActions(_this.session.getArticleGfxId(comId));
                                },
                                deleteArticleAndChildren: function (comId) {
                                    return _this.session.deleteArticle(comId);
                                },
                                updateArticlePosition: this.apiUtil
                                    .placeArticleAtAttachPoint,
                            })];
                    case 24:
                        _j.sent();
                        _j.label = 25;
                    case 25:
                        _j.trys.push([25, 30, 31, 32]);
                        newUnplacedArticles_1 = tslib_1.__values(newUnplacedArticles), newUnplacedArticles_1_1 = newUnplacedArticles_1.next();
                        _j.label = 26;
                    case 26:
                        if (!!newUnplacedArticles_1_1.done) return [3 /*break*/, 29];
                        comId = newUnplacedArticles_1_1.value;
                        return [4 /*yield*/, this.queueArticleInsert(articleQueue.get(comId)).catch(Logger_1.Logger.error)];
                    case 27:
                        _j.sent();
                        _j.label = 28;
                    case 28:
                        newUnplacedArticles_1_1 = newUnplacedArticles_1.next();
                        return [3 /*break*/, 26];
                    case 29: return [3 /*break*/, 32];
                    case 30:
                        e_6_1 = _j.sent();
                        e_6 = { error: e_6_1 };
                        return [3 /*break*/, 32];
                    case 31:
                        try {
                            if (newUnplacedArticles_1_1 && !newUnplacedArticles_1_1.done && (_h = newUnplacedArticles_1.return)) _h.call(newUnplacedArticles_1);
                        }
                        finally { if (e_6) throw e_6.error; }
                        return [7 /*endfinally*/];
                    case 32: 
                    // all articles are loaded -> we can execute neighbor actions
                    return [4 /*yield*/, Promise.all(articles.map(function (comId) {
                            var article = _this.session.getArticleByComId(comId);
                            if (article !== undefined && article.gfxId !== undefined) {
                                return _this.executeNeighborActions(article.gfxId);
                            }
                        }))];
                    case 33:
                        // all articles are loaded -> we can execute neighbor actions
                        _j.sent();
                        return [3 /*break*/, 35];
                    case 34:
                        err_2 = _j.sent();
                        Logger_1.Logger.error("Encountered an error while updating the scene", err_2);
                        return [3 /*break*/, 35];
                    case 35: return [4 /*yield*/, this.hooks.sceneUpdateDone
                            .emit(Array.from(allArticles))
                            .catch(Logger_1.Logger.error)];
                    case 36:
                        _j.sent();
                        Logger_1.Logger.info("Finished updating scene");
                        this.loadInfo = undefined;
                        return [2 /*return*/];
                }
            });
        });
    };
    PlannerManager.prototype.updateSet = function (setComId) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var setArticles;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                setArticles = this.session
                    .getArticleChildren(setComId)
                    .map(function (comId) { return _this.session.getArticleByComId(comId); });
                // for (const article of setArticles) {
                // 	if (article.sourcePlacement !== undefined) {
                // 		this.session.setArticlePlacement(
                // 			article.comId,
                // 			article.sourcePlacement,
                // 		);
                // 		if (article.gfxId !== undefined) {
                // 			this.synchronizeArticleScenePlacement(article.comId);
                // 		}
                // 	}
                // }
                return [2 /*return*/, setArticles];
            });
        });
    };
    //#region Old Set stuff
    // private async updateSet(setComId: string) {
    // 	try {
    // 		const setArticles = this.session
    // 			.getArticleChildren(setComId)
    // 			.map(comId => this.session.getArticleByComId(comId)!);
    // 		if (setArticles.length === 0) {
    // 			throw new Error(
    // 				`Could not find any articles for the set: ${setComId}`,
    // 			);
    // 		}
    // 		this.setUpdateQueue.delete(setComId);
    // 		if (setArticles.length === 0) {
    // 			return [];
    // 		}
    // 		let positionRootArticle: Article | undefined;
    // 		const secondaryArticles: Article[] = [];
    // 		const articlePlacements: Record<
    // 			string,
    // 			SourcePlacement | undefined
    // 		> = {};
    // 		for (const article of setArticles) {
    // 			secondaryArticles.push(article);
    // 			articlePlacements[article.comId] = article.sourcePlacement;
    // 		}
    // 		/* ICOM Article Placement
    // 			a) Scan all the already placed articles for the smallest position and use that article as the placement anchor for all the others
    // 			b) Look for an article which doesn't have a placement inside the icom context
    // 			c) Look for the smallest icom placement
    // 		*/
    // 		// random condition block here, so smallestPosition and smallestIndex dont leak out into the main method scope
    // 		if (true) {
    // 			let smallestPosition: Core.vec3 | undefined;
    // 			let smallestIndex: number | undefined;
    // 			for (let index = 0; index < setArticles.length; index++) {
    // 				const article = setArticles[index];
    // 				const placement = this.getSceneArticlePlacement(
    // 					article.comId,
    // 				);
    // 				if (
    // 					placement !== undefined &&
    // 					(smallestPosition === undefined ||
    // 						placement.position.x < smallestPosition.x)
    // 				) {
    // 					smallestPosition = placement.position;
    // 					smallestIndex = index;
    // 				}
    // 			}
    // 			if (smallestIndex !== undefined) {
    // 				positionRootArticle = secondaryArticles.splice(
    // 					smallestIndex,
    // 					1,
    // 				)[0];
    // 				const rootArticlePlacement =
    // 					articlePlacements[positionRootArticle.comId];
    // 				if (rootArticlePlacement !== undefined) {
    // 					for (const { comId } of secondaryArticles) {
    // 						const articlePlacement = articlePlacements[comId]!;
    // 						if (articlePlacement.type === "absolute") {
    // 							continue;
    // 						}
    // 						articlePlacement.position = Core.Vector.Sub3(
    // 							articlePlacement.position,
    // 							rootArticlePlacement.position,
    // 						);
    // 					}
    // 				}
    // 			}
    // 		}
    // 		if (positionRootArticle === undefined) {
    // 			const articleWithoutPlacement = setArticles.find(
    // 				article => articlePlacements[article.comId] === undefined,
    // 			);
    // 			if (articleWithoutPlacement === undefined) {
    // 				positionRootArticle = articleWithoutPlacement;
    // 			}
    // 		}
    // 		if (positionRootArticle === undefined) {
    // 			// no article has a position of undefined -> look for the smallest position and use it as the root
    // 			let smallestPosition: Core.vec3 | undefined;
    // 			let smallestIndex: number;
    // 			for (let index = 0; index < secondaryArticles.length; index++) {
    // 				const article = secondaryArticles[index];
    // 				const articlePosition = articlePlacements[article.comId]!;
    // 				if (
    // 					smallestPosition === undefined ||
    // 					smallestPosition.x > articlePosition.position!.x
    // 				) {
    // 					smallestPosition = articlePosition.position;
    // 					smallestIndex = index;
    // 				}
    // 			}
    // 			positionRootArticle = secondaryArticles[smallestIndex!];
    // 			secondaryArticles.splice(smallestIndex!, 1);
    // 			for (const { comId } of secondaryArticles) {
    // 				const articlePlacement = articlePlacements[comId]!;
    // 				if (articlePlacement.type === "absolute") {
    // 					continue;
    // 				}
    // 				articlePlacement.position = Core.Vector.Sub3(
    // 					articlePlacement.position,
    // 					smallestPosition!,
    // 				);
    // 			}
    // 		}
    // 		if (positionRootArticle === undefined) {
    // 			// this should never happen
    // 			throw new Error(
    // 				`Could not find the position root article for set: ${setComId}`,
    // 			);
    // 		}
    // 		// load the position root article first, so we can place other articles relative to it
    // 		await this.insertSingleArticle(positionRootArticle.comId);
    // 		const positionRootGfxId = this.session.getArticleGfxId(
    // 			positionRootArticle.comId,
    // 		)!;
    // 		const rootPlacement = articlePlacements[positionRootArticle.comId];
    // 		const mainPosition = this.api.GetPosition(positionRootGfxId);
    // 		if (
    // 			rootPlacement !== undefined &&
    // 			rootPlacement.rotation !== undefined
    // 		) {
    // 			this.api.SetYRotation(
    // 				positionRootGfxId,
    // 				rootPlacement.rotation.y,
    // 			);
    // 		}
    // 		this.synchronizeArticlePlacement(positionRootArticle.comId);
    // 		// calculate absolute positions for all remaining articles and save them to be used later
    // 		for (const article of secondaryArticles) {
    // 			const articlePlacement = articlePlacements[article.comId];
    // 			const placement = this.session.getArticlePlacement(
    // 				article.comId,
    // 			);
    // 			const dependentPlacement = RuleUtil.hasRule(
    // 				article,
    // 				"manipulable",
    // 			);
    // 			// when an article has not yet been placed, or if it's not manipulable by itself
    // 			// -> always update placement
    // 			if (
    // 				(placement === undefined || dependentPlacement) &&
    // 				articlePlacement !== undefined
    // 			) {
    // 				if (articlePlacement.type === "absolute") {
    // 					this.session.setArticlePlacement(
    // 						article.comId,
    // 						articlePlacement,
    // 					);
    // 					continue;
    // 				}
    // 				const newPlacement: Required<IPlacement> = {
    // 					position: Core.Vector3.add(
    // 						mainPosition,
    // 						articlePlacement.position,
    // 					),
    // 					rotation: {
    // 						x: articlePlacement.rotation.x,
    // 						y: articlePlacement.rotation.y,
    // 						z: articlePlacement.rotation.z,
    // 					},
    // 				};
    // 				this.session.setArticlePlacement(
    // 					article.comId,
    // 					newPlacement,
    // 				);
    // 			}
    // 		}
    // 		// return all the remaining not yet loaded articles and let them be handled by the normal article loading
    // 		return secondaryArticles;
    // 	} catch (err) {
    // 		throw err;
    // 	}
    // }
    //#endregion
    PlannerManager.prototype.queueArticleInsert = function (update) {
        var _this = this;
        return this.session.scheduleArticleOperation(update.article.comId, function () {
            return _this.articleLoadLimiter.queue(function () {
                return _this.insertSingleArticle(update);
            });
        });
    };
    /**
     * Load graphics for the passed commercial article directly.
     *
     * @private
     * @param {string} comId
     */
    PlannerManager.prototype.insertSingleArticle = function (update) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var comId, queueItem, item, result, _a;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        comId = update.article.comId;
                        LifecycleLogger.info("Inserting article \"" + comId + "\"");
                        queueItem = this.articleUpdateQueue.get(comId);
                        // if this article has been added to the queue again, we can safely remove it from the queue and apply both updates at once
                        this.articleUpdateQueue.delete(comId);
                        item = this.session.getArticleByComId(comId);
                        if (!(item === undefined)) return [3 /*break*/, 3];
                        if (!(queueItem !== undefined)) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.deleteArticle(queueItem.article)];
                    case 1:
                        _b.sent();
                        _b.label = 2;
                    case 2:
                        LifecycleLogger.info("Skipping update for \"" + comId + "\" because it does not exist");
                        return [2 /*return*/];
                    case 3: return [4 /*yield*/, this.articleInserter.insertSingleArticle(tslib_1.__assign(tslib_1.__assign({}, update), { article: item }))];
                    case 4:
                        result = _b.sent();
                        if (!(result.status === "error")) return [3 /*break*/, 9];
                        _a = result.error.type;
                        switch (_a) {
                            case "articleUnknown": return [3 /*break*/, 5];
                            case "noGfxRepresentation": return [3 /*break*/, 7];
                        }
                        return [3 /*break*/, 8];
                    case 5: return [4 /*yield*/, this.deleteArticle(comId)];
                    case 6:
                        _b.sent();
                        return [2 /*return*/];
                    case 7: throw new Error("Article \"" + comId + "\" does not have a gfx representation");
                    case 8: throw result.error.error;
                    case 9:
                        if (result.status === "updateRequired") {
                            this.addQueueItem(this.session.getArticleByComId(comId), {
                                force: true,
                            });
                        }
                        this.session.sendNotification(NOTIFICATION_ID, "ItemGfxInserted", this.session.getArticleByComId(comId));
                        LifecycleLogger.info("Finished inserting article \"" + comId + "\"");
                        return [2 /*return*/];
                }
            });
        });
    };
    PlannerManager.prototype.filterArticles = function (changedArticles) {
        var e_10, _a;
        var uniqueArticles = new Set(changedArticles);
        var existingArticles = [];
        var newUnplacedArticles = [];
        var newPlacedArticles = [];
        try {
            for (var uniqueArticles_1 = tslib_1.__values(uniqueArticles), uniqueArticles_1_1 = uniqueArticles_1.next(); !uniqueArticles_1_1.done; uniqueArticles_1_1 = uniqueArticles_1.next()) {
                var comId = uniqueArticles_1_1.value;
                var article = this.session.getArticleByComId(comId);
                if (article === undefined) {
                    continue;
                }
                if (this.apiUtil.getRootGfxId(comId) !== undefined) {
                    existingArticles.push(comId);
                    continue;
                }
                var _b = this.session.getArticleByComId(comId), placement = _b.placement, sourcePlacement = _b.sourcePlacement;
                if (placement !== undefined ||
                    (sourcePlacement !== undefined &&
                        sourcePlacement.type === "absolute")) {
                    newPlacedArticles.push(comId);
                    continue;
                }
                newUnplacedArticles.push(comId);
            }
        }
        catch (e_10_1) { e_10 = { error: e_10_1 }; }
        finally {
            try {
                if (uniqueArticles_1_1 && !uniqueArticles_1_1.done && (_a = uniqueArticles_1.return)) _a.call(uniqueArticles_1);
            }
            finally { if (e_10) throw e_10.error; }
        }
        return {
            existingArticles: existingArticles,
            newUnplacedArticles: newUnplacedArticles,
            newPlacedArticles: newPlacedArticles,
        };
    };
    PlannerManager.prototype.synchronizeArticleSessionPlacement = function (comId) {
        var placement = this.getSceneArticlePlacement(comId);
        if (placement !== undefined) {
            this.session.setArticlePlacement(comId, placement);
        }
    };
    PlannerManager.prototype.getSceneArticlePlacement = function (comId) {
        var gfxId = this.session.getArticleGfxId(comId);
        if (gfxId === undefined) {
            return undefined;
        }
        var component = this.api.GetComponent(gfxId);
        if (ig_utilities_1.isNullOrUndefined(component)) {
            return undefined;
        }
        return {
            position: this.api.GetPosition(gfxId),
            rotation: {
                x: 0,
                y: this.api.GetYRotation(gfxId),
                z: 0,
            },
        };
    };
    PlannerManager.prototype.executeParentActions = function (actionHolderId, gfxId) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var parentAction, executionResult, didChange;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        parentAction = this.api
                            .GetActions(actionHolderId)
                            .find(function (action) { return action.Mode === "ParentDependency"; });
                        if (!parentAction) {
                            return [2 /*return*/, false];
                        }
                        return [4 /*yield*/, MultiArticle.Rule.executeActions(parentAction.Rules, gfxId, actionHolderId)];
                    case 1:
                        executionResult = _a.sent();
                        didChange = typeof executionResult === "boolean" ? executionResult : true;
                        return [2 /*return*/, didChange];
                }
            });
        });
    };
    PlannerManager.prototype.executeNeighborActions = function (subRootName) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var neighbors, rootsThatShouldExecuteActions, extractRules, didChange, rootsThatShouldExecuteActions_1, rootsThatShouldExecuteActions_1_1, id, neighborActions, actionRules, result, e_11_1;
            var e_11, _a;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        neighbors = [
                            ig_gfx_planning_1.Associates.neighbor(subRootName, ig_gfx_planning_1.NeighborType.Left),
                            ig_gfx_planning_1.Associates.neighbor(subRootName, ig_gfx_planning_1.NeighborType.Right),
                        ].filter(Boolean);
                        rootsThatShouldExecuteActions = tslib_1.__spread([subRootName], neighbors);
                        extractRules = function (action) { return action.Rules; };
                        didChange = false;
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 6, 7, 8]);
                        rootsThatShouldExecuteActions_1 = tslib_1.__values(rootsThatShouldExecuteActions), rootsThatShouldExecuteActions_1_1 = rootsThatShouldExecuteActions_1.next();
                        _b.label = 2;
                    case 2:
                        if (!!rootsThatShouldExecuteActions_1_1.done) return [3 /*break*/, 5];
                        id = rootsThatShouldExecuteActions_1_1.value;
                        neighborActions = this.api
                            .GetActions(id)
                            .filter(function (action) {
                            return action.Mode === "NeighborDependency";
                        });
                        actionRules = lodash_1.flatMap(neighborActions, extractRules);
                        if (!actionRules.length) return [3 /*break*/, 4];
                        return [4 /*yield*/, MultiArticle.Rule.executeActions(actionRules, id, id)];
                    case 3:
                        result = _b.sent();
                        didChange =
                            didChange || (typeof result === "boolean" ? result : true);
                        _b.label = 4;
                    case 4:
                        rootsThatShouldExecuteActions_1_1 = rootsThatShouldExecuteActions_1.next();
                        return [3 /*break*/, 2];
                    case 5: return [3 /*break*/, 8];
                    case 6:
                        e_11_1 = _b.sent();
                        e_11 = { error: e_11_1 };
                        return [3 /*break*/, 8];
                    case 7:
                        try {
                            if (rootsThatShouldExecuteActions_1_1 && !rootsThatShouldExecuteActions_1_1.done && (_a = rootsThatShouldExecuteActions_1.return)) _a.call(rootsThatShouldExecuteActions_1);
                        }
                        finally { if (e_11) throw e_11.error; }
                        return [7 /*endfinally*/];
                    case 8: return [2 /*return*/, didChange];
                }
            });
        });
    };
    return PlannerManager;
}());
exports.PlannerManager = PlannerManager;
