/// <reference path="../../../js/common.d.ts" />
/// <reference path="../../../js/knockout.d.ts" />
/// <reference path="../../../modules/actor-selector/actor-selector.ts" />
/// <reference path="../../../js/lodash-3.10.d.ts" />
/// <reference path="../../../js/lazyload.d.ts" />
/// <reference path="../../../ajax-wrapper/ajax-action-wrapper.d.ts" />
/// <reference path="./eh-preferences.d.ts" />
'use strict';
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var ameEasyHideModel = null;
var AmeEasyHide;
(function (AmeEasyHide) {
    var _ = wsAmeLodash;
    var SortOrder;
    (function (SortOrder) {
        SortOrder[SortOrder["SORT_ALPHA"] = 0] = "SORT_ALPHA";
        SortOrder[SortOrder["SORT_INSERTION"] = 1] = "SORT_INSERTION";
    })(SortOrder || (SortOrder = {}));
    var Category = /** @class */ (function () {
        function Category(id, label, parent, invertItemState, filterState, initialSortOrder, itemSortOrder, priority, subtitle) {
            var _this = this;
            if (parent === void 0) { parent = null; }
            if (invertItemState === void 0) { invertItemState = false; }
            if (filterState === void 0) { filterState = null; }
            if (initialSortOrder === void 0) { initialSortOrder = SortOrder.SORT_ALPHA; }
            if (itemSortOrder === void 0) { itemSortOrder = SortOrder.SORT_INSERTION; }
            if (priority === void 0) { priority = Category.DEFAULT_PRIORITY; }
            if (subtitle === void 0) { subtitle = null; }
            this.id = id;
            this.label = label;
            this.parent = parent;
            this.invertItemState = invertItemState;
            this.initialSortOrder = initialSortOrder;
            this.itemSortOrder = itemSortOrder;
            this.priority = priority;
            this.subtitle = subtitle;
            this.containsSelectedCategory = ko.observable(false);
            this.isStandardRenderingEnabled = ko.observable(true);
            this.tableViewEnabled = false;
            this.tableView = null;
            this.cachedParentList = null;
            Category.counter++;
            this.safeElementId = 'ame-eh-category-n-' + Category.counter;
            this.isSelected = ko.observable(false);
            /**
             * Is this category selected or inside a selected category?
             */
            this.isInSelectedCategory = ko.pureComputed(function () {
                var current = _this;
                while (current !== null) {
                    if (current.isSelected()) {
                        return true;
                    }
                    current = current.parent;
                }
                return false;
            });
            this.isShowingItems = ko.pureComputed(function () {
                //The items are visible if this category or one of its parents is selected.
                return _this.isInSelectedCategory();
            });
            this.isExpanded = ko.observable(this.parent === null);
            this.isVisible = ko.pureComputed(function () {
                var inSelectedTree = _this.isInSelectedCategory() || _this.containsSelectedCategory();
                if (!inSelectedTree) {
                    return false;
                }
                //Show the category if it has any visible children: items or subcategories.
                var items = _this.directItems();
                if (_.some(items, function (item) { return item.isVisible(); })) {
                    return true;
                }
                var subcategories = _this.subcategories();
                return _.some(subcategories, function (category) { return category.isVisible(); });
            });
            this.subcategories = ko.observableArray([]);
            this.sortedSubcategories = ko.pureComputed(function () {
                var cats = _this.subcategories();
                //Remove categories that won't be rendered.
                cats = cats.filter(function (c) { return c.isStandardRenderingEnabled(); });
                if (_this.initialSortOrder === SortOrder.SORT_ALPHA) {
                    cats.sort(function (a, b) {
                        if (a.priority !== b.priority) {
                            return a.priority - b.priority;
                        }
                        return a.label.localeCompare(b.label);
                    });
                }
                else if (_this.initialSortOrder === SortOrder.SORT_INSERTION) {
                    //We want a stable sort that preserves insertion order for
                    //categories that have the same priority. As of this writing,
                    //Array.sort() is not stable in IE, so let's use Lodash.
                    cats = _.sortBy(cats, 'priority');
                }
                return cats;
            });
            this.items = ko.observableArray([]);
            this.directItems = ko.pureComputed(function () {
                var results = _(_this.items())
                    .filter(function (item) {
                    //An item is a direct/root level item in this category
                    //only if it has no parent or if its parent is not
                    //in the same category.
                    if (item.parent === null) {
                        return true;
                    }
                    else {
                        return !_.contains(item.parent.categories, _this);
                    }
                })
                    .value();
                //Sort items alphabetically if requested.
                if (_this.itemSortOrder === SortOrder.SORT_ALPHA) {
                    results.sort(function (a, b) {
                        return a.label.localeCompare(b.label);
                    });
                }
                return results;
            });
            this.isIndeterminate = ko.observable(false);
            //The whole category is checked if at least one of its
            //items or subcategories is checked.
            this.isChecked = ko.computed({
                read: function () {
                    var hasIndeterminateChildren = false;
                    var hasCheckedItems = false, hasUncheckedItems = false;
                    _.forEach(_this.items(), function (item) {
                        if (item.isChecked()) {
                            hasCheckedItems = true;
                        }
                        else {
                            hasUncheckedItems = true;
                        }
                        if (item.isIndeterminate()) {
                            hasIndeterminateChildren = true;
                        }
                        if (hasCheckedItems && hasUncheckedItems) {
                            //We know the category has a mix of checked and
                            //unchecked items, so there's no need to continue.
                            return false;
                        }
                    });
                    var hasCheckedCats = false, hasUncheckedCats = false;
                    _.forEach(_this.subcategories(), function (category) {
                        if (category.isChecked()) {
                            hasCheckedCats = true;
                        }
                        else {
                            hasUncheckedCats = true;
                        }
                        if (category.isIndeterminate()) {
                            hasIndeterminateChildren = true;
                        }
                        if (hasCheckedCats && hasUncheckedCats) {
                            return false;
                        }
                    });
                    var areAnyChecked = hasCheckedItems || hasCheckedCats;
                    var areAnyUnchecked = hasUncheckedItems || hasUncheckedCats;
                    _this.isIndeterminate(hasIndeterminateChildren || (areAnyChecked && areAnyUnchecked));
                    return areAnyChecked;
                },
                write: function (checked) {
                    //Update items.
                    _.forEach(_this.items(), function (item) {
                        if (item.isEditableForSelectedActor) {
                            item.isChecked(checked);
                        }
                    });
                    //Update subcategories.
                    _.forEach(_this.subcategories(), function (category) {
                        category.isChecked(checked);
                    });
                },
                deferEvaluation: true
            }).extend({ rateLimit: { timeout: 20, method: 'notifyWhenChangesStop' } });
            this.nestingDepth = ko.pureComputed({
                read: function () {
                    if (_this.parent !== null) {
                        return _this.parent.nestingDepth() + 1;
                    }
                    return 1;
                },
                deferEvaluation: true
            });
            this.navCssClasses = ko.pureComputed({
                read: function () {
                    var classes = [];
                    if (_this.isSelected()) {
                        //classes.push('ame-selected-cat-nav-item');
                    }
                    if (_this.sortedSubcategories().length > 0) {
                        classes.push('ame-cat-nav-has-children');
                    }
                    if (_this.isExpanded()) {
                        classes.push('ame-cat-nav-is-expanded');
                    }
                    if (_this.isSelected()) {
                        classes.push('ame-selected-cat-nav-item');
                    }
                    classes.push('ame-cat-nav-level-' + _this.nestingDepth());
                    return classes.join(' ');
                },
                deferEvaluation: true
            });
            this.isNavVisible = ko.pureComputed({
                read: function () {
                    if (_this.parent === null) {
                        return true;
                    }
                    if (!_this.isStandardRenderingEnabled()) {
                        return false;
                    }
                    return _this.parent.isNavVisible() && _this.parent.isExpanded();
                },
                deferEvaluation: true
            });
            //Category labels are not searched, but the table view has categories that
            //represent the same item being used on multiple screens. In that case,
            //category labels typically match item labels, so let's highlight them.
            this.highlightedLabel = ko.pureComputed(function () {
                var text = _.escape(_this.label);
                if (filterState !== null) {
                    text = filterState.highlightSearchKeywords(text);
                }
                return text;
            });
            var wasRendered = ko.observable(false);
            this.shouldRenderContent = ko.computed({
                read: function () {
                    return wasRendered();
                },
                write: function (state) {
                    //This is a write-once flag. Once it's turned on,
                    //it can't be turned off again.
                    if (wasRendered()) {
                        return;
                    }
                    wasRendered(state);
                    //Notify that a category is being rendered. The DOM might not be
                    //updated yet when this event happens, so subscribers should wait
                    //at least until the next frame.
                    if (wasRendered()) {
                        jQuery(document).trigger('adminmenueditor:ehCategoryRendering', [_this]);
                    }
                }
            });
        }
        Category.prototype.toggle = function () {
            this.isExpanded(!this.isExpanded());
        };
        Category.prototype.enableTableView = function (rowCat, columnCat) {
            this.tableViewEnabled = true;
            this.tableView = new CategoryTableView(rowCat, columnCat);
            //Disable normal rendering for the two row/column categories.
            //They will only appear in the table.
            rowCat.isStandardRenderingEnabled(false);
            columnCat.isStandardRenderingEnabled(false);
        };
        Object.defineProperty(Category.prototype, "allParents", {
            get: function () {
                //The parent property is readonly, so the result should not change after
                //the category is initialized. We can cache it indefinitely.
                if (this.cachedParentList === null) {
                    var parents = [];
                    var current = this.parent;
                    while (current !== null) {
                        parents.push(current);
                        current = current.parent;
                    }
                    //Reverse the list so that it starts at the root.
                    parents.reverse();
                    this.cachedParentList = parents;
                }
                return this.cachedParentList;
            },
            enumerable: false,
            configurable: true
        });
        Category.fromProps = function (props, parent, filterState) {
            var _a, _b, _c, _d, _e;
            if (parent === void 0) { parent = null; }
            if (filterState === void 0) { filterState = null; }
            return new Category(props.id, props.label, parent, (_a = props.invertItemState) !== null && _a !== void 0 ? _a : false, filterState, (_b = props.sort) !== null && _b !== void 0 ? _b : SortOrder.SORT_ALPHA, (_c = props.itemSort) !== null && _c !== void 0 ? _c : SortOrder.SORT_INSERTION, (_d = props.priority) !== null && _d !== void 0 ? _d : Category.DEFAULT_PRIORITY, (_e = props.subtitle) !== null && _e !== void 0 ? _e : null);
        };
        Category.DEFAULT_PRIORITY = 10;
        Category.counter = 0;
        return Category;
    }());
    function isBinaryProps(props) {
        return ('binary' in props) ? props.binary : false;
    }
    var HideableItem = /** @class */ (function () {
        function HideableItem(id, label, categories, parent, initialEnabled, isInverted, component, tooltip, subtitle, selectedActorRef, allActorsRef, filterState) {
            var _this = this;
            if (categories === void 0) { categories = []; }
            if (parent === void 0) { parent = null; }
            if (initialEnabled === void 0) { initialEnabled = {}; }
            if (isInverted === void 0) { isInverted = false; }
            if (component === void 0) { component = null; }
            if (tooltip === void 0) { tooltip = null; }
            if (subtitle === void 0) { subtitle = null; }
            this.id = id;
            this.label = label;
            this.categories = categories;
            this.parent = parent;
            this.initialEnabled = initialEnabled;
            this.isInverted = isInverted;
            this.component = component;
            this.tooltip = tooltip;
            this.subtitle = subtitle;
            this.selectedActorRef = selectedActorRef;
            this.children = [];
            this.actorSettings = new AmeObservableActorSettings(initialEnabled);
            var _isIndeterminate = ko.observable(false);
            this.isIndeterminate = ko.pureComputed(function () {
                if (selectedActorRef() === null) {
                    return _isIndeterminate();
                }
                return false;
            });
            this.isChecked = this.createCheckedObservable(selectedActorRef, allActorsRef, _isIndeterminate);
            var wasRendered = false;
            this.shouldRender = ko.observable(false);
            this.isVisible = ko.computed(function () {
                var visible = filterState.itemMatchesFilter(_this);
                if (visible && !wasRendered) {
                    wasRendered = true;
                    _this.shouldRender(true);
                }
                return visible;
            });
            this.htmlLabel = ko.pureComputed(function () {
                var html = _.escape(_this.label);
                if (_this.isVisible()) {
                    html = filterState.highlightSearchKeywords(html);
                }
                return html;
            });
        }
        HideableItem.prototype.createCheckedObservable = function (selectedActorRef, allActorsRef, outIndeterminate) {
            var _this = this;
            return ko.computed({
                read: function () {
                    var enabled = _this.actorSettings.isEnabledFor(selectedActorRef(), allActorsRef(), _this.isInverted, _this.isInverted, _this.isInverted, outIndeterminate);
                    return _this.isInverted ? (!enabled) : enabled;
                },
                write: function (checked) {
                    _this.actorSettings.setEnabledFor(selectedActorRef(), _this.isInverted ? !checked : checked, allActorsRef(), _this.isInverted);
                    for (var i = 0; i < _this.children.length; i++) {
                        _this.children[i].isChecked(checked);
                    }
                },
                deferEvaluation: true
            });
        };
        HideableItem.fromJs = function (props, selectedActor, allActors, filterState, categories, parent) {
            var _a, _b, _c, _d, _e;
            if (categories === void 0) { categories = []; }
            if (parent === void 0) { parent = null; }
            if (isBinaryProps(props)) {
                return BinaryHideableItem.fromJs(props, selectedActor, allActors, filterState, categories, parent);
            }
            return new HideableItem(props.id, props.label, categories, parent, (_a = props.enabled) !== null && _a !== void 0 ? _a : {}, (_b = props.inverted) !== null && _b !== void 0 ? _b : false, (_c = props.component) !== null && _c !== void 0 ? _c : null, (_d = props.tooltip) !== null && _d !== void 0 ? _d : null, (_e = props.subtitle) !== null && _e !== void 0 ? _e : null, selectedActor, allActors, filterState);
        };
        HideableItem.prototype.toJs = function () {
            var result = {};
            if (this.isInverted) {
                result.inverted = true;
            }
            if ((this.component !== null) && (this.component !== '')) {
                result.component = this.component;
            }
            var enabled = this.actorSettings.getAll();
            if (!_.isEmpty(enabled)) {
                result.enabled = enabled;
            }
            return result;
        };
        Object.defineProperty(HideableItem.prototype, "isEditableForSelectedActor", {
            get: function () {
                return true;
            },
            enumerable: false,
            configurable: true
        });
        return HideableItem;
    }());
    var BinaryHideableItem = /** @class */ (function (_super) {
        __extends(BinaryHideableItem, _super);
        function BinaryHideableItem() {
            var _this = _super !== null && _super.apply(this, arguments) || this;
            _this.isEnabledForAll = ko.observable(false);
            return _this;
        }
        BinaryHideableItem.prototype.createCheckedObservable = function (selectedActorRef, allActorsRef, outIndeterminate) {
            var _this = this;
            var observable = ko.computed({
                read: function () {
                    if (_this.isInverted) {
                        return !_this.isEnabledForAll();
                    }
                    else {
                        return _this.isEnabledForAll();
                    }
                },
                write: function (value) {
                    if (_this.isEditableForSelectedActor) {
                        if (_this.isInverted) {
                            value = !value;
                        }
                        _this.isEnabledForAll(value);
                    }
                    else {
                        //Reset the checkbox to the original value.
                        observable.notifySubscribers();
                    }
                },
                deferEvaluation: true,
            });
            return observable;
        };
        BinaryHideableItem.fromJs = function (props, selectedActor, allActors, filterState, categories, parent) {
            var _a, _b, _c, _d;
            if (categories === void 0) { categories = []; }
            if (parent === void 0) { parent = null; }
            var instance = new BinaryHideableItem(props.id, props.label, categories, parent, {}, (_a = props.inverted) !== null && _a !== void 0 ? _a : false, (_b = props.component) !== null && _b !== void 0 ? _b : null, (_c = props.tooltip) !== null && _c !== void 0 ? _c : null, (_d = props.subtitle) !== null && _d !== void 0 ? _d : null, selectedActor, allActors, filterState);
            if (props.hasOwnProperty('enabledForAll')) {
                instance.isEnabledForAll(props.enabledForAll);
            }
            return instance;
        };
        BinaryHideableItem.prototype.toJs = function () {
            var result = _super.prototype.toJs.call(this);
            delete result.enabled;
            result.enabledForAll = this.isEnabledForAll();
            return result;
        };
        Object.defineProperty(BinaryHideableItem.prototype, "isEditableForSelectedActor", {
            get: function () {
                return (this.selectedActorRef() === null);
            },
            enumerable: false,
            configurable: true
        });
        return BinaryHideableItem;
    }(HideableItem));
    var CategoryTableView = /** @class */ (function () {
        function CategoryTableView(rowCategory, columnCategory) {
            this.rowCategory = rowCategory;
            this.columnCategory = columnCategory;
            this.itemLookup = {};
            this.columnHeaders = null;
        }
        Object.defineProperty(CategoryTableView.prototype, "rows", {
            get: function () {
                return CategoryTableView.sortCategoriesByLabel(this.rowCategory.subcategories);
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(CategoryTableView.prototype, "columns", {
            get: function () {
                return CategoryTableView.sortCategoriesByLabel(this.columnCategory.subcategories);
            },
            enumerable: false,
            configurable: true
        });
        CategoryTableView.sortCategoriesByLabel = function (categories) {
            var list = categories();
            list.sort(function (a, b) {
                return a.label.localeCompare(b.label);
            });
            return list;
        };
        CategoryTableView.prototype.getCellItems = function (row, column) {
            var path = [row.id, column.id];
            var items = _.get(this.itemLookup, path, null);
            if (items !== null) {
                return items;
            }
            //Find items that are present in both categories.
            items = _.intersection(row.directItems(), column.directItems());
            _.set(this.itemLookup, path, items);
            return items;
        };
        /**
         * Highlight the column heading when the user hovers over a table cell.
         *
         * @param unused Knockout provides the current model value to the callback, but we don't need it.
         * @param event JavaScript event object
         */
        CategoryTableView.prototype.onTableHover = function (unused, event) {
            if (!event || !event.target) {
                return;
            }
            var $cell = jQuery(event.target).closest('td, th');
            if ($cell.length < 1) {
                return;
            }
            if (
            //Has the header list been initialized?
            (this.columnHeaders === null)
                //The table might have been re-rendered or removed from the DOM.
                //In that case, we'll need to find the new header elements.
                || (this.columnHeaders.closest('body').length < 1)) {
                this.columnHeaders = $cell.closest('table').find('thead tr').first().find('th');
            }
            var index = $cell.index();
            //The first column doesn't have a header, so it doesn't need to be highlighted.
            if (index === 0) {
                return;
            }
            var $heading = this.columnHeaders.eq(index);
            if (!$heading || ($heading.length === 0)) {
                return;
            }
            var highlightClass = 'ame-eh-hovered-column';
            if ($heading.hasClass(highlightClass)) {
                return; //This column is already highlighted.
            }
            this.columnHeaders.removeClass(highlightClass);
            $heading.addClass(highlightClass);
        };
        return CategoryTableView;
    }());
    var FilterOptions = /** @class */ (function () {
        function FilterOptions() {
            var _this = this;
            /**
             * Bind this observable to the search box, then use searchQuery to actually
             * read the query. This is only public because it's used in a KO template.
             */
            this.internalSearchQuery = ko.observable('');
            this.searchQuery = ko.pureComputed(function () { return _this.internalSearchQuery(); });
            this.searchQuery.extend({ rateLimit: { timeout: 100, method: "notifyWhenChangesStop" } });
            this.searchKeywords = ko.pureComputed(function () {
                var query = _this.searchQuery().trim();
                if (query === '') {
                    return [];
                }
                return _(query.split(' '))
                    .map(function (keyword) { return keyword.trim(); })
                    .filter(function (keyword) { return (keyword !== ''); })
                    .value();
            });
            this.highlightRegex = ko.pureComputed(function () {
                var keywordList = _this.searchKeywords();
                if (keywordList.length < 1) {
                    return null;
                }
                var keywordGroup = _.map(keywordList, _.escapeRegExp).join('|');
                return new RegExp('(?:' + keywordGroup + ')', 'gi');
            });
        }
        FilterOptions.prototype.itemMatchesFilter = function (item) {
            var keywords = this.searchKeywords();
            if (keywords.length > 0) {
                var haystack_1 = item.label.toLowerCase();
                var matchesKeywords = _.all(keywords, function (keyword) { return (haystack_1.indexOf(keyword) >= 0); });
                if (!matchesKeywords) {
                    return false;
                }
            }
            return true;
        };
        FilterOptions.prototype.highlightSearchKeywords = function (input) {
            var regex = this.highlightRegex();
            if (regex === null) {
                return input;
            }
            return input.replace(regex, function (foundKeyword) {
                return '<mark class="ame-eh-search-highlight">' + foundKeyword + '</mark>';
            });
        };
        FilterOptions.prototype.clearSearchBox = function () {
            this.internalSearchQuery('');
        };
        FilterOptions.prototype.processEscKey = function (unusedKoModel, event) {
            //Ignore events triggered during IME composition.
            //See https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event#ignoring_keydown_during_ime_composition
            if (event.isComposing) {
                return true;
            }
            //noinspection JSDeprecatedSymbols
            var isEscape = (((typeof event['code'] !== 'undefined') && (event.code === 'Escape'))
                //IE doesn't support KeyboardEvent.code, so use keyCode instead.
                || ((typeof event['keyCode'] !== 'undefined') && (event.keyCode === 27)));
            if (isEscape) {
                this.clearSearchBox();
            }
            return true;
        };
        return FilterOptions;
    }());
    var Model = /** @class */ (function () {
        function Model(settings, prefs) {
            var _this = this;
            var _a, _b;
            this.categoryLookup = {};
            this.settingsData = ko.observable('');
            this.isSaveButtonEnabled = ko.observable(true);
            this.preferences = prefs.observableObject(ko);
            prefs.enableAutoSave(3000);
            this.actorSelector = new AmeActorSelector(AmeActors, true);
            this.selectedActor = this.actorSelector.createActorObservable(ko);
            this.selectedActorId = ko.pureComputed(function () {
                var actor = _this.selectedActor();
                if (actor === null) {
                    return '';
                }
                return actor.getId();
            });
            var allActors = ko.pureComputed(function () {
                return _this.actorSelector.getVisibleActors();
            });
            //Reselect the previously selected actor.
            if (settings.selectedActor && AmeActors.actorExists(settings.selectedActor)) {
                this.selectedActor(AmeActors.getActor(settings.selectedActor));
            }
            this.filterState = new FilterOptions();
            var _ = wsAmeLodash;
            //Initialize categories.
            this.rootCategory = new Category('_root', 'All', null, false, this.filterState, SortOrder.SORT_ALPHA);
            this.rootCategory.shouldRenderContent(true);
            var catsWithTableView = [];
            _.forEach(settings.categories, function (props) {
                var parent = _this.rootCategory;
                if (props.parent) {
                    parent = _this.categoryLookup[props.parent];
                }
                var cat = Category.fromProps(props, parent, _this.filterState);
                _this.categoryLookup[cat.id] = cat;
                parent.subcategories.push(cat);
                if (props.tableView) {
                    catsWithTableView.push(props);
                }
            });
            //Initialize table views. This is a separate step because tables need
            //their row and column categories to be already created.
            _.forEach(catsWithTableView, function (props) {
                var cat = _this.categoryLookup[props.id];
                cat.enableTableView(_this.categoryLookup[props.tableView.rowCategory], _this.categoryLookup[props.tableView.columnCategory]);
            });
            //Initialize items.
            var itemsById = {};
            _.forEach(settings.items, function (props) {
                var parent = null;
                if (props.parent && itemsById.hasOwnProperty(props.parent)) {
                    parent = itemsById[props.parent];
                }
                var categories = [];
                if (props.categories) {
                    _.forEach(props.categories, function (id) {
                        if (_this.categoryLookup.hasOwnProperty(id)) {
                            categories.push(_this.categoryLookup[id]);
                        }
                    });
                }
                if (categories.length < 1) {
                    categories.push(_this.rootCategory);
                }
                if (_.some(categories, 'invertItemState') && (typeof props['inverted'] === 'undefined')) {
                    props.inverted = true;
                }
                var item = HideableItem.fromJs(props, _this.selectedActor, allActors, _this.filterState, categories, parent);
                itemsById[item.id] = item;
                if (parent) {
                    parent.children.push(item);
                }
                _.forEach(categories, function (category) {
                    category.items.push(item);
                });
            });
            this.itemContainerClasses = ko.pureComputed(function () {
                var classes = [];
                var columns = _this.preferences.numberOfColumns();
                if (columns > 1) {
                    classes.push('ame-eh-item-columns-' + columns);
                }
                return classes.join(' ');
            });
            this.selectedCategory = ko.observable(this.rootCategory);
            //Update the "isSelected" and "containsSelectedCategory" flags
            //on the category object when the user selects a category.
            var previousSelectedCategory = this.selectedCategory.peek();
            if (previousSelectedCategory) {
                previousSelectedCategory.isSelected(true);
            }
            this.selectedCategory.subscribe(function (newSelection) {
                if (newSelection !== previousSelectedCategory) {
                    //Save the old selection in case changing isSelected also triggers this callback somehow.
                    var oldSelection = previousSelectedCategory;
                    previousSelectedCategory = newSelection;
                    //The previous category is no longer selected.
                    oldSelection.isSelected(false);
                    var previousTree = oldSelection.allParents;
                    var newTree = newSelection.allParents;
                    //Find the point of divergence.
                    var minLength = Math.min(previousTree.length, newTree.length);
                    var divergenceIndex = -1;
                    for (var i = 0; i < minLength; i++) {
                        if (newTree[i] !== previousTree[i]) {
                            divergenceIndex = i;
                            break;
                        }
                    }
                    //Update categories that are no longer in the selected tree.
                    if (divergenceIndex >= 0) {
                        for (var i = divergenceIndex; i < previousTree.length; i++) {
                            previousTree[i].containsSelectedCategory(false);
                        }
                    }
                    //Update categories that contain the new selection.
                    for (var i = Math.max(divergenceIndex, 0); i < newTree.length; i++) {
                        newTree[i].containsSelectedCategory(true);
                    }
                }
                newSelection.isSelected(true);
                newSelection.shouldRenderContent(true);
            });
            //Restore previously expanded categories.
            _.forEach((_a = this.preferences.csExpandedCategories()) === null || _a === void 0 ? void 0 : _a.split("\n"), function (id) {
                if ((typeof id === 'string') && _this.categoryLookup.hasOwnProperty(id)) {
                    _this.categoryLookup[id].isExpanded(true);
                }
            });
            //Save expanded categories in user preferences.
            var expandedCategories = ko.computed(function () {
                //Make a list of category IDs.
                return _(_this.categoryLookup).filter(function (category) {
                    //Skip the root category. It's always expanded.
                    if (category === _this.rootCategory) {
                        return false;
                    }
                    //Skip categories that don't have any children.
                    if (category.subcategories().length === 0) {
                        return false;
                    }
                    return category.isExpanded();
                }).pluck('id').value();
            }).extend({ rateLimit: { timeout: 100, method: 'notifyWhenChangesStop' } });
            expandedCategories.subscribe(function (newValue) {
                _this.preferences.csExpandedCategories(newValue.join("\n"));
            });
            //Reselect the previously selected category.
            if (settings.selectedCategory
                && this.categoryLookup.hasOwnProperty(settings.selectedCategory)) {
                this.selectedCategory(this.categoryLookup[settings.selectedCategory]);
            }
            //Render the first couple of categories to push the other category
            //placeholders below the bottom of the viewport.
            _(this.rootCategory.sortedSubcategories()).take(2).forEach(function (c) {
                c.shouldRenderContent(true);
            }).commit();
            //Always render the selected category.
            (_b = this.selectedCategory()) === null || _b === void 0 ? void 0 : _b.shouldRenderContent(true);
            this.selectedCategoryId = ko.pureComputed(function () {
                var category = _this.selectedCategory();
                if (category === null) {
                    return '';
                }
                return category.id;
            });
        }
        Model.prototype.onCategoryEntersViewport = function (element) {
            var category = ko.dataFor(element);
            if (category instanceof Category) {
                if (console && console.log) {
                    console.log('Rendering category', category.id);
                }
                category.shouldRenderContent(true);
            }
        };
        Model.prototype.renderAllCategories = function () {
            function renderChildren(category) {
                _.forEach(category.sortedSubcategories(), function (c) {
                    c.shouldRenderContent(true);
                    renderChildren(c);
                });
            }
            renderChildren(this.rootCategory);
        };
        Model.prototype.saveChanges = function () {
            this.isSaveButtonEnabled(false);
            this.settingsData(JSON.stringify(this.getCurrentSettings()));
            return true;
        };
        Model.prototype.getCurrentSettings = function () {
            function collectItemsRecursively(category, output) {
                if (output === void 0) { output = {}; }
                _.forEach(category.items(), function (item) {
                    if (!output.hasOwnProperty(item.id)) {
                        output[item.id] = item.toJs();
                    }
                });
                _.forEach(category.subcategories(), function (subcategory) { return collectItemsRecursively(subcategory, output); });
                return output;
            }
            return {
                items: collectItemsRecursively(this.rootCategory)
            };
        };
        return Model;
    }());
    AmeEasyHide.Model = Model;
})(AmeEasyHide || (AmeEasyHide = {}));
document.addEventListener('DOMContentLoaded', function () {
    ameEasyHideModel = new AmeEasyHide.Model(wsEasyHideData, ameEhUserPreferences);
    ko.applyBindings(ameEasyHideModel, document.getElementById('ame-easy-hide-container'));
    //Render categories lazily.
    try {
        var lazyUpdateTimer_1 = null;
        var ameEhLazyLoad_1 = new LazyLoad({
            elements_selector: '.ame-eh-lazy-category',
            unobserve_entered: true,
            callback_enter: function (element) {
                ameEasyHideModel.onCategoryEntersViewport(element);
            }
        });
        jQuery(document).on('adminmenueditor:ehCategoryRendering', function () {
            //New placeholders might be created after rendering a category,
            //so let's update LazyLoad.
            //Debounce updates by ensuring that there's only one pending timer.
            if (lazyUpdateTimer_1 !== null) {
                clearTimeout(lazyUpdateTimer_1);
            }
            lazyUpdateTimer_1 = window.setTimeout(function () {
                lazyUpdateTimer_1 = null;
                ameEhLazyLoad_1.update();
            }, 40);
        });
    }
    catch (ex) {
        //I'm not sure if LazyLoad will actually throw an exception if the user has
        //an old browser that doesn't support IntersectionObserver, but let's fall back
        //to showing all categories if anything goes wrong.
        ameEasyHideModel.renderAllCategories();
    }
    //Handle clicks on the "dismiss" button in the explanatory notice. It wouldn't be safe
    //to use Knockout for this because WordPress automatically moves notices below the first
    //h1/h2 element, and any external DOM manipulation can mess up KO bindings.
    jQuery('#ame-easy-hide-explanation').on('click', '.notice-dismiss', function () {
        if (typeof ameEhIsExplanationHidden !== 'undefined') {
            ameEhIsExplanationHidden(true);
            ameEhIsExplanationHidden.save();
        }
    });
    //We no longer need the input data, so we can potentially free up
    //some memory by clearing it.
    wsEasyHideData = null;
});
//# sourceMappingURL=easy-hide.js.map