"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.EmphasizeElements = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
/** An implementation of [[FeatureOverrideProvider]] for emphasizing selected elements through simple color/transparency appearance overrides.
 * @public
 * @extensions
 */
class EmphasizeElements {
    constructor() {
        this._emphasizedAppearance = core_common_1.FeatureAppearance.fromJSON({ emphasized: true });
        /** If true, all overridden and emphasized elements will also have the "emphasis" effect applied to them. This causes them to be hilited using the current [[Viewport.emphasisSettings]]. */
        this.wantEmphasis = false;
    }
    /** Establish active feature overrides to emphasize elements and apply color/transparency overrides.
     * @see [[Viewport.addFeatureOverrideProvider]]
     */
    addFeatureOverrides(overrides, vp) {
        const emphasizedElements = this.getEmphasizedElements(vp);
        if (undefined !== emphasizedElements) {
            overrides.setDefaultOverrides(this._defaultAppearance);
            const appearance = this.wantEmphasis ? this._emphasizedAppearance : core_common_1.FeatureAppearance.defaults;
            // Avoid creating a new object per-element inside the loop
            const args = { elementId: "", appearance };
            for (const elementId of emphasizedElements) {
                args.elementId = elementId;
                overrides.override(args);
            }
            // Do not apply animation overrides to de-emphasized elements.
            overrides.ignoreAnimationOverrides((args) => {
                const id = core_bentley_1.Id64.fromUint32Pair(args.elementId.lower, args.elementId.upper);
                return !emphasizedElements.has(id);
            });
        }
        const overriddenElements = this.getOverriddenElements();
        if (undefined !== overriddenElements) {
            if (undefined !== this._defaultAppearance)
                overrides.setDefaultOverrides(this._defaultAppearance);
            // Avoid creating a new object per-element inside the loop
            const args = { elementId: "", appearance: core_common_1.FeatureAppearance.defaults };
            for (const [key, ids] of overriddenElements) {
                args.appearance = this.createAppearanceFromKey(key);
                for (const elementId of ids) {
                    args.elementId = elementId;
                    overrides.override(args);
                }
            }
        }
        if (this._unanimatedAppearance) {
            if (this._unanimatedAppearance.isFullyTransparent)
                overrides.neverDrawnAnimationNodes.add(0);
            else
                overrides.animationNodeOverrides.set(0, this._unanimatedAppearance);
        }
    }
    /** @internal */
    createAppearanceFromKey(key) {
        let transparency;
        let rgb;
        if (key < 0) {
            transparency = Math.abs(key);
        }
        else {
            const color = core_common_1.ColorDef.fromJSON(key);
            rgb = core_common_1.RgbColor.fromColorDef(color);
            if (0 !== color.getAlpha()) // Fully transparent signifies to use color only...
                transparency = color.getTransparency() / 255;
        }
        const emphasized = this.wantEmphasis ? true : undefined;
        return core_common_1.FeatureAppearance.fromJSON({ rgb, transparency, emphasized });
    }
    /** Get override key from color and override type */
    createOverrideKey(color, override) {
        const colorValues = color.colors;
        switch (override) {
            case core_common_1.FeatureOverrideType.ColorAndAlpha:
                return 255 === colorValues.t ? undefined : color.tbgr; // Hiding elements should be done using neverDrawn, not transparency...
            case core_common_1.FeatureOverrideType.ColorOnly:
                return core_common_1.ColorDef.from(colorValues.r, colorValues.g, colorValues.b, 255).tbgr;
            case core_common_1.FeatureOverrideType.AlphaOnly:
                return -(colorValues.t / 255);
        }
    }
    /** Get color and override type for the given key. */
    getOverrideFromKey(key) {
        let overrideType;
        let color;
        if (key < 0) {
            color = core_common_1.ColorDef.from(0, 0, 0, 255 * Math.abs(key));
            overrideType = core_common_1.FeatureOverrideType.AlphaOnly;
        }
        else {
            color = core_common_1.ColorDef.fromJSON(key);
            if (0 === color.getAlpha()) {
                color = color.withAlpha(255);
                overrideType = core_common_1.FeatureOverrideType.ColorOnly;
            }
            else {
                overrideType = core_common_1.FeatureOverrideType.ColorAndAlpha;
            }
        }
        return { overrideType, color };
    }
    /** Establish a default appearance to apply to elements without overrides. If changing the default appearance
     * without also calling overrideElements, an explicit refresh must be requested for the change to take effect.
     * @see [[Viewport.setFeatureOverrideProviderChanged]]
     */
    get defaultAppearance() { return this._defaultAppearance; }
    set defaultAppearance(appearance) { this._defaultAppearance = appearance; }
    /** Establish a default appearance to apply to elements that are not animated by the view's [RenderSchedule.Script]($common).
     * @note If this is the only change made to EmphasizeElements, you must call [[Viewport.setFeatureOverrideProviderChanged]] for
     * the change to take immediate effect.
     * @see [[createDefaultAppearance]] to create an appearance suitable for de-emphasizing the non-animated elements.
     */
    get unanimatedAppearance() {
        return this._unanimatedAppearance;
    }
    set unanimatedAppearance(appearance) {
        this._unanimatedAppearance = appearance;
    }
    /** Create default appearance to use for emphasizeElements when not supplied by caller. */
    createDefaultAppearance() {
        return core_common_1.FeatureAppearance.fromJSON({
            rgb: new core_common_1.RgbColor(0xe4, 0xe4, 0xe4),
            transparency: 0.8,
            nonLocatable: true,
        });
    }
    /** Get the IDs of the currently never drawn elements. */
    getNeverDrawnElements(vp) {
        return (undefined !== vp.neverDrawn && 0 !== vp.neverDrawn.size ? vp.neverDrawn : undefined);
    }
    /** Get the IDs of the currently always drawn elements. */
    getAlwaysDrawnElements(vp) {
        return (undefined !== vp.alwaysDrawn && 0 !== vp.alwaysDrawn.size ? vp.alwaysDrawn : undefined);
    }
    /** Get the IDs of the currently hidden elements. */
    getHiddenElements(vp) {
        return this.getNeverDrawnElements(vp);
    }
    /** Get the IDs of the currently isolated elements. */
    getIsolatedElements(vp) {
        return (vp.isAlwaysDrawnExclusive ? this.getAlwaysDrawnElements(vp) : undefined);
    }
    /** Get the IDs of the currently emphasized isolated elements. */
    getEmphasizedIsolatedElements() {
        return (undefined !== this._defaultAppearance && undefined !== this._emphasizeIsolated && 0 !== this._emphasizeIsolated.size ? this._emphasizeIsolated : undefined);
    }
    /** Get the IDs of the currently emphasized elements. */
    getEmphasizedElements(vp) {
        return (undefined !== this.getEmphasizedIsolatedElements() ? this._emphasizeIsolated : (undefined !== this._defaultAppearance && !vp.isAlwaysDrawnExclusive ? this.getAlwaysDrawnElements(vp) : undefined));
    }
    /** Get the map of current elements with color/transparency overrides. */
    getOverriddenElements() {
        return (undefined !== this._overrideAppearance && 0 !== this._overrideAppearance.size ? this._overrideAppearance : undefined);
    }
    /** Get the IDs of current elements with the specified color/transparency override. */
    getOverriddenElementsByKey(key) {
        return (undefined !== this._overrideAppearance ? this._overrideAppearance.get(key) : undefined);
    }
    /** Clear never drawn elements.
     * @return false if nothing to clear.
     */
    clearNeverDrawnElements(vp) {
        if (undefined === this.getNeverDrawnElements(vp))
            return false;
        vp.clearNeverDrawn();
        return true;
    }
    /** Clear always drawn elements.
     * @return false if nothing to clear.
     */
    clearAlwaysDrawnElements(vp) {
        if (undefined === this.getAlwaysDrawnElements(vp))
            return false;
        vp.clearAlwaysDrawn();
        return true;
    }
    /** Clear hidden elements.
     * @return false if nothing to clear.
     */
    clearHiddenElements(vp) {
        return this.clearNeverDrawnElements(vp);
    }
    /** Clear isolated elements.
     * @return false if nothing to clear.
     */
    clearIsolatedElements(vp) {
        if (undefined === this.getIsolatedElements(vp))
            return false;
        if (this.clearEmphasizedIsolatedElements(vp, true))
            return true;
        return this.clearAlwaysDrawnElements(vp);
    }
    /** Clear emphasized elements.
     * @return false if nothing to clear.
     */
    clearEmphasizedElements(vp) {
        if (undefined === this.getEmphasizedElements(vp))
            return false;
        if (this.clearEmphasizedIsolatedElements(vp, false))
            return true;
        if (!this.clearAlwaysDrawnElements(vp))
            return false;
        this._defaultAppearance = undefined;
        return true;
    }
    /** Clear emphasized isolated elements.
     * @return false if nothing to clear.
     */
    clearEmphasizedIsolatedElements(vp, setToAlwaysDrawn) {
        const emphasizedIsolated = this.getEmphasizedIsolatedElements();
        this._emphasizeIsolated = undefined; // Always clear in case default appearance was unset...
        if (undefined === emphasizedIsolated)
            return false;
        if (setToAlwaysDrawn && this.setAlwaysDrawnElements(emphasizedIsolated, vp, false))
            return true;
        this._defaultAppearance = undefined;
        vp.setFeatureOverrideProviderChanged();
        return true;
    }
    /** Clear color/transparency overrides from elements. Removes all overrides when keyOrIds isn't supplied.
     * @param keyOrIds Specify a key value from [[EmphasizeElements.getOverriddenElements]] or [[EmphasizeElements.createOverrideKey]]
     * to remove a single color/transparency override for the corresponding elements or specify the IDs of elements to
     * remove any color/transparency override from.
     * @return false if nothing to clear.
     */
    clearOverriddenElements(vp, keyOrIds) {
        if (undefined === this._overrideAppearance)
            return false;
        if (undefined !== keyOrIds) {
            if (typeof keyOrIds === "number") {
                if (!this._overrideAppearance.delete(keyOrIds))
                    return false;
            }
            else {
                let changed = false;
                for (const [otherKey, otherIds] of this._overrideAppearance) {
                    const oldSize = otherIds.size;
                    for (const id of core_bentley_1.Id64.iterable(keyOrIds))
                        otherIds.delete(id);
                    if (oldSize !== otherIds.size)
                        changed = true;
                    if (0 === otherIds.size)
                        this._overrideAppearance.delete(otherKey);
                }
                if (!changed)
                    return false;
            }
        }
        else {
            this._overrideAppearance = undefined;
        }
        vp.setFeatureOverrideProviderChanged();
        return true;
    }
    /** @internal */
    updateIdSet(ids, replace, existingIds) {
        const newIds = new Set();
        for (const id of core_bentley_1.Id64.iterable(ids))
            newIds.add(id);
        if (0 === newIds.size)
            return undefined;
        const oldSize = (!replace && undefined !== existingIds ? existingIds.size : 0);
        if (0 !== oldSize && undefined !== existingIds)
            for (const id of existingIds)
                newIds.add(id);
        if (oldSize === newIds.size)
            return undefined;
        return newIds;
    }
    /** Set the element IDs to be never drawn.
     * @param ids The IDs of the elements to never draw.
     * @param vp The viewport.
     * @param replace true to replace currently hidden elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.neverDrawn]]
     * @internal
     */
    setNeverDrawnElements(ids, vp, replace = true) {
        const hiddenIds = this.updateIdSet(ids, replace, vp.neverDrawn);
        if (undefined === hiddenIds)
            return false;
        vp.setNeverDrawn(hiddenIds);
        return true;
    }
    /** Set the element IDs to be always drawn.
     * @param ids The IDs of the elements to always draw.
     * @param vp The viewport.
     * @param exclusive If true, *only* the specified elements will be drawn.
     * @param replace true to replace currently always drawn elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.alwaysDrawn]]
     * @see [[Viewport.isAlwaysDrawnExclusive]]
     * @internal
     */
    setAlwaysDrawnElements(ids, vp, exclusive = true, replace = true) {
        const visibleIds = this.updateIdSet(ids, replace, vp.alwaysDrawn);
        if (undefined === visibleIds)
            return false;
        vp.setAlwaysDrawn(visibleIds, exclusive);
        return true;
    }
    /** Set the element IDs to be never drawn.
     * @param ids The IDs of the elements to never draw.
     * @param vp The viewport.
     * @param replace true to replace currently hidden elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.neverDrawn]]
     */
    hideElements(ids, vp, replace = false) {
        return this.setNeverDrawnElements(ids, vp, replace);
    }
    /** Set the currently selected elements to be never drawn.
     * @param vp The viewport.
     * @param replace true to replace currently hidden elements (if any) or false to add to the existing set.
     * @param clearSelection true to clear current selection after setting appearance override, false to leave selected.
     * @return true if overrides were changed.
     * @see [[Viewport.neverDrawn]]
     */
    hideSelectedElements(vp, replace = false, clearSelection = true) {
        const selection = vp.view.iModel.selectionSet;
        if (!selection.isActive || !this.hideElements(selection.elements, vp, replace))
            return false;
        if (clearSelection)
            selection.emptyAll();
        return true;
    }
    /** Set the element IDs to be always drawn exclusively.
     * @param ids The IDs of the elements to always draw.
     * @param vp The viewport.
     * @param replace true to replace currently isolated elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.alwaysDrawn]]
     * @see [[Viewport.isAlwaysDrawnExclusive]]
     */
    isolateElements(ids, vp, replace = true) {
        const wasEmphasized = (undefined !== this.getEmphasizedElements(vp));
        if (!this.setAlwaysDrawnElements(ids, vp, true, replace))
            return false;
        if (wasEmphasized)
            this._defaultAppearance = this._emphasizeIsolated = undefined; // Don't clear defaultAppearance unless it was established by emphasize...
        return true;
    }
    /** Set the currently selected elements to be always drawn exclusively.
     * @param vp The viewport.
     * @param replace true to replace currently isolated elements (if any) or false to add to the existing set.
     * @param clearSelection true to clear current selection after setting appearance override, false to leave selected.
     * @return true if overrides were changed.
     * @see [[Viewport.alwaysDrawn]]
     * @see [[Viewport.isAlwaysDrawnExclusive]]
     */
    isolateSelectedElements(vp, replace = true, clearSelection = true) {
        const selection = vp.view.iModel.selectionSet;
        if (!selection.isActive || !this.isolateElements(selection.elements, vp, replace))
            return false;
        if (clearSelection)
            selection.emptyAll();
        return true;
    }
    /** Set the element IDs to be always drawn normally with all other elements in the view overridden to draw using a default appearance.
     * @param ids The IDs of the elements to always draw.
     * @param vp The viewport.
     * @param defaultAppearance Optional default appearance, uses non-locatable transparent grey if not specified.
     * @param replace true to replace currently overridden elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.alwaysDrawn]]
     * @see [[Viewport.isAlwaysDrawnExclusive]]
     */
    emphasizeElements(ids, vp, defaultAppearance, replace = true) {
        if (undefined !== this.getIsolatedElements(vp)) {
            const emphasizeIds = this.updateIdSet(ids, replace, this._emphasizeIsolated);
            if (undefined === emphasizeIds)
                return false;
            this._emphasizeIsolated = emphasizeIds;
            vp.setFeatureOverrideProviderChanged();
        }
        else {
            if (!this.setAlwaysDrawnElements(ids, vp, false, replace))
                return false;
            this._emphasizeIsolated = undefined;
        }
        this._defaultAppearance = (undefined === defaultAppearance ? this.createDefaultAppearance() : defaultAppearance);
        return true;
    }
    /** Set the currently selected elements to be always drawn normally with all other elements in the view overridden to draw using a default appearance.
     * @param vp The viewport.
     * @param defaultAppearance Optional default appearance, uses transparent grey if not specified.
     * @param replace true to replace currently overridden elements (if any) or false to add to the existing set.
     * @param clearSelection true to clear current selection after setting appearance override, false to leave selected.
     * @return true if overrides were changed.
     * @see [[Viewport.alwaysDrawn]]
     * @see [[Viewport.isAlwaysDrawnExclusive]]
     */
    emphasizeSelectedElements(vp, defaultAppearance, replace = true, clearSelection = true) {
        const selection = vp.view.iModel.selectionSet;
        if (!selection.isActive || !this.emphasizeElements(selection.elements, vp, defaultAppearance, replace))
            return false;
        if (clearSelection)
            selection.emptyAll();
        return true;
    }
    /** Set the element IDs to display with a color/transparency override.
     * @param ids The IDs of the elements.
     * @param vp The viewport.
     * @param color ColorDef to specify override rgb and alpha.
     * @param override Whether to use color and alpha, only color, or only alpha from the supplied ColorDef.
     * @param replace true to replace currently overridden elements (if any) or false to add to the existing set.
     * @return true if overrides were changed.
     * @see [[Viewport.addFeatureOverrideProvider]]
     */
    overrideElements(ids, vp, color, override = core_common_1.FeatureOverrideType.ColorOnly, replace = false) {
        const ovrKey = this.createOverrideKey(color, override);
        if (undefined === ovrKey)
            return false;
        const overrideIds = new Set();
        for (const id of core_bentley_1.Id64.iterable(ids))
            overrideIds.add(id);
        if (0 === overrideIds.size)
            return false;
        const existingIds = (!replace ? this.getOverriddenElementsByKey(ovrKey) : undefined);
        const oldSize = (undefined !== existingIds ? existingIds.size : 0);
        if (0 !== oldSize && undefined !== existingIds)
            for (const id of existingIds)
                overrideIds.add(id);
        if (oldSize === overrideIds.size)
            return false;
        if (undefined === this._overrideAppearance) {
            this._overrideAppearance = new Map();
        }
        else {
            for (const [key, otherIds] of this._overrideAppearance) {
                if (key === ovrKey) // Make sure these ids are unique to this color/transparency key...
                    continue;
                for (const id of core_bentley_1.Id64.iterable(ids))
                    otherIds.delete(id);
                if (0 !== otherIds.size)
                    continue;
                this._overrideAppearance.delete(key);
            }
        }
        this._overrideAppearance.set(ovrKey, overrideIds);
        vp.setFeatureOverrideProviderChanged();
        return true;
    }
    /** Set the currently selected elements to display with a color/transparency override.
     * @param vp The viewport.
     * @param color ColorDef to specify override rgb and alpha.
     * @param override Whether to use color and alpha, only color, or only alpha from the supplied ColorDef.
     * @param replace true to replace currently overridden elements (if any) or false to add to the existing set.
     * @param clearSelection true to clear current selection after setting appearance override, false to leave selected.
     * @return true if overrides were changed.
     * @see [[Viewport.addFeatureOverrideProvider]]
     */
    overrideSelectedElements(vp, color, override = core_common_1.FeatureOverrideType.ColorOnly, replace = false, clearSelection = true) {
        const selection = vp.view.iModel.selectionSet;
        if (!selection.isActive || !this.overrideElements(selection.elements, vp, color, override, replace))
            return false;
        if (clearSelection)
            selection.emptyAll();
        return true;
    }
    /** @return true if provider is currently overriding the display of any elements. */
    isActive(vp) { return (undefined !== this.getNeverDrawnElements(vp) || undefined !== this.getAlwaysDrawnElements(vp) || undefined !== this.getOverriddenElements()); }
    /** Serialize to JSON representation.
     * @see [[EmphasizeElements.fromJSON]]
     */
    toJSON(vp) {
        const props = {};
        const neverDrawn = this.getNeverDrawnElements(vp);
        if (undefined !== neverDrawn)
            props.neverDrawn = [...neverDrawn];
        const alwaysDrawn = this.getAlwaysDrawnElements(vp);
        if (undefined !== alwaysDrawn)
            props.alwaysDrawn = [...alwaysDrawn];
        if (vp.isAlwaysDrawnExclusive)
            props.isAlwaysDrawnExclusive = true; // isolate
        const alwaysDrawnExclusiveEmphasized = this.getEmphasizedIsolatedElements();
        if (undefined !== alwaysDrawnExclusiveEmphasized)
            props.alwaysDrawnExclusiveEmphasized = [...alwaysDrawnExclusiveEmphasized];
        if (undefined !== this.defaultAppearance)
            props.defaultAppearance = this.defaultAppearance; // emphasize (or specifically set for override)
        if (this.unanimatedAppearance)
            props.unanimatedAppearance = this.unanimatedAppearance;
        const overriddenElements = this.getOverriddenElements();
        if (undefined !== overriddenElements) {
            const appearanceOverride = [];
            for (const [key, ovrIds] of overriddenElements) {
                const { color, overrideType } = { ...this.getOverrideFromKey(key) };
                const ids = [...ovrIds];
                appearanceOverride.push({ overrideType, color: color.toJSON(), ids });
            }
            props.appearanceOverride = appearanceOverride;
        }
        if (this.wantEmphasis)
            props.wantEmphasis = true;
        return props;
    }
    /** Initialize from JSON representation.
     * @see [[EmphasizeElements.toJSON]]
     */
    fromJSON(props, vp) {
        let changed = false;
        if (undefined !== props.neverDrawn && this.setNeverDrawnElements(new Set(props.neverDrawn), vp, true))
            changed = true;
        if (undefined !== props.alwaysDrawn && this.setAlwaysDrawnElements(new Set(props.alwaysDrawn), vp, undefined !== props.isAlwaysDrawnExclusive && props.isAlwaysDrawnExclusive))
            changed = true;
        if (undefined !== props.alwaysDrawnExclusiveEmphasized)
            this._emphasizeIsolated = new Set(props.alwaysDrawnExclusiveEmphasized); // changed status determined by setAlwaysDrawnElements...
        if (undefined !== props.defaultAppearance)
            this.defaultAppearance = core_common_1.FeatureAppearance.fromJSON(props.defaultAppearance); // changed status determined by setAlwaysDrawnElements or overrideElements...
        if (props.unanimatedAppearance)
            this.unanimatedAppearance = core_common_1.FeatureAppearance.fromJSON(props.unanimatedAppearance);
        if (undefined !== props.appearanceOverride) {
            for (const ovrApp of props.appearanceOverride) {
                if (undefined === ovrApp.ids)
                    continue;
                if (this.overrideElements(new Set(ovrApp.ids), vp, core_common_1.ColorDef.fromJSON(ovrApp.color), ovrApp.overrideType, true))
                    changed = true;
            }
        }
        const wantEmphasis = true === props.wantEmphasis;
        if (wantEmphasis !== this.wantEmphasis) {
            this.wantEmphasis = wantEmphasis;
            changed = true;
        }
        return changed;
    }
    /** Return the EmphasizeElements provider currently registered with the specified Viewport, if one is already registered. */
    static get(vp) {
        return vp.findFeatureOverrideProviderOfType(EmphasizeElements);
    }
    /** Return the EmphasizeElements provider currently registered with the specified Viewport, or register a new one and return it. */
    static getOrCreate(vp) {
        let provider = this.get(vp);
        if (!provider) {
            provider = new EmphasizeElements();
            vp.addFeatureOverrideProvider(provider);
        }
        return provider;
    }
    /** Drop the EmphasizeElements provider currently registered with the specified Viewport, if any is registered. */
    static clear(vp, inactiveOnly = false) {
        const provider = this.get(vp);
        if (undefined === provider || (inactiveOnly && provider.isActive(vp)))
            return;
        vp.clearNeverDrawn();
        vp.clearAlwaysDrawn();
        vp.dropFeatureOverrideProvider(provider);
    }
}
exports.EmphasizeElements = EmphasizeElements;
//# sourceMappingURL=EmphasizeElements.js.map