"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Rendering
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeatureAppearanceProvider = exports.FeatureOverrides = exports.FeatureAppearance = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const FeatureTable_1 = require("./FeatureTable");
const GeometryParams_1 = require("./GeometryParams");
const RgbColor_1 = require("./RgbColor");
function copyIdSetToUint32Set(dst, src) {
    dst.clear();
    if (typeof src === "string") {
        dst.addId(src);
    }
    else {
        for (const id of src)
            dst.addId(id);
    }
}
function propsMatchDefaults(props) {
    return !props.rgb && !props.lineRgb
        && undefined === props.weight && undefined === props.linePixels
        && undefined === props.transparency && undefined === props.lineTransparency
        && !props.ignoresMaterial && !props.nonLocatable && !props.emphasized;
}
function equalRgb(a, b) {
    if (a === b) {
        return true;
    }
    else if (!a || !b) {
        return false;
    }
    else {
        return a.equals(b);
    }
}
function equalLineRgb(a, b) {
    if (a === b) {
        return true;
    }
    else if (a instanceof RgbColor_1.RgbColor && b instanceof RgbColor_1.RgbColor) {
        return equalRgb(a, b);
    }
    else {
        (0, core_bentley_1.assert)(a === undefined || a === false);
        (0, core_bentley_1.assert)(b === undefined || b === false);
        return false;
    }
}
function equalTransparency(a, b) {
    if (a === b) {
        return true;
    }
    else if (undefined === a || undefined === b) {
        return false;
    }
    else {
        return Math.floor(a * 0xff) === Math.floor(b * 0xff);
    }
}
function equalLineTransparency(a, b) {
    if (a === b) {
        return true;
    }
    else if (typeof a === "number" && typeof b === "number") {
        return equalTransparency(a, b);
    }
    else {
        (0, core_bentley_1.assert)(a === undefined || a === false);
        (0, core_bentley_1.assert)(b === undefined || b === false);
        return false;
    }
}
function transparencyFromJSON(transp) {
    if (undefined === transp) {
        return undefined;
    }
    transp = Math.max(0, Math.min(transp, 1));
    // Fix up rounding errors...
    const smallDelta = 0.0001;
    if (1.0 - transp < smallDelta) {
        transp = 1.0;
    }
    else if (transp < smallDelta) {
        transp = 0.0;
    }
    return transp;
}
/** Defines overrides for selected aspects of a [[Feature]]'s symbology.
 * Any member defined in the appearance overrides that aspect of symbology for all [[Feature]]s to which the appearance is applied.
 *
 * The [[rgb]] and [[transparency]] overrides, if defined, apply to all geometry by default.
 * However, the color and transparency of "linear" geometry can optionally be controlled independently from the rest of the geometry via [[lineRgb]] and [[lineTransparency]].
 * Linear geometry consists of any of the following:
 * - Curves and line strings;
 * - Points and point strings; and
 * - The outlines of planar regions.
 * The edges of 3d surfaces like spheres are not considered linear geometry.
 *
 * @see [[FeatureOverrides]] to customize the appearance of multiple features.
 * @public
 */
class FeatureAppearance {
    static fromJSON(props) {
        if (!props || propsMatchDefaults(props)) {
            return this.defaults;
        }
        return new FeatureAppearance(props);
    }
    /** Create a FeatureAppearance that overrides only the RGB color.
     * @note The transparency component of the ColorDef is ignored.
     */
    static fromRgb(color) {
        return new FeatureAppearance({ rgb: RgbColor_1.RgbColor.fromColorDef(color) });
    }
    /** Create a FeatureAppearance that overrides the RGB and transparency.
     * The appearance's transparency is derived from the transparency component of the ColorDef.
     */
    static fromRgba(color, viewDependentTransparency = false) {
        return new FeatureAppearance({
            rgb: RgbColor_1.RgbColor.fromColorDef(color),
            transparency: color.colors.t / 255,
            viewDependentTransparency: viewDependentTransparency ? true : undefined,
        });
    }
    /** Create a FeatureAppearance that overrides only the transparency */
    static fromTransparency(transparencyValue, viewDependent = false) {
        return new FeatureAppearance({
            transparency: transparencyValue,
            viewDependentTransparency: viewDependent ? true : undefined,
        });
    }
    /** Create a FeatureAppearance with overrides corresponding to those defined by the supplied SubCategoryOverride.
     * @note Subcategory overrides set [[viewDependentTransparency]] to `true`.
     */
    static fromSubCategoryOverride(ovr) {
        const rgb = undefined !== ovr.color ? RgbColor_1.RgbColor.fromColorDef(ovr.color) : undefined;
        const transparency = ovr.transparency;
        const weight = ovr.weight;
        const ignoresMaterial = undefined !== ovr.material && core_bentley_1.Id64.isValid(ovr.material) ? true : undefined;
        return this.fromJSON({ rgb, transparency, weight, ignoresMaterial, viewDependentTransparency: true });
    }
    /** Returns true if this appearance does not override any aspects of symbology. */
    get matchesDefaults() {
        return this.equals(FeatureAppearance.defaults);
    }
    get overridesRgb() { return undefined !== this.rgb; }
    get overridesTransparency() { return undefined !== this.transparency; }
    get overridesLinePixels() { return undefined !== this.linePixels; }
    get overridesWeight() { return undefined !== this.weight; }
    /** Get the color that will be applied to linear geometry, or undefined if not overridden.
     * This is the same as [[rgb]] if [[lineRgb]] is `undefined`.
     */
    getLineRgb() {
        return false !== this.lineRgb ? (this.lineRgb ?? this.rgb) : undefined;
    }
    /** Get the transparency that will be applied to linear geometry, or undefined if not overridden.
     * This is the same as [[transparency]] if [[lineTransparency]] is `undefined`.
     */
    getLineTransparency() {
        return false !== this.lineTransparency ? (this.lineTransparency ?? this.transparency) : undefined;
    }
    get overridesSymbology() {
        return this.overridesRgb || this.overridesTransparency || this.overridesWeight || this.overridesLinePixels || !!this.ignoresMaterial
            || this.emphasized || this.overridesNonLocatable
            || undefined !== this.getLineRgb() || undefined !== this.getLineTransparency();
    }
    get overridesNonLocatable() { return undefined !== this.nonLocatable; }
    get isFullyTransparent() {
        const surf = this.transparency ?? 0;
        const line = this.getLineTransparency() ?? 0;
        return surf >= 1 && line >= 1;
    }
    /** Returns true if any aspect of the appearance is overridden (i.e., if any member is not undefined). */
    get anyOverridden() { return this.overridesSymbology || this.overridesNonLocatable; }
    equals(other) {
        if (this === other)
            return true;
        return equalRgb(this.rgb, other.rgb)
            && this.weight === other.weight
            && equalTransparency(this.transparency, other.transparency)
            && this.linePixels === other.linePixels
            && this.ignoresMaterial === other.ignoresMaterial
            && this.nonLocatable === other.nonLocatable
            && this.emphasized === other.emphasized
            && this.viewDependentTransparency === other.viewDependentTransparency
            && equalLineTransparency(this.lineTransparency, other.lineTransparency)
            && equalLineRgb(this.lineRgb, other.lineRgb);
    }
    toJSON() {
        const props = {};
        if (this.rgb)
            props.rgb = this.rgb.toJSON();
        if (undefined !== this.weight)
            props.weight = this.weight;
        if (undefined !== this.transparency) {
            props.transparency = this.transparency;
            if (this.viewDependentTransparency)
                props.viewDependentTransparency = true;
        }
        if (undefined !== this.linePixels)
            props.linePixels = this.linePixels;
        if (true === this.ignoresMaterial)
            props.ignoresMaterial = true;
        if (true === this.nonLocatable)
            props.nonLocatable = true;
        if (true === this.emphasized)
            props.emphasized = true;
        if (undefined !== this.lineTransparency)
            props.lineTransparency = this.lineTransparency;
        if (this.lineRgb) {
            props.lineRgb = this.lineRgb;
            if (this.viewDependentTransparency)
                props.viewDependentTransparency = true;
        }
        return props;
    }
    /** Convert this appearance to JSON, and override any properties explicitly specified by `changedProps` in the result.
     * Example:
     * ```ts
     *  const base = FeatureAppearance.fromRgba(ColorDef.white); // transparency=0, rgb=white
     *  const clone = base.cloneProps({ transparency: undefined, weight: 5 }); // transparency=undefined, rgb=white, weight=5
     * ```
     * @see [[FeatureAppearance.clone]].
     */
    cloneProps(changedProps) {
        return {
            ...this.toJSON(),
            ...changedProps,
        };
    }
    /** Create a copy of this appearance, overriding any properties explicitly specified by `changedProps`.
     * Example:
     * ```ts
     *  const base = FeatureAppearance.fromRgba(ColorDef.white); // transparency=0, rgb=white
     *  const clone = base.clone({ transparency: undefined, weight: 5 }); // transparency=undefined, rgb=white, weight=5
     * ```
     * @see [[FeatureAppearance.cloneProps]].
     */
    clone(changedProps) {
        return FeatureAppearance.fromJSON(this.cloneProps(changedProps));
    }
    /** Produce a FeatureAppearance from the supplied appearance in which any aspect not defined by the base appearance is overridden by this appearance. */
    extendAppearance(base) {
        if (!this.overridesSymbology)
            return base;
        const props = base.toJSON();
        if (undefined === props.rgb)
            props.rgb = this.rgb;
        if (undefined === props.transparency)
            props.transparency = this.transparency;
        if (undefined === props.linePixels)
            props.linePixels = this.linePixels;
        if (undefined === props.weight)
            props.weight = this.weight;
        if (undefined === props.ignoresMaterial && this.ignoresMaterial)
            props.ignoresMaterial = true;
        if (undefined === props.nonLocatable && this.nonLocatable)
            props.nonLocatable = true;
        if (undefined === props.emphasized && this.emphasized)
            props.emphasized = true;
        if (!props.lineRgb)
            props.lineRgb = this.lineRgb;
        if (undefined === props.lineTransparency)
            props.lineTransparency = this.lineTransparency;
        if (this.viewDependentTransparency && (undefined !== props.transparency || undefined !== props.lineTransparency))
            props.viewDependentTransparency = true;
        return FeatureAppearance.fromJSON(props);
    }
    constructor(props) {
        this.rgb = undefined !== props.rgb ? RgbColor_1.RgbColor.fromJSON(props.rgb) : undefined;
        this.lineRgb = typeof props.lineRgb === "object" ? RgbColor_1.RgbColor.fromJSON(props.lineRgb) : (false === props.lineRgb ? false : undefined);
        this.transparency = transparencyFromJSON(props.transparency);
        this.lineTransparency = typeof props.lineTransparency === "number" ? transparencyFromJSON(props.lineTransparency) : (false === props.lineTransparency ? false : undefined);
        this.weight = props.weight;
        this.linePixels = props.linePixels;
        this.ignoresMaterial = props.ignoresMaterial;
        this.nonLocatable = props.nonLocatable;
        this.emphasized = props.emphasized;
        if (undefined !== this.weight) {
            this.weight = Math.max(1, Math.min(this.weight, 32));
        }
        if (props.viewDependentTransparency && (undefined !== this.transparency || undefined !== this.getLineTransparency())) {
            this.viewDependentTransparency = true;
        }
    }
}
exports.FeatureAppearance = FeatureAppearance;
/** An appearance that overrides nothing. */
FeatureAppearance.defaults = new FeatureAppearance({});
const scratchIgnoreAnimationOverridesArgs = {
    elementId: { lower: 0, upper: 0 },
    animationNodeId: 0,
};
/** Specifies how to customize the appearance of individual [[Feature]]s, typically within the context of a [Viewport]($frontend).
 * Individual aspects of a feature's appearance - like visibility, color, and transparency - are overridden by supplying a [[FeatureAppearance]].
 * Those overrides can be specified on the basis of the feature's model, element, and/or subcategory. A default set of overrides can also be specified to
 * apply to the appearance of any feature not otherwise overridden.
 *
 * It is possible to override multiple aspects of a feature on different bases. For example, you might specify that all features belonging to subcategory "A" should be drawn in red,
 * and that all features belonging to model "B" should be drawn 50% transparent. In this case, a feature belonging to both subcategory "A" and model "B" will be drawn as 50% transparent red -
 * the separate overrides are combined to produce the feature's overall appearance.
 *
 * In the case of conflicts, there is an order of precedence:
 *  - Model overrides take highest precedence.
 *  - Element overrides are of higher precedence than subcategory and animation overrides.
 *  - Overrides applied by a [[RenderSchedule.Script]]'s [[RenderSchedule.ElementTimeline]] are of higher precedence than subcategory overrides, but can be suppressed on a per-element basis via [[ignoreAnimationOverrides]].
 *  - Subcategory overrides have lowest precedence.
 *
 * For example, you might specify that all features belonging to subcategory "A" should be drawn in red, and all those belonging to model "B" should be drawn in green.
 * Then a feature belonging to subcategory "A" and model "B" will be drawn in green, because the model overrides take precedence.
 *
 * Instances of this class are not typically instantiated by an application directly; instead, an application can implement a [FeatureOverrideProvider]($frontend)
 * that augments the overrides supplied by a viewport.
 *
 * @see [FeatureSymbology.Overrides]($frontend) to create overrides specific to a [Viewport]($frontend) or [ViewState]($frontend).
 * @see [FeatureOverrideProvider]($frontend) to customize the appearance of features within a [Viewport]($frontend).
 * @public
 */
class FeatureOverrides {
    /** Accepts a criterion that determines whether color and transparency overrides originating from the view's [[RenderSchedule.Script]] should be ignored for a given element.
     * The function receives a description of the element in question and returns `true` if the script's overrides should be ignored.
     * Any number of such functions can be registered; if any one of them returns `true`, the script's overrides are not applied to the specified element.
     *
     * For example, applications commonly emphasize a set of elements by applying a [[FeatureAppearance.emphasized]] override to them, and specifying a highly-transparent
     * default appearance to de-emphasize the rest of the elements in the view. If some of the de-emphasized elements' appearances are also being overridden by the schedule script, then
     * they won't appear de-emphasized, making it difficult for the emphasized elements to stand out. In situations like this, [FeatureOverrideProvider]($frontend)s like [EmphasizeElements]($frontend) can register an [[IgnoreAnimationOverrides]] function that returns true if the element in question is not in the set of emphasized elements.
     */
    ignoreAnimationOverrides(ignore) {
        this._ignoreAnimationOverrides.push(ignore);
    }
    /** Overrides applied to features for which no other overrides are defined */
    get defaultOverrides() { return this._defaultOverrides; }
    /** Whether or not line weights are applied. If false, all lines are drawn with a weight of 1. */
    get lineWeights() { return this._lineWeights; }
    /** A set of elements that are always invisible.
     * @note If an element is present in both `alwaysDrawn` and [[neverDrawn]], it will not be displayed - `neverDrawn` takes precedence.
     */
    get neverDrawn() { return this._neverDrawn; }
    /** A set of elements that are unconditionally displayed.
     * @see [[isAlwaysDrawnExclusive]] to specify that *only* elements in this set will be displayed.
     * @note If an element is present in both `alwaysDrawn` and [[neverDrawn]], it will not be displayed - `neverDrawn` takes precedence.
     */
    get alwaysDrawn() { return this._alwaysDrawn; }
    /** @internal */
    isNeverDrawn(elemIdLo, elemIdHi, animationNodeId) {
        if (this._neverDrawn.has(elemIdLo, elemIdHi))
            return true;
        else
            return this.neverDrawnAnimationNodes.has(animationNodeId);
    }
    /** @internal */
    isAlwaysDrawn(idLo, idHi) { return this._alwaysDrawn.has(idLo, idHi); }
    /** Returns true if the [SubCategory]($backend) specified by Id is in the set of visible subcategories. @internal */
    isSubCategoryVisible(idLo, idHi) { return this._visibleSubCategories.has(idLo, idHi); }
    /** @internal */
    isSubCategoryVisibleInModel(subcatLo, subcatHi, modelLo, modelHi) {
        if (this.ignoreSubCategory)
            return true;
        let vis = this.isSubCategoryVisible(subcatLo, subcatHi);
        const modelOvr = this._modelSubCategoryOverrides.get(modelLo, modelHi);
        if (undefined !== modelOvr && modelOvr.has(subcatLo, subcatHi))
            vis = !vis;
        return vis;
    }
    /** @internal */
    getModelOverrides(idLo, idHi) {
        return this._modelOverrides.get(idLo, idHi);
    }
    getElementAnimationOverrides(idLo, idHi, animationNodeId) {
        if (this.animationNodeOverrides.size === 0)
            return undefined;
        // NB: An animation node Id of zero means "not animated". Some providers like EmphasizeElements may provide an appearance override for unanimated nodes.
        // That should be preserved.
        const app = this.animationNodeOverrides.get(animationNodeId);
        if (!app || 0 === animationNodeId || this._ignoreAnimationOverrides.length === 0)
            return app;
        const args = scratchIgnoreAnimationOverridesArgs;
        args.elementId.lower = idLo;
        args.elementId.upper = idHi;
        args.animationNodeId = animationNodeId;
        return this._ignoreAnimationOverrides.some((ignore) => ignore(args)) ? undefined : app;
    }
    /** @internal */
    getElementOverrides(idLo, idHi, animationNodeId) {
        const elemApp = this._elementOverrides.get(idLo, idHi);
        const nodeApp = this.getElementAnimationOverrides(idLo, idHi, animationNodeId);
        if (elemApp)
            return nodeApp ? nodeApp.extendAppearance(elemApp) : elemApp;
        return nodeApp;
    }
    /** @internal */
    getSubCategoryOverrides(idLo, idHi) { return this._subCategoryOverrides.get(idLo, idHi); }
    /** Add a [SubCategory]($backend) to the set of visible subcategories. */
    setVisibleSubCategory(id) { this._visibleSubCategories.addId(id); }
    /** Specify the Id of an element that should never be drawn. */
    setNeverDrawn(id) { this._neverDrawn.addId(id); }
    /** Specify the Id of an element that should always be drawn. */
    setAlwaysDrawn(id) { this._alwaysDrawn.addId(id); }
    /** Specify the Id of a animation node that should never be drawn. */
    setAnimationNodeNeverDrawn(id) { this.neverDrawnAnimationNodes.add(id); }
    /** Specify the Ids of elements that should never be drawn. */
    setNeverDrawnSet(ids) { copyIdSetToUint32Set(this._neverDrawn, ids); }
    /** Specify the Ids of elements that should always be drawn. */
    setAlwaysDrawnSet(ids, exclusive, ignoreSubCategory = true) {
        copyIdSetToUint32Set(this._alwaysDrawn, ids);
        this.isAlwaysDrawnExclusive = exclusive;
        this.alwaysDrawnIgnoresSubCategory = ignoreSubCategory;
    }
    /** Returns the feature's appearance overrides, or undefined if the feature is not visible. */
    getFeatureAppearance(feature, modelId, type = FeatureTable_1.BatchType.Primary, animationNodeId = 0) {
        return this.getAppearance(core_bentley_1.Id64.getLowerUint32(feature.elementId), core_bentley_1.Id64.getUpperUint32(feature.elementId), core_bentley_1.Id64.getLowerUint32(feature.subCategoryId), core_bentley_1.Id64.getUpperUint32(feature.subCategoryId), feature.geometryClass, core_bentley_1.Id64.getLowerUint32(modelId), core_bentley_1.Id64.getUpperUint32(modelId), type, animationNodeId);
    }
    /** Returns a feature's appearance overrides, or undefined if the feature is not visible.
     * Takes Id64s as pairs of unsigned 32-bit integers for efficiency, because that is how they are stored by the PackedFeatureTable associated with each batch of graphics.
     * @see [[getFeatureAppearance]] for an equivalent function that accepts [Id64String]($core-bentley)s instead of integer pairs.
     */
    getAppearance(elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) {
        if (FeatureTable_1.BatchType.VolumeClassifier === type || FeatureTable_1.BatchType.PlanarClassifier === type)
            return this.getClassifierAppearance(elemLo, elemHi, subcatLo, subcatHi, modelLo, modelHi, animationNodeId);
        let app = !this._lineWeights ? FeatureOverrides._weight1Appearance : FeatureAppearance.defaults;
        const modelApp = this.getModelOverrides(modelLo, modelHi);
        if (undefined !== modelApp)
            app = modelApp.extendAppearance(app);
        // Is the element visible?
        let elemApp, alwaysDrawn = false;
        if (core_bentley_1.Id64.isValidUint32Pair(elemLo, elemHi)) {
            if (this.isNeverDrawn(elemLo, elemHi, animationNodeId))
                return undefined;
            alwaysDrawn = this.isAlwaysDrawn(elemLo, elemHi);
            if (!alwaysDrawn && this.isAlwaysDrawnExclusive)
                return undefined;
            // Element overrides take precedence
            elemApp = this.getElementOverrides(elemLo, elemHi, animationNodeId);
            if (undefined !== elemApp)
                app = undefined !== modelApp ? elemApp.extendAppearance(app) : elemApp;
        }
        let subCatApp;
        if (!this.ignoreSubCategory && core_bentley_1.Id64.isValidUint32Pair(subcatLo, subcatHi)) {
            if ((!alwaysDrawn || !this.alwaysDrawnIgnoresSubCategory) && !this.isSubCategoryVisibleInModel(subcatLo, subcatHi, modelLo, modelHi))
                return undefined;
            subCatApp = this.getSubCategoryOverrides(subcatLo, subcatHi);
            if (undefined !== subCatApp)
                app = subCatApp.extendAppearance(app);
        }
        // Only apply default if *no* appearance was explicitly registered (doesn't matter if registered appearance does not actually override anything)
        if (undefined === elemApp && undefined === modelApp && undefined === subCatApp)
            app = this._defaultOverrides.extendAppearance(app);
        let visible = alwaysDrawn || this.isClassVisible(geomClass);
        if (visible && app.isFullyTransparent)
            visible = false; // don't bother rendering something with full transparency...
        return visible ? app : undefined;
    }
    /** Classifiers behave totally differently...in particular they are never invisible unless fully-transparent.
     * @internal
     */
    getClassifierAppearance(elemLo, elemHi, subcatLo, subcatHi, modelLo, modelHi, animationNodeId) {
        let app = FeatureAppearance.defaults;
        const modelApp = this.getModelOverrides(modelLo, modelHi);
        if (undefined !== modelApp)
            app = modelApp.extendAppearance(app);
        const elemApp = this.getElementOverrides(elemLo, elemHi, animationNodeId);
        if (undefined !== elemApp)
            app = undefined !== modelApp ? elemApp.extendAppearance(app) : elemApp;
        if (!this.ignoreSubCategory && core_bentley_1.Id64.isValidUint32Pair(subcatLo, subcatHi)) {
            const subCat = this.getSubCategoryOverrides(subcatLo, subcatHi);
            if (undefined !== subCat)
                app = subCat.extendAppearance(app);
        }
        if (undefined === elemApp && undefined === modelApp)
            app = this._defaultOverrides.extendAppearance(app);
        // NB: A fully-transparent classifier means the classifier is a clip mask - classified pixels will be discarded.
        return app;
    }
    /** Return whether geometry of the specified class should be drawn.
     * @see [[ViewFlags.constructions]], [[ViewFlags.dimensions]], and [[ViewFlags.patterns]].
     */
    isClassVisible(geomClass) {
        switch (geomClass) {
            case GeometryParams_1.GeometryClass.Construction: return this._constructions;
            case GeometryParams_1.GeometryClass.Dimension: return this._dimensions;
            case GeometryParams_1.GeometryClass.Pattern: return this._patterns;
            default: return true;
        }
    }
    /** Specify overrides for all elements belonging to a specified [GeometricModel]($backend), or all geometry belonging to a specified [GeometricElement]($backend) or [SubCategory]($backend). */
    override(args) {
        let id;
        let map;
        if (undefined !== args.elementId) {
            id = args.elementId;
            map = this._elementOverrides;
        }
        else if (undefined !== args.modelId) {
            id = args.modelId;
            map = this._modelOverrides;
        }
        else {
            id = args.subCategoryId;
            map = this._subCategoryOverrides;
        }
        let app = args.appearance;
        const idLo = core_bentley_1.Id64.getLowerUint32(id);
        const idHi = core_bentley_1.Id64.getUpperUint32(id);
        if (undefined !== args.elementId && this.isNeverDrawn(idLo, idHi, 0))
            return;
        const replace = "replace" === args.onConflict;
        const existing = replace ? undefined : map.get(idLo, idHi);
        if (existing) {
            (0, core_bentley_1.assert)("replace" !== args.onConflict);
            switch (args.onConflict) {
                case "skip":
                    return;
                case "extend":
                    app = app.extendAppearance(existing);
                    break;
                default:
                    app = existing.extendAppearance(app);
                    break;
            }
        }
        map.set(idLo, idHi, app);
    }
    /** Specify overrides for all elements within the specified model.
     * @param id The Id of the model.
     * @param app The symbology overrides.
     * @param replaceExisting Specifies whether to replace a pre-existing override for the same model.
     * @note These overrides take priority over all other overrides.
     * @note If [[defaultOverrides]] are defined, they will not apply to any element within this model, even if the supplied appearance overrides nothing.
     * @deprecated in 3.x. Use [[FeatureOverrides.override]].
     */
    overrideModel(id, app, replaceExisting = true) {
        this.override({ modelId: id, appearance: app, onConflict: replaceExisting ? "replace" : "skip" });
    }
    /** Specify overrides for all geometry belonging to the specified [SubCategory]($backend).
     * @param id The Id of the subcategory.
     * @param app The symbology overrides.
     * @param replaceExisting Specifies whether to replace a pre-existing override for the same subcategory.
     * @note These overrides have lower priority than element and model overrides.
     * @note If [[defaultOverrides]] are defined, they will not apply to any geometry within this subcategory, even if the supplied appearance overrides nothing.
     * @deprecated in 3.x. Use [[FeatureOverrides.override]].
     */
    overrideSubCategory(id, app, replaceExisting = true) {
        this.override({ subCategoryId: id, appearance: app, onConflict: replaceExisting ? "replace" : "skip" });
    }
    /** Specify overrides for all geometry originating from the specified element.
     * @param id The Id of the element.
     * @param app The symbology overrides.
     * @param replaceExisting Specifies whether to replace a pre-existing override for the same element.
     * @note These overrides take precedence over subcategory overrides, but not over model overrides.
     * @note If [[defaultOverrides]] are defined, they will not apply to this element, even if the supplied appearance overrides nothing.
     * @deprecated in 3.x. Use [[FeatureOverrides.override]].
     */
    overrideElement(id, app, replaceExisting = true) {
        this.override({ elementId: id, appearance: app, onConflict: replaceExisting ? "replace" : "skip" });
    }
    /** Specify overrides for all geometry originating from the specified animation node.
     * @param id The Id of the animation node.
     * @param app The symbology overrides.
     * @note These overrides do not take precedence over element overrides.
     */
    overrideAnimationNode(id, app) {
        this.animationNodeOverrides.set(id, app);
    }
    /** Defines a default appearance to be applied to any [[Feature]] *not* explicitly overridden.
     * @param appearance The symbology overrides.
     * @param replaceExisting Specifies whether to replace the current default overrides if they are already defined.
     */
    setDefaultOverrides(appearance, replaceExisting = true) {
        if (replaceExisting || !appearance.overridesSymbology)
            this._defaultOverrides = appearance;
    }
    /** Get the display priority of a subcategory. This is only relevant when using [[PlanProjectionSettings]].
     * @internal
     */
    getSubCategoryPriority(idLo, idHi) {
        return this._subCategoryPriorities.get(idLo, idHi) ?? 0;
    }
    /** Adds all fully transparent elements to the _neverDrawn set.  This is used for BatchedModels planar masks.
     * @internal
     */
    addInvisibleElementOverridesToNeverDrawn() {
        this._elementOverrides.forEach((lo, hi) => {
            const app = this.getElementOverrides(lo, hi, 0);
            if (app?.isFullyTransparent)
                this._neverDrawn.add(lo, hi);
        });
    }
    /** Construct a new Overrides that overrides nothing.
     * @see [FeatureSymbology.Overrides]($frontend) to construct overrides based on a [ViewState]($frontend) or [Viewport]($frontend).
     */
    constructor() {
        /** @internal */
        this._ignoreAnimationOverrides = [];
        /** Ids of elements that should never be drawn. This takes precedence over [[alwaysDrawn]]. @internal */
        this._neverDrawn = new core_bentley_1.Id64.Uint32Set();
        /** Ids of elements that should always be drawn. [[neverDrawn]] takes precedence over this set. @internal */
        this._alwaysDrawn = new core_bentley_1.Id64.Uint32Set();
        /** If true, no elements *except* those defined in the "always drawn" set will be drawn.
         * @see [[setAlwaysDrawn]]
         */
        this.isAlwaysDrawnExclusive = false;
        /** If true, the always-drawn elements are drawn even if their subcategories are not visible.
         * @see [[setAlwaysDrawn]]
         */
        this.alwaysDrawnIgnoresSubCategory = true;
        /** If true, all subcategories are considered visible. This is used for drawing sheets via section callouts in the absence of an actual sheet view.
         * @internal
         */
        this.ignoreSubCategory = false;
        /** Overrides applied to any feature not explicitly overridden. @internal */
        this._defaultOverrides = FeatureAppearance.defaults;
        /** Whether construction geometry should be drawn. @internal */
        this._constructions = false;
        /** Whether dimensions should be drawn. @internal */
        this._dimensions = false;
        /** Whether area patterns should be drawn. @internal */
        this._patterns = false;
        /** Whether line weights should be applied. If false, all lines are rendered 1-pixel wide. @internal */
        this._lineWeights = true;
        /** Overrides applied to all elements belonging to each model. @internal */
        this._modelOverrides = new core_bentley_1.Id64.Uint32Map();
        /** Overrides applied to specific elements. @internal */
        this._elementOverrides = new core_bentley_1.Id64.Uint32Map();
        /** Overrides applied to geometry belonging to each subcategory. @internal */
        this._subCategoryOverrides = new core_bentley_1.Id64.Uint32Map();
        /** The set of displayed subcategories. Geometry belonging to subcategories not included in this set will not be drawn. @internal */
        this._visibleSubCategories = new core_bentley_1.Id64.Uint32Set();
        /** Display priorities assigned to subcategories, possibly overridden by display style. Only applicable for plan projection models. @internal */
        this._subCategoryPriorities = new core_bentley_1.Id64.Uint32Map();
        /** Per-model, a set of subcategories whose visibility should be inverted for elements within that model.
         * Populated by Viewport.
         * @internal
         */
        this._modelSubCategoryOverrides = new core_bentley_1.Id64.Uint32Map();
        /** Ids of animation nodes that should never be drawn.
         * @internal
         */
        this.neverDrawnAnimationNodes = new Set();
        /** Mapping of animation node Ids to overrides applied to the corresponding animation nodes.
         * @internal
         */
        this.animationNodeOverrides = new Map();
        //
    }
    /** Returns true if geometry belonging to the specified subcategory will be drawn. */
    isSubCategoryIdVisible(id) { return this.isSubCategoryVisible(core_bentley_1.Id64.getLowerUint32(id), core_bentley_1.Id64.getUpperUint32(id)); }
    /** Returns the overrides applied to geometry belonging to the specified model, if any such are defined. */
    getModelOverridesById(id) { return this.getModelOverrides(core_bentley_1.Id64.getLowerUint32(id), core_bentley_1.Id64.getUpperUint32(id)); }
    /** Returns the overrides applied to geometry belonging to the specified element, if any such are defined. */
    getElementOverridesById(id) { return this.getElementOverrides(core_bentley_1.Id64.getLowerUint32(id), core_bentley_1.Id64.getUpperUint32(id), 0); }
    /** Returns the overrides applied to geometry belonging to the specified subcategory, if any such are defined. */
    getSubCategoryOverridesById(id) { return this.getSubCategoryOverrides(core_bentley_1.Id64.getLowerUint32(id), core_bentley_1.Id64.getUpperUint32(id)); }
    /** Returns true if the specified Feature will be drawn. */
    isFeatureVisible(feature) {
        const { elementId, subCategoryId, geometryClass } = feature;
        const isValidElemId = !core_bentley_1.Id64.isInvalid(elementId);
        const elemIdParts = isValidElemId ? core_bentley_1.Id64.getUint32Pair(elementId) : undefined;
        if (undefined !== elemIdParts && this.isNeverDrawn(elemIdParts.lower, elemIdParts.upper, 0))
            return false;
        const alwaysDrawn = undefined !== elemIdParts && this.isAlwaysDrawn(elemIdParts.lower, elemIdParts.upper);
        if (alwaysDrawn || this.isAlwaysDrawnExclusive)
            return alwaysDrawn;
        // NB: This ignores per-model subcategory visibility overrides, because caller did not specify a model.
        if (!this.isSubCategoryIdVisible(subCategoryId))
            return false;
        return this.isClassVisible(geometryClass);
    }
}
exports.FeatureOverrides = FeatureOverrides;
FeatureOverrides._weight1Appearance = FeatureAppearance.fromJSON({ weight: 1 });
/** @public */
var FeatureAppearanceProvider;
(function (FeatureAppearanceProvider) {
    /** Produce a FeatureAppearanceSource for which `getAppearance()` returns the appearance specified in `source`, potentially modified by `provider`. */
    function wrap(source, provider) {
        return {
            getAppearance: (elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
                return provider.getFeatureAppearance(source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
            },
        };
    }
    /** Create a provider that obtains each feature's appearance from the source, and if the feature is visible, modifies the appearance.
     * @param supplementAppearance A function accepting the feature's base appearance and returning a supplemental appearance.
     * @public
     */
    function supplement(supplementAppearance) {
        return {
            getFeatureAppearance: (source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
                const app = source.getAppearance(elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
                return app ? supplementAppearance(app) : app;
            },
        };
    }
    FeatureAppearanceProvider.supplement = supplement;
    /** Chain two FeatureAppearanceProviders together such that `first`'s `getFeatureAppearance` function is applied before `second`'s.
     * If `second` invokes `source.getAppearance()`, the returned appearance will include any modifications applied by `first`.
     * @public
     */
    function chain(first, second) {
        if (first === second)
            return first;
        return {
            getFeatureAppearance: (source, elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId) => {
                return second.getFeatureAppearance(wrap(source, first), elemLo, elemHi, subcatLo, subcatHi, geomClass, modelLo, modelHi, type, animationNodeId);
            },
        };
    }
    FeatureAppearanceProvider.chain = chain;
})(FeatureAppearanceProvider || (exports.FeatureAppearanceProvider = FeatureAppearanceProvider = {}));
//# sourceMappingURL=FeatureSymbology.js.map