/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Views
 */
import { Id64 } from "@itwin/core-bentley";
/** Per-model category visibility permits the visibility of categories within a [[Viewport]] displaying a [[SpatialViewState]] to be overridden in
 * the context of individual [[GeometricModelState]]s.
 * If a category's visibility is overridden for a given model, then elements belonging to that category within that model will be displayed or hidden regardless of the category's inclusion in the Viewport's [[CategorySelectorState]].
 * The override affects geometry on all subcategories belonging to the overridden category. That is, if the category is overridden to be visible, then geometry on all subcategories of the category
 * will be visible, regardless of any [SubCategoryOverride]($common)s applied by the view's [[DisplayStyleState]].
 * @see [[Viewport.perModelCategoryVisibility]] to define the per-model category visibility for a viewport.
 * @public
 * @extensions
 */
export var PerModelCategoryVisibility;
(function (PerModelCategoryVisibility) {
    /** Describes whether and how a category's visibility is overridden. */
    let Override;
    (function (Override) {
        /** The category's visibility is not overridden; its visibility is wholly controlled by the [[Viewport]]'s [[CategorySelectorState]]. */
        Override[Override["None"] = 0] = "None";
        /** The category is overridden to be visible. */
        Override[Override["Show"] = 1] = "Show";
        /** The category is overridden to be invisible. */
        Override[Override["Hide"] = 2] = "Hide";
    })(Override = PerModelCategoryVisibility.Override || (PerModelCategoryVisibility.Override = {}));
    function createOverrides(viewport) {
        return new PerModelCategoryVisibilityOverrides(viewport);
    }
    PerModelCategoryVisibility.createOverrides = createOverrides;
})(PerModelCategoryVisibility || (PerModelCategoryVisibility = {}));
/** The Viewport-specific implementation of PerModelCategoryVisibility.Overrides. */
class PerModelCategoryVisibilityOverrides {
    _map = new Map();
    /** Flat set of all override entries, providing O(1) iteration via [Symbol.iterator]. Kept in sync with `_map`. */
    _set = new Set();
    _vp;
    constructor(vp) {
        this._vp = vp;
    }
    [Symbol.iterator]() {
        return this._set[Symbol.iterator]();
    }
    getOverride(modelId, categoryId) {
        const ovr = this._map.get(modelId)?.get(categoryId);
        if (undefined !== ovr)
            return ovr.visible ? PerModelCategoryVisibility.Override.Show : PerModelCategoryVisibility.Override.Hide;
        else
            return PerModelCategoryVisibility.Override.None;
    }
    getModelEntry({ modelId, override }) {
        let modelEntry = this._map.get(modelId);
        if (modelEntry) {
            return modelEntry;
        }
        if (override === PerModelCategoryVisibility.Override.None) {
            return undefined;
        }
        modelEntry = new Map();
        this._map.set(modelId, modelEntry);
        return modelEntry;
    }
    addOrRemoveOverrideEntry({ modelEntry, categoryId, modelId, override }) {
        const overrideEntry = modelEntry.get(categoryId);
        if (override === PerModelCategoryVisibility.Override.None) {
            if (overrideEntry === undefined) {
                return false;
            }
            this._set.delete(overrideEntry);
            modelEntry.delete(categoryId);
            return true;
        }
        const visible = override === PerModelCategoryVisibility.Override.Show;
        if (overrideEntry === undefined) {
            const ovr = { modelId, categoryId, visible };
            modelEntry.set(categoryId, ovr);
            this._set.add(ovr);
            return true;
        }
        if (overrideEntry.visible !== visible) {
            overrideEntry.visible = visible;
            return true;
        }
        return false;
    }
    applyOverride(modelId, categoryIds, override, catIdsToLoad) {
        const modelEntry = this.getModelEntry({ modelId, override });
        let changed = false;
        if (!modelEntry)
            return changed;
        for (const categoryId of categoryIds) {
            if (this.addOrRemoveOverrideEntry({ modelEntry, categoryId, modelId, override })) {
                changed = true;
                if (catIdsToLoad && override !== PerModelCategoryVisibility.Override.None)
                    catIdsToLoad.push(categoryId);
            }
        }
        if (modelEntry.size === 0) {
            this._map.delete(modelId);
        }
        return changed;
    }
    /**
     * set the overrides for multiple perModelCategoryVisibility props, loading categoryIds from the iModel if necessary.
     * @see [[PerModelCategoryVisibility]]
     * @param perModelCategoryVisibility array of model category visibility overrides @see [[PerModelCategoryVisibility.Props]]
     * @param iModel Optional param iModel. If no iModel is provided, then the iModel associated with the viewport (used to construct this class) is used.
     * This optional iModel param is useful for apps which may show multiple iModels at once. Passing in an iModel ensures that the subcategories cache for the provided iModel
     * is populated as opposed to the iModel associated with the viewport which may or may not be an empty iModel.
     * @returns a promise that resolves once the overrides have been applied.
     */
    async setOverrides(perModelCategoryVisibility, iModel) {
        let anyChanged = false;
        const catIdsToLoad = [];
        const iModelToUse = iModel ? iModel : this._vp.iModel;
        for (const override of perModelCategoryVisibility) {
            // The caller may pass a single categoryId as a string, if we don't convert this to an array we will iterate
            // over each individual character of that string, which is not the desired behavior.
            const categoryIds = typeof override.categoryIds === "string" ? [override.categoryIds] : override.categoryIds;
            if (this.applyOverride(override.modelId, categoryIds, override.visOverride, catIdsToLoad))
                anyChanged = true;
        }
        if (anyChanged) {
            this._vp.setViewedCategoriesPerModelChanged();
            if (catIdsToLoad.length !== 0) {
                this._vp.subcategories.push(iModelToUse.subcategories, catIdsToLoad, () => this._vp.setViewedCategoriesPerModelChanged());
            }
        }
    }
    setOverride(modelIds, categoryIds, override) {
        const categoryIdIterable = Id64.iterable(categoryIds);
        let changed = false;
        for (const modelId of Id64.iterable(modelIds)) {
            if (this.applyOverride(modelId, categoryIdIterable, override))
                changed = true;
        }
        if (changed) {
            this._vp.setViewedCategoriesPerModelChanged();
            if (override !== PerModelCategoryVisibility.Override.None) {
                // Ensure subcategories loaded.
                this._vp.subcategories.push(this._vp.iModel.subcategories, categoryIds, () => this._vp.setViewedCategoriesPerModelChanged());
            }
        }
    }
    clearOverrides(modelIds) {
        if (undefined === modelIds) {
            if (this._map.size > 0) {
                this._map.clear();
                this._set.clear();
                this._vp.setViewedCategoriesPerModelChanged();
            }
            return;
        }
        let changed = false;
        for (const modelId of Id64.iterable(modelIds)) {
            const modelEntry = this._map.get(modelId);
            if (!modelEntry) {
                continue;
            }
            changed = true;
            for (const overrideEntry of modelEntry.values()) {
                this._set.delete(overrideEntry);
            }
            this._map.delete(modelId);
        }
        if (changed) {
            this._vp.setViewedCategoriesPerModelChanged();
        }
    }
    addOverrides(fs, ovrs) {
        const cache = this._vp.iModel.subcategories;
        for (const ovr of this._set) {
            const subcats = cache.getSubCategories(ovr.categoryId);
            if (undefined === subcats)
                continue;
            // It's pointless to override for models which aren't displayed...except if we do this, and then someone enables that model,
            // we would need to regenerate our symbology overrides in response. Preferably people wouldn't bother overriding models that
            // they don't want us to draw...
            /* if (!this._vp.view.viewsModel(ovr.modelId))
              continue; */
            // ###TODO: Avoid recomputing upper and lower portions of modelId if modelId repeated.
            // Also avoid computing if no effective overrides.
            const modelLo = Id64.getLowerUint32(ovr.modelId);
            const modelHi = Id64.getUpperUint32(ovr.modelId);
            for (const subcat of subcats) {
                const subcatLo = Id64.getLowerUint32(subcat);
                const subcatHi = Id64.getUpperUint32(subcat);
                const vis = fs.isSubCategoryVisible(subcatLo, subcatHi);
                if (vis !== ovr.visible) {
                    // Only care if visibility differs from that defined for entire view
                    let entry = ovrs.get(modelLo, modelHi);
                    if (undefined === entry) {
                        entry = new Id64.Uint32Set();
                        ovrs.set(modelLo, modelHi, entry);
                    }
                    entry.add(subcatLo, subcatHi);
                }
            }
        }
    }
}
//# sourceMappingURL=PerModelCategoryVisibility.js.map