"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 DisplayStyles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisplayStyle3dSettings = exports.DisplayStyleSettings = exports.MonochromeMode = void 0;
// cspell:ignore greyscale ovrs
const core_bentley_1 = require("@itwin/core-bentley");
const AmbientOcclusion_1 = require("./AmbientOcclusion");
const AnalysisStyle_1 = require("./AnalysisStyle");
const BackgroundMapSettings_1 = require("./BackgroundMapSettings");
const ClipStyle_1 = require("./ClipStyle");
const ColorDef_1 = require("./ColorDef");
const HiddenLine_1 = require("./HiddenLine");
const FeatureSymbology_1 = require("./FeatureSymbology");
const PlanarClipMask_1 = require("./PlanarClipMask");
const SubCategoryOverride_1 = require("./SubCategoryOverride");
const LightSettings_1 = require("./LightSettings");
const MapImagerySettings_1 = require("./MapImagerySettings");
const PlanProjectionSettings_1 = require("./PlanProjectionSettings");
const Environment_1 = require("./Environment");
const SolarShadows_1 = require("./SolarShadows");
const ThematicDisplay_1 = require("./ThematicDisplay");
const ViewFlags_1 = require("./ViewFlags");
const Cartographic_1 = require("./geometry/Cartographic");
const IModel_1 = require("./IModel");
const SolarCalculate_1 = require("./SolarCalculate");
const ContextRealityModel_1 = require("./ContextRealityModel");
const RealityModelDisplaySettings_1 = require("./RealityModelDisplaySettings");
const WhiteOnWhiteReversalSettings_1 = require("./WhiteOnWhiteReversalSettings");
const ContourDisplay_1 = require("./ContourDisplay");
/** Describes the style in which monochrome color is applied by a [[DisplayStyleSettings]].
 * @public
 * @extensions
 */
var MonochromeMode;
(function (MonochromeMode) {
    /** The color of the geometry is replaced with the monochrome color. e.g., if monochrome color is white, the geometry will be white. */
    MonochromeMode[MonochromeMode["Flat"] = 0] = "Flat";
    /** The color of surfaces is computed as normal, then scaled to a shade of the monochrome color based on the surface color's intensity.
     * For example, if the monochrome color is white, this results in a greyscale effect.
     * Geometry other than surfaces is treated the same as [[MonochromeMode.Flat]].
     */
    MonochromeMode[MonochromeMode["Scaled"] = 1] = "Scaled";
})(MonochromeMode || (exports.MonochromeMode = MonochromeMode = {}));
/** DisplayStyleSettings initially persisted its excluded elements as an array of Id64Strings in JSON, and exposed them as a Set<string>.
 * This becomes problematic when these arrays become very large, in terms of the amount of data and the time required to convert them to a Set.
 * The Ids are now persisted to JSON as a [[CompressedId64Set]], significantly reducing their size. However, for backwards API compatibility we must
 * continue to expose [[DisplayStyleSettings.excludedElements]] as a Set<string>. The [[ExcludedElements]] class tries to minimize the impact of that requirement by
 * maintaining the Ids primarily as a [[MutableCompressedId64Set]], only allocating the Set<string> if a caller actually requests it.
 * The only operation Set provides more efficiently than MutableCompressedId64Set is checking for the presence of an Id (the `has()` method).
 * @internal
 */
class ExcludedElements {
    _json;
    _ids;
    _synchronizing = false;
    constructor(json) {
        this._json = json;
        if (Array.isArray(json.excludedElements))
            this._ids = new core_bentley_1.MutableCompressedId64Set(core_bentley_1.CompressedId64Set.compressIds(core_bentley_1.OrderedId64Iterable.sortArray(json.excludedElements)));
        else
            this._ids = new core_bentley_1.MutableCompressedId64Set(json.excludedElements);
    }
    reset(ids) {
        this.synchronize(() => {
            this._ids.reset((ids && "string" !== typeof ids) ? core_bentley_1.CompressedId64Set.compressIds(ids) : ids);
        });
    }
    get ids() {
        return this._ids.ids;
    }
    add(ids) {
        this.synchronize(() => {
            for (const id of ids)
                this._ids.add(id);
        });
    }
    delete(ids) {
        this.synchronize(() => {
            for (const id of ids)
                this._ids.delete(id);
        });
    }
    [Symbol.iterator]() {
        return this._ids[Symbol.iterator]();
    }
    /** The JSON must be kept up-to-date at all times. */
    synchronize(func) {
        if (this._synchronizing)
            return;
        this._synchronizing = true;
        try {
            func();
        }
        finally {
            this._synchronizing = false;
            const ids = this._ids.ids;
            if (0 === ids.length)
                delete this._json.excludedElements;
            else
                this._json.excludedElements = ids;
        }
    }
}
/** An implementation of Map that is based on a JSON array, used for a display styles subcategory overrides, model appearance overrides,
 * and planar clip masks. Ensures:
 *  - JSON representation kept in sync with changes to map; and
 *  - Events dispatched when map contents change.
 */
class OverridesMap extends Map {
    _json;
    _arrayKey;
    _event;
    _idFromProps;
    _overrideToProps;
    _overrideFromProps;
    // This is required for mock framework used by ui libraries, which otherwise try to clone this as a standard Map.
    get [Symbol.toStringTag]() { return "OverridesMap"; }
    /** Maps Id64String to its index in the JSON props array, avoiding O(n) scans in set/delete. */
    #indexById = new Map();
    constructor(_json, _arrayKey, _event, _idFromProps, _overrideToProps, _overrideFromProps) {
        super();
        this._json = _json;
        this._arrayKey = _arrayKey;
        this._event = _event;
        this._idFromProps = _idFromProps;
        this._overrideToProps = _overrideToProps;
        this._overrideFromProps = _overrideFromProps;
        this.populate();
    }
    set(id, override) {
        this._event.raiseEvent(id, override);
        super.set(id, override);
        const index = this.findOrAllocateIndex(id);
        const array = this._array;
        (0, core_bentley_1.assert)(undefined !== array);
        array[index] = this._overrideToProps(override, id);
        return this;
    }
    delete(id) {
        this._event.raiseEvent(id, undefined);
        if (!super.delete(id))
            return false;
        const array = this._array;
        const index = this.#indexById.get(id);
        if (array && undefined !== index) {
            const lastIndex = array.length - 1;
            if (index < lastIndex) {
                // Swap the last element into the removed slot to avoid O(n) splice + index fixup.
                const lastProps = array[lastIndex];
                array[index] = lastProps;
                const lastId = this._idFromProps(lastProps);
                if (undefined !== lastId) {
                    this.#indexById.set(lastId, index);
                }
            }
            array.length = lastIndex;
            this.#indexById.delete(id);
        }
        return true;
    }
    clear() {
        for (const id of this.keys())
            this.delete(id);
        this._json[this._arrayKey] = undefined;
    }
    populate() {
        super.clear();
        this.#indexById.clear();
        const ovrs = this._array;
        if (!ovrs)
            return;
        for (let i = 0; i < ovrs.length; i++) {
            const id = this._idFromProps(ovrs[i]);
            if (undefined !== id && core_bentley_1.Id64.isValidId64(id)) {
                this.#indexById.set(id, i);
                const ovr = this._overrideFromProps(ovrs[i]);
                if (ovr)
                    super.set(id, ovr);
            }
        }
    }
    get _array() {
        return core_bentley_1.JsonUtils.asArray(this._json[this._arrayKey]);
    }
    findOrAllocateIndex(id) {
        const existing = this.#indexById.get(id);
        if (undefined !== existing) {
            return existing;
        }
        let ovrs = this._array;
        if (!ovrs)
            ovrs = this._json[this._arrayKey] = [];
        const newIndex = ovrs.length;
        this.#indexById.set(id, newIndex);
        return newIndex;
    }
}
/** Provides access to the settings defined by a [[DisplayStyle]] or [[DisplayStyleState]], and ensures that
 * the style's JSON properties are kept in sync.
 * @see [[DisplayStyleSettingsProps]] for the JSON representation of these settings.
 * @public
 */
class DisplayStyleSettings {
    _json;
    _viewFlags;
    _background;
    _monochrome;
    _monochromeMode;
    _subCategoryOverrides;
    _modelAppearanceOverrides;
    _realityModelDisplaySettings;
    _planarClipMasks;
    _excludedElements;
    _backgroundMap;
    _mapImagery;
    _analysisStyle;
    _clipStyle;
    _contextRealityModels;
    _whiteOnWhiteReversal;
    /** Returns true if this is a [[DisplayStyle3dSettings]]. */
    is3d() {
        return false;
    }
    /** Planar clip masks to be applied to persistent reality models (@see [SpatialModelState.isRealityModel]($frontend).
     * The key for each entry is the Id of the model to which the mask settings apply.
     */
    get planarClipMasks() {
        return this._planarClipMasks;
    }
    /** Reality models to be displayed in the view. */
    get contextRealityModels() {
        return this._contextRealityModels;
    }
    /** Event raised by [[applyOverrides]] just before the overrides are applied. */
    onApplyOverrides = new core_bentley_1.BeEvent();
    /** Event raised by [[applyOverrides]] after the overrides are applied. */
    onOverridesApplied = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[viewFlags]] property. */
    onViewFlagsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[backgroundColor]] property. */
    onBackgroundColorChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[monochromeColor]] property. */
    onMonochromeColorChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[monochromeMode]] property. */
    onMonochromeModeChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[backgroundMap]] property. */
    onBackgroundMapChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[mapImagery]] property.
     * @beta
     */
    onMapImageryChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the `scheduleScriptProps` property.
     * @see [[onRenderTimelineChanged]] to be notified when the [[renderTimeline]] property from which a script can be obtained is changed.
     */
    onScheduleScriptPropsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[renderTimeline]] property. */
    onRenderTimelineChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[timePoint]] property. */
    onTimePointChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[analysisStyle]] property. */
    onAnalysisStyleChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[analysisFraction]] property. */
    onAnalysisFractionChanged = new core_bentley_1.BeEvent();
    /** Event raised when the contents of [[excludedElementIds]] changes. */
    onExcludedElementsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[clipStyle]] property. */
    onClipStyleChanged = new core_bentley_1.BeEvent();
    /** Event raised when the [[SubCategoryOverride]]s change. */
    onSubCategoryOverridesChanged = new core_bentley_1.BeEvent();
    /** Event raised just before changing the appearance override for a model. */
    onModelAppearanceOverrideChanged = new core_bentley_1.BeEvent();
    /** Event raised just before [[setRealityModelDisplaySettings]] changes the display settings for a reality model.
     * @beta
     */
    onRealityModelDisplaySettingsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.thematic]] property. */
    onThematicChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.contours]] property. */
    onContoursChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.hiddenLineSettings]] property. */
    onHiddenLineSettingsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.ambientOcclusionSettings]] property. */
    onAmbientOcclusionSettingsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.solarShadows]] property. */
    onSolarShadowsChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.environment]] property. */
    onEnvironmentChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[DisplayStyle3dSettings.lights]] property. */
    onLightsChanged = new core_bentley_1.BeEvent();
    /** Event raised just before changing the plan projection settings for a model. */
    onPlanProjectionSettingsChanged = new core_bentley_1.BeEvent();
    /** Event raised just before adding or removing an entry from [[planarClipMasks]]. */
    onPlanarClipMaskChanged = new core_bentley_1.BeEvent();
    /** Event raised just prior to assignment to the [[whiteOnWhiteReversal]] property. */
    onWhiteOnWhiteReversalChanged = new core_bentley_1.BeEvent();
    /** Construct a new DisplayStyleSettings from an [[ElementProps.jsonProperties]].
     * @param jsonProperties An object with an optional `styles` property containing a display style's settings.
     * @param options Options for customizing the display style settings.
     * @note When the `DisplayStyleSetting`'s properties are modified by public setters, the `jsonProperties`'s `styles` object will be updated to reflect the change.
     * @note If `jsonProperties` contains no `styles` member, one will be added as an empty object.
     * @note Generally there is no reason to create an object of this type directly; a [[DisplayStyle]] or [[DisplayStyleState]] constructs one as part of its own construction.
     */
    constructor(jsonProperties, options) {
        if (undefined === jsonProperties.styles)
            jsonProperties.styles = {};
        this._json = jsonProperties.styles;
        this._viewFlags = ViewFlags_1.ViewFlags.fromJSON(this._json.viewflags);
        this._background = ColorDef_1.ColorDef.fromJSON(this._json.backgroundColor);
        this._monochrome = undefined !== this._json.monochromeColor ? ColorDef_1.ColorDef.fromJSON(this._json.monochromeColor) : ColorDef_1.ColorDef.white;
        this._monochromeMode = MonochromeMode.Flat === this._json.monochromeMode ? MonochromeMode.Flat : MonochromeMode.Scaled;
        this._backgroundMap = BackgroundMapSettings_1.BackgroundMapSettings.fromPersistentJSON(this._json.backgroundMap);
        this._mapImagery = MapImagerySettings_1.MapImagerySettings.createFromJSON(this._json.mapImagery, this._json.backgroundMap);
        // Ensure that if we used the deprecated imagery properties from this._backgroundMap to set up the base layer of this._mapImagery,
        // we update our JSON to include that base layer.
        this._json.mapImagery = this._mapImagery.toJSON();
        this._excludedElements = new ExcludedElements(this._json);
        if (this._json.analysisStyle)
            this._analysisStyle = AnalysisStyle_1.AnalysisStyle.fromJSON(this._json.analysisStyle);
        this._whiteOnWhiteReversal = WhiteOnWhiteReversalSettings_1.WhiteOnWhiteReversalSettings.fromJSON(this._json.whiteOnWhiteReversal);
        this._clipStyle = ClipStyle_1.ClipStyle.fromJSON(this._json.clipStyle);
        this._subCategoryOverrides = new OverridesMap(this._json, "subCategoryOvr", this.onSubCategoryOverridesChanged, (props) => props.subCategory, (ovr, subCategory) => { return { ...ovr.toJSON(), subCategory }; }, (props) => {
            const ovr = SubCategoryOverride_1.SubCategoryOverride.fromJSON(props);
            return ovr.anyOverridden ? ovr : undefined;
        });
        this._modelAppearanceOverrides = new OverridesMap(this._json, "modelOvr", this.onModelAppearanceOverrideChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => {
            const app = FeatureSymbology_1.FeatureAppearance.fromJSON(props);
            return app.anyOverridden ? app : undefined;
        });
        this._realityModelDisplaySettings = new OverridesMap(this._json, "realityModelDisplay", this.onRealityModelDisplaySettingsChanged, (props) => props.modelId, (settings, modelId) => { return { ...settings.toJSON(), modelId }; }, (props) => RealityModelDisplaySettings_1.RealityModelDisplaySettings.fromJSON(props));
        this._planarClipMasks = new OverridesMap(this._json, "planarClipOvr", this.onPlanarClipMaskChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => {
            const settings = PlanarClipMask_1.PlanarClipMaskSettings.fromJSON(props);
            return settings.isValid ? settings : undefined;
        });
        this._contextRealityModels = new ContextRealityModel_1.ContextRealityModels({
            container: this._json,
            createContextRealityModel: options?.createContextRealityModel,
            deferPopulating: options?.deferContextRealityModels,
        });
    }
    /** Flags controlling various aspects of the display style. */
    get viewFlags() { return this._viewFlags; }
    set viewFlags(flags) {
        if (this.viewFlags.equals(flags))
            return;
        this.onViewFlagsChanged.raiseEvent(flags);
        this._viewFlags = flags;
        this._json.viewflags = flags.toJSON();
    }
    /** The color displayed in the view background - by default, [[ColorDef.black]]. */
    get backgroundColor() { return this._background; }
    set backgroundColor(color) {
        if (this.backgroundColor.equals(color))
            return;
        this.onBackgroundColorChanged.raiseEvent(color);
        this._background = color;
        this._json.backgroundColor = color.toJSON();
    }
    /** The color used to draw geometry when [[ViewFlags.monochrome]] is enabled - by default, [[ColorDef.white]].
     * The monochrome color is applied to all surfaces and linear geometry, but only applied to the **edges** of surfaces in [[RenderMode.Wireframe]].
     * @see [[monochromeMode]] to control how the color is applied.
     */
    get monochromeColor() { return this._monochrome; }
    set monochromeColor(color) {
        if (this.monochromeColor.equals(color))
            return;
        this.onMonochromeColorChanged.raiseEvent(color);
        this._monochrome = color;
        this._json.monochromeColor = color.toJSON();
    }
    /** The style in which [[monochromeColor]] is applied when [[ViewFlags.monochrome]] is enabled - by default, [[MonochromeMode.Scaled]]. */
    get monochromeMode() { return this._monochromeMode; }
    set monochromeMode(mode) {
        if (this.monochromeMode === mode)
            return;
        this.onMonochromeModeChanged.raiseEvent(mode);
        this._monochromeMode = mode;
        this._json.monochromeMode = mode;
    }
    /** Settings controlling display of the background map within views of geolocated models. */
    get backgroundMap() { return this._backgroundMap; }
    set backgroundMap(map) {
        if (!this.backgroundMap.equals(map)) {
            this.onBackgroundMapChanged.raiseEvent(map);
            this._backgroundMap = map; // it's an immutable type.
            this._json.backgroundMap = map.toPersistentJSON();
        }
    }
    /** Settings defining the map imagery layers to be displayed within the view.
     * @beta
     */
    get mapImagery() { return this._mapImagery; }
    set mapImagery(mapImagery) {
        this.onMapImageryChanged.raiseEvent(mapImagery);
        this._mapImagery = mapImagery;
        this._json.mapImagery = this._mapImagery.toJSON();
    }
    /** @internal
     * Handles keeping the map imagery layers in synch after changes have been made (used internally only by front end)
     */
    synchMapImagery() {
        this.onMapImageryChanged.raiseEvent(this._mapImagery);
        this._json.mapImagery = this._mapImagery.toJSON();
    }
    /** The Id of a [RenderTimeline]($backend) element containing a [[RenderSchedule.Script]] used to animate the view.
     * If [[scheduleScriptProps]] is defined, it takes precedence over the script supplied by the RenderTimeline.
     * @note If this [[DisplayStyleSettings]] is associated with a [DisplayStyleState]($frontend), assigning to [[renderTimeline]] will enqueue asynchronous loading of
     * the script from the [RenderTimeline]($backend) element; for more readable code, prefer instead to `await` [DisplayStyleState.changeRenderTimeline]($frontend).
     * @see [[onRenderTimelineChanged]] to be notified of changes to this property.
     */
    get renderTimeline() {
        return this._json.renderTimeline;
    }
    set renderTimeline(id) {
        if (id !== this.renderTimeline) {
            this.onRenderTimelineChanged.raiseEvent(id);
            this._json.renderTimeline = id;
        }
    }
    /** JSON representation of a [[RenderSchedule.Script]] embedded in the display style describing how to animate the contents of the view over time.
     * This script, if present, takes precedence over a script supplied by [[renderTimeline]].
     * @see [[onScheduleScriptPropsChanged]] to be notified when this property changes.
     * @see [DisplayStyleState.scheduleScript]($frontend) to change the [[RenderSchedule.Script]] object directly rather than via JSON.
     */
    get scheduleScriptProps() {
        return this._json.scheduleScript;
    }
    set scheduleScriptProps(props) {
        this.onScheduleScriptPropsChanged.raiseEvent(props);
        this._json.scheduleScript = props;
    }
    /** The point in time currently reflected by the view, expressed in seconds in the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
     * This identifies a point on the timeline of the style's [[RenderSchedule.Script]], if any; it may also affect display of four-dimensional reality models.
     * @see [[onTimePointChanged]] to be notified of changes to this property.
     */
    get timePoint() {
        return this._json.timePoint;
    }
    set timePoint(timePoint) {
        if (timePoint !== this.timePoint) {
            this.onTimePointChanged.raiseEvent(timePoint);
            this._json.timePoint = timePoint;
        }
    }
    /** Settings controlling the display of analytical models.
     * @see [[analysisFraction]] to control playback of the animation.
     */
    get analysisStyle() { return this._analysisStyle; }
    set analysisStyle(style) {
        if (style === this.analysisStyle)
            return;
        this.onAnalysisStyleChanged.raiseEvent(style);
        this._analysisStyle = style;
        if (style)
            this._json.analysisStyle = style.toJSON();
        else
            delete this._json.analysisStyle;
    }
    /** A floating point value in [0..1] indicating the current point in animation of the [[analysisStyle]], where 0 corresponds to the beginning of
     * the animation and 1 to the end. Default: 0.0.
     */
    get analysisFraction() {
        const fraction = this._json.analysisFraction ?? 0;
        return Math.max(0, Math.min(1, fraction));
    }
    set analysisFraction(fraction) {
        if (this.analysisFraction === fraction)
            return;
        this.onAnalysisFractionChanged.raiseEvent(fraction);
        this._json.analysisFraction = Math.max(0, Math.min(1, fraction));
    }
    /** Settings controlling how white-on-white reversal is applied when [[ViewFlags.whiteOnWhiteReversal]] is enabled. */
    get whiteOnWhiteReversal() { return this._whiteOnWhiteReversal; }
    set whiteOnWhiteReversal(settings) {
        if (settings.equals(this.whiteOnWhiteReversal))
            return;
        this.onWhiteOnWhiteReversalChanged.raiseEvent(settings);
        this._whiteOnWhiteReversal = settings;
        const json = settings.toJSON();
        if (json)
            this._json.whiteOnWhiteReversal = json;
        else
            delete this._json.whiteOnWhiteReversal;
    }
    /** Customize the way geometry belonging to a [[SubCategory]] is drawn by this display style.
     * @param id The Id of the SubCategory whose appearance is to be overridden.
     * @param ovr The overrides to apply to the [[SubCategoryAppearance]].
     * @see [[dropSubCategoryOverride]]
     */
    overrideSubCategory(id, ovr) {
        this.subCategoryOverrides.set(id, ovr);
    }
    /** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style.
     * @param id The Id of the [[SubCategory]].
     * @see [[overrideSubCategory]]
     */
    dropSubCategoryOverride(id) {
        this.subCategoryOverrides.delete(id);
    }
    /** The overrides applied by this style. */
    get subCategoryOverrides() {
        return this._subCategoryOverrides;
    }
    /** Obtain the override applied to a [[SubCategoryAppearance]] by this style.
     * @param id The Id of the [[SubCategory]].
     * @returns The corresponding SubCategoryOverride, or undefined if the SubCategory's appearance is not overridden.
     * @see [[overrideSubCategory]]
     */
    getSubCategoryOverride(id) {
        return this.subCategoryOverrides.get(id);
    }
    /** Returns true if an [[SubCategoryOverride]]s are defined by this style. */
    get hasSubCategoryOverride() {
        return this.subCategoryOverrides.size > 0;
    }
    /** Customize the way a [Model]($backend)   is drawn by this display style.
     * @param modelId The Id of the [Model]($backend)  whose appearance is to be overridden.
     * @param ovr The overrides to apply to the [Model]($backend) .
     * @see [[dropModelAppearanceOverride]]
     */
    overrideModelAppearance(modelId, ovr) {
        this.modelAppearanceOverrides.set(modelId, ovr);
    }
    /** Remove any appearance overrides applied to a [Model]($backend)  by this style.
     * @param modelId The Id of the [Model]($backend) .
     * @param ovr The overrides to apply to the [Model]($backend) .
     * @see [[overrideModelAppearance]]
     */
    dropModelAppearanceOverride(id) {
        this.modelAppearanceOverrides.delete(id);
    }
    /** The overrides applied by this style. */
    get modelAppearanceOverrides() {
        return this._modelAppearanceOverrides;
    }
    /** Obtain the override applied to a [Model]($backend)  by this style.
     * @param id The Id of the [Model]($backend).
     * @returns The corresponding FeatureAppearance, or undefined if the Model's appearance is not overridden.
     * @see [[overrideModelAppearance]]
     */
    getModelAppearanceOverride(id) {
        return this.modelAppearanceOverrides.get(id);
    }
    /** Returns true if model appearance overrides are defined by this style. */
    get hasModelAppearanceOverride() {
        return this.modelAppearanceOverrides.size > 0;
    }
    /** Get any settings that override how the reality model with the specified Id is displayed.
     * @param modelId The Id of the [Model]($backend).
     * @returns the display settings, or `undefined` if no settings have been associated with `modelId`.
     * @see [[setRealityModelDisplaySettings]] to change the settings.
     * @beta
     */
    getRealityModelDisplaySettings(modelId) {
        return this._realityModelDisplaySettings.get(modelId);
    }
    /** Change the settings that control how the reality model with the specified Id is displayed.
     * @param modelId The Id of the [Model]($backend) to which the settings apply.
     * @param settings The settings to apply to the model, or `undefined` to clear any previous settings for that model.
     * @beta
     */
    setRealityModelDisplaySettings(modelId, settings) {
        if (settings)
            this._realityModelDisplaySettings.set(modelId, settings);
        else
            this._realityModelDisplaySettings.delete(modelId);
    }
    /** The set of elements that will not be drawn by this display style.
     * @returns An iterable over the elements' Ids.
     */
    get excludedElementIds() {
        return this._excludedElements;
    }
    /** @internal */
    get compressedExcludedElementIds() {
        return this._excludedElements.ids;
    }
    /** Add one or more elements to the set of elements not to be displayed.
     * @param id The Ids of the element(s) to be excluded.
     */
    addExcludedElements(id) {
        this._excludedElements.add("string" === typeof id ? [id] : id);
        this.onExcludedElementsChanged.raiseEvent();
    }
    /** Remove an element from the set of elements not to be displayed. */
    dropExcludedElement(id) {
        this._excludedElements.delete([id]);
        this.onExcludedElementsChanged.raiseEvent();
    }
    /** Remove one or more elements from the set of elements not to be displayed.
     * @param id The Ids of the element(s) to be removed from the set of excluded elements.
     */
    dropExcludedElements(id) {
        this._excludedElements.delete("string" === typeof id ? [id] : id);
        this.onExcludedElementsChanged.raiseEvent();
    }
    /** Remove all elements from the set of elements not to be displayed. */
    clearExcludedElements() {
        this._excludedElements.reset(undefined);
        this.onExcludedElementsChanged.raiseEvent();
    }
    /** The style applied to the view's [ClipVector]($core-geometry). */
    get clipStyle() {
        return this._clipStyle;
    }
    set clipStyle(style) {
        this.onClipStyleChanged.raiseEvent(style);
        this._clipStyle = style;
        if (style.matchesDefaults)
            delete this._json.clipStyle;
        else
            this._json.clipStyle = style.toJSON();
    }
    /** Convert these settings to their JSON representation. */
    toJSON() {
        return this._json;
    }
    /** Serialize a subset of these settings to JSON, such that they can be applied to another DisplayStyleSettings to selectively override those settings.
     * @param options Specifies which settings should be serialized. By default, settings that are specific to an iModel (e.g., subcategory overrides) or iTwin (e.g., context reality models)
     * are omitted, as are drawing aids (e.g., ACS triad and grid).
     * @returns a JSON representation of the selected settings suitable for passing to [[applyOverrides]].
     * @see [[applyOverrides]] to apply the overrides to another DisplayStyleSettings..
     */
    toOverrides(options) {
        if (options?.includeAll) {
            return {
                ...this.toJSON(),
                viewflags: this.viewFlags.toFullyDefinedJSON(),
            };
        }
        const viewflags = this.viewFlags.toFullyDefinedJSON();
        const props = {
            viewflags,
            backgroundColor: this.backgroundColor.toJSON(),
            monochromeColor: this.monochromeColor.toJSON(),
            monochromeMode: this.monochromeMode,
            whiteOnWhiteReversal: this.whiteOnWhiteReversal.toJSON() ?? { ignoreBackgroundColor: false },
        };
        if (options?.includeBackgroundMap) {
            props.backgroundMap = this.backgroundMap.toPersistentJSON();
            props.mapImagery = this.mapImagery.toJSON();
        }
        else {
            delete viewflags.backgroundMap;
        }
        if (!options?.includeDrawingAids) {
            delete viewflags.acs;
            delete viewflags.grid;
        }
        if (options?.includeITwinSpecific || options?.includeIModelSpecific) {
            props.timePoint = this.timePoint;
            if (this._json.contextRealityModels) {
                props.contextRealityModels = this._json.contextRealityModels;
                if (!options?.includeIModelSpecific)
                    for (const model of this._json.contextRealityModels)
                        delete model.classifiers;
            }
        }
        if (options?.includeIModelSpecific) {
            if (this.analysisStyle) {
                props.analysisStyle = this.analysisStyle.toJSON();
                props.analysisFraction = this.analysisFraction;
            }
            if (this.scheduleScriptProps)
                props.scheduleScript = [...this.scheduleScriptProps];
            if (this.renderTimeline)
                props.renderTimeline = this.renderTimeline;
            props.subCategoryOvr = this._json.subCategoryOvr ? [...this._json.subCategoryOvr] : [];
            props.modelOvr = this._json.modelOvr ? [...this._json.modelOvr] : [];
            props.excludedElements = this._excludedElements.ids;
        }
        return props;
    }
    /** Selectively override some of these settings. Any field that is explicitly defined by the input will be overridden in these settings; any fields left undefined in the input
     * will retain their current values in these settings. The input's [[ViewFlags]] are applied individually - only those flags that are explicitly defined will be overridden.
     * For example, the following overrides will set the render mode to "smooth", change the background color to white, turn shadows off, and leave all other settings intact:
     * ```ts
     *  {
     *    viewflags: {
     *      renderMode: RenderMode.SmoothShade,
     *      shadows: false,
     *    },
     *    backgroundColor: ColorByName.white,
     *  }
     * ```
     * @see [[toOverrides]] to produce overrides from an existing DisplayStyleSettings.
     */
    applyOverrides(overrides) {
        this._applyOverrides(overrides);
        this.onOverridesApplied.raiseEvent(overrides);
    }
    /** @internal */
    _applyOverrides(overrides) {
        this.onApplyOverrides.raiseEvent(overrides);
        if (overrides.viewflags) {
            this.viewFlags = ViewFlags_1.ViewFlags.fromJSON({
                ...this.viewFlags.toJSON(),
                ...overrides.viewflags,
            });
        }
        if (undefined !== overrides.backgroundColor)
            this.backgroundColor = ColorDef_1.ColorDef.fromJSON(overrides.backgroundColor);
        if (undefined !== overrides.monochromeColor)
            this.monochromeColor = ColorDef_1.ColorDef.fromJSON(overrides.monochromeColor);
        if (undefined !== overrides.monochromeMode)
            this.monochromeMode = overrides.monochromeMode;
        if (overrides.backgroundMap)
            this.backgroundMap = BackgroundMapSettings_1.BackgroundMapSettings.fromPersistentJSON(overrides.backgroundMap);
        if (overrides.mapImagery)
            this.mapImagery = MapImagerySettings_1.MapImagerySettings.createFromJSON(overrides.mapImagery, this.backgroundMap.toPersistentJSON());
        if (undefined !== overrides.timePoint)
            this.timePoint = overrides.timePoint;
        if (overrides.contextRealityModels) {
            this.contextRealityModels.clear();
            for (const props of overrides.contextRealityModels)
                this.contextRealityModels.add(props);
        }
        if (overrides.analysisStyle)
            this.analysisStyle = AnalysisStyle_1.AnalysisStyle.fromJSON(overrides.analysisStyle);
        if (overrides.whiteOnWhiteReversal)
            this.whiteOnWhiteReversal = WhiteOnWhiteReversalSettings_1.WhiteOnWhiteReversalSettings.fromJSON(overrides.whiteOnWhiteReversal);
        if (undefined !== overrides.analysisFraction)
            this.analysisFraction = overrides.analysisFraction;
        if (overrides.scheduleScript)
            this.scheduleScriptProps = [...overrides.scheduleScript];
        if (overrides.renderTimeline)
            this.renderTimeline = overrides.renderTimeline;
        if (overrides.subCategoryOvr) {
            this._json.subCategoryOvr = [...overrides.subCategoryOvr];
            this._subCategoryOverrides.populate();
        }
        if (overrides.modelOvr) {
            this._json.modelOvr = [...overrides.modelOvr];
            this._modelAppearanceOverrides.populate();
        }
        if (overrides.realityModelDisplay) {
            this._json.realityModelDisplay = [...overrides.realityModelDisplay];
            this._realityModelDisplaySettings.populate();
        }
        if (overrides.excludedElements)
            this._excludedElements.reset("string" === typeof overrides.excludedElements ? overrides.excludedElements : [...overrides.excludedElements]);
        this.onOverridesApplied.raiseEvent(overrides);
    }
}
exports.DisplayStyleSettings = DisplayStyleSettings;
/** Provides access to the settings defined by a [[DisplayStyle3d]] or [[DisplayStyle3dState]], and ensures that
 * the style's JSON properties are kept in sync.
 * @public
 */
class DisplayStyle3dSettings extends DisplayStyleSettings {
    _thematic;
    _contours;
    _hline;
    _ao;
    _solarShadows;
    _lights;
    _environment;
    _planProjections;
    get _json3d() { return this._json; }
    is3d() {
        return true;
    }
    constructor(jsonProperties, options) {
        super(jsonProperties, options);
        this._thematic = ThematicDisplay_1.ThematicDisplay.fromJSON(this._json3d.thematic);
        this._contours = ContourDisplay_1.ContourDisplay.fromJSON(this._json3d.contours);
        this._hline = HiddenLine_1.HiddenLine.Settings.fromJSON(this._json3d.hline);
        this._ao = AmbientOcclusion_1.AmbientOcclusion.Settings.fromJSON(this._json3d.ao);
        this._solarShadows = SolarShadows_1.SolarShadowSettings.fromJSON(this._json3d.solarShadows);
        this._environment = Environment_1.Environment.fromJSON(this._json3d.environment);
        // Very long ago we used to stick MicroStation's light settings into json.sceneLights. Later we started adding the sunDir.
        // We don't want any of MicroStation's settings. We do want to preserve the sunDir if present.
        if (this._json3d.lights) {
            this._lights = LightSettings_1.LightSettings.fromJSON(this._json3d.lights);
        }
        else {
            const sunDir = this._json3d.sceneLights?.sunDir;
            this._lights = LightSettings_1.LightSettings.fromJSON(sunDir ? { solar: { direction: sunDir } } : undefined);
        }
        this.populatePlanProjectionsFromJSON();
    }
    populatePlanProjectionsFromJSON() {
        this._planProjections = undefined;
        const projections = this._json3d.planProjections;
        if (undefined !== projections) {
            for (const key of Object.keys(projections)) {
                const id = core_bentley_1.Id64.fromJSON(key);
                if (!core_bentley_1.Id64.isValidId64(id)) {
                    delete projections[key];
                    continue;
                }
                const settings = PlanProjectionSettings_1.PlanProjectionSettings.fromJSON(projections[key]);
                if (undefined === settings) {
                    delete projections[key];
                    continue;
                }
                if (undefined === this._planProjections)
                    this._planProjections = new Map();
                this._planProjections.set(id, settings);
            }
        }
    }
    /** Convert these settings to their JSON representation. */
    toJSON() {
        return this._json3d;
    }
    /** See [[DisplayStyleSettings.toOverrides]]. */
    toOverrides(options) {
        const props = super.toOverrides(options);
        if (options?.includeAll)
            return props;
        (0, core_bentley_1.assert)(undefined !== props.viewflags);
        props.environment = this.environment.toJSON();
        props.hline = this.hiddenLineSettings.toJSON();
        props.ao = this.ambientOcclusionSettings.toJSON();
        props.solarShadows = this.solarShadows.toJSON();
        props.lights = this.lights.toJSON();
        if (options?.includeIModelSpecific) {
            props.thematic = this.thematic.toJSON();
            if (this._json3d.planProjections)
                props.planProjections = { ...this._json3d.planProjections };
        }
        else if (ThematicDisplay_1.ThematicDisplayMode.InverseDistanceWeightedSensors !== this.thematic.displayMode) {
            props.thematic = {
                ...this.thematic.toJSON(),
                sensorSettings: undefined,
            };
            if (ThematicDisplay_1.ThematicDisplayMode.Height === props.thematic.displayMode) {
                // DisplayStyle3dState will compute range based on project extents.
                props.thematic.range = undefined;
            }
        }
        props.contours = this.contours.toJSON();
        return props;
    }
    /** See [[DisplayStyleSettings.applyOverrides]]. */
    applyOverrides(overrides) {
        super._applyOverrides(overrides);
        if (overrides.environment)
            this.environment = Environment_1.Environment.fromJSON(overrides.environment);
        if (overrides.hline)
            this.hiddenLineSettings = HiddenLine_1.HiddenLine.Settings.fromJSON(overrides.hline);
        if (overrides.ao)
            this.ambientOcclusionSettings = AmbientOcclusion_1.AmbientOcclusion.Settings.fromJSON(overrides.ao);
        if (overrides.solarShadows)
            this.solarShadows = SolarShadows_1.SolarShadowSettings.fromJSON(overrides.solarShadows);
        if (overrides.lights)
            this.lights = LightSettings_1.LightSettings.fromJSON(overrides.lights);
        if (overrides.planProjections) {
            this._json3d.planProjections = { ...overrides.planProjections };
            this.populatePlanProjectionsFromJSON();
        }
        if (overrides.thematic)
            this.thematic = ThematicDisplay_1.ThematicDisplay.fromJSON(overrides.thematic);
        if (overrides.contours)
            this.contours = ContourDisplay_1.ContourDisplay.fromJSON(overrides.contours);
        this.onOverridesApplied.raiseEvent(overrides);
    }
    /** The settings that control thematic display. */
    get thematic() { return this._thematic; }
    set thematic(thematic) {
        if (thematic.equals(this.thematic))
            return;
        this.onThematicChanged.raiseEvent(thematic);
        this._thematic = thematic;
        this._json3d.thematic = thematic.toJSON();
    }
    /** The settings that control contour display. */
    get contours() { return this._contours; }
    set contours(contours) {
        if (contours.equals(this.contours))
            return;
        this.onContoursChanged.raiseEvent(contours);
        this._contours = contours;
        this._json3d.contours = contours.toJSON();
    }
    /** The settings that control how visible and hidden edges are displayed.  */
    get hiddenLineSettings() { return this._hline; }
    set hiddenLineSettings(hline) {
        if (hline.equals(this.hiddenLineSettings))
            return;
        this.onHiddenLineSettingsChanged.raiseEvent(hline);
        this._hline = hline;
        this._json3d.hline = hline.toJSON();
    }
    /** The settings that control how ambient occlusion is displayed. */
    get ambientOcclusionSettings() { return this._ao; }
    set ambientOcclusionSettings(ao) {
        this.onAmbientOcclusionSettingsChanged.raiseEvent(ao);
        this._ao = ao;
        this._json3d.ao = ao.toJSON();
    }
    /** The settings that control how solar shadows are displayed. */
    get solarShadows() {
        return this._solarShadows;
    }
    set solarShadows(solarShadows) {
        if (solarShadows.equals(this.solarShadows))
            return;
        this.onSolarShadowsChanged.raiseEvent(solarShadows);
        this._solarShadows = solarShadows;
        const json = solarShadows.toJSON();
        if (!json)
            delete this._json3d.solarShadows;
        else
            this._json3d.solarShadows = json;
    }
    /** Controls the display of a [[SkyBox]], [[GroundPlane]], and [[Atmosphere]].
     * @public
     */
    get environment() {
        return this._environment;
    }
    set environment(environment) {
        if (environment !== this.environment) {
            this.onEnvironmentChanged.raiseEvent(environment);
            this._environment = environment;
            this._json3d.environment = environment.toJSON();
        }
    }
    /** Toggle display of the [[environment]]'s [[SkyBox]].
     * @param display Whether to display the skybox, or `undefined` to toggle the current display.
     */
    toggleSkyBox(display) {
        display = display ?? this.environment.displaySky;
        if (display !== this.environment.displaySky)
            this.environment = this.environment.withDisplay({ sky: display });
    }
    /** Toggle display of the [[environment]]'s [[GroundPlane]].
     * @param display Whether to display the ground plane, or `undefined` to toggle the current display.
     */
    toggleGroundPlane(display) {
        display = display ?? this.environment.displayGround;
        if (display !== this.environment.displayGround)
            this.environment = this.environment.withDisplay({ ground: display });
    }
    /** Toggle display of the [[environment]]'s [[Atmosphere]].
     * @beta
     * @param display Whether to display the atmosphere, or `undefined` to toggle the current display.
     */
    toggleAtmosphere(display) {
        display = display ?? this.environment.displayAtmosphere;
        if (display !== this.environment.displayAtmosphere)
            this.environment = this.environment.withDisplay({ atmosphere: display });
    }
    get lights() {
        return this._lights;
    }
    set lights(lights) {
        if (this.lights.equals(lights))
            return;
        this.onLightsChanged.raiseEvent(lights);
        this._lights = lights;
        this._json3d.lights = lights.toJSON();
    }
    /** Adjust the solar light direction based on a date and time at a geographic location.
     * This replaces `this.lights` with a copy that records the time point and the computed direction.
     * @param timePoint The time in UNIX milliseconds.
     * @param location The geographic location; or an iModel, in which case the iModel's [[EcefLocation]] is used.
     * @see [[sunTime]] to get the current sun time.
     * @see [[clearSunTime]] to clear the time point.
     * @note If `location` is an iModel lacking an EcefLocation, a location in Exton, Pennsylvania will be used to compute the light direction instead.
     */
    setSunTime(timePoint, location) {
        let cartoCenter;
        if (location instanceof IModel_1.IModel) {
            if (location.ecefLocation)
                cartoCenter = Cartographic_1.Cartographic.fromEcef(location.ecefLocation.origin);
            if (!cartoCenter)
                cartoCenter = Cartographic_1.Cartographic.fromDegrees({ longitude: -75.17035, latitude: 39.954927, height: 0.0 });
        }
        else {
            cartoCenter = location;
        }
        const direction = (0, SolarCalculate_1.calculateSolarDirection)(new Date(timePoint), cartoCenter);
        this.lights = this.lights.clone({ solar: { direction, timePoint } });
    }
    /** Clear the solar time point stored in `this.lights.solarLight`.
     * @note This does not affect the solar light direction.
     * @see [[sunTime]] to get the current sun time.
     * @see [[setSunTime]] to set the time point and the solar light direction derived from it.
     */
    clearSunTime() {
        if (this.lights.solar.timePoint === undefined)
            return;
        const solar = this.lights.solar.toJSON() ?? {};
        solar.timePoint = undefined;
        this.lights = this.lights.clone({ solar });
    }
    /** The time point from which the solar light direction was derived, in UNIX milliseconds.
     * @see [[setSunTime]] to change the time point and solar direction.
     * @see [[clearSunTime]] to reset the time point to `undefined`.
     */
    get sunTime() {
        return this.lights.solar.timePoint;
    }
    /** Get the plan projection settings associated with the specified model, if defined. */
    getPlanProjectionSettings(modelId) {
        return undefined !== this._planProjections ? this._planProjections.get(modelId) : undefined;
    }
    /** Set or clear the plan projection settings associated with the specified model. */
    setPlanProjectionSettings(modelId, settings) {
        this.onPlanProjectionSettingsChanged.raiseEvent(modelId, settings);
        if (undefined === settings) {
            if (undefined !== this._planProjections) {
                (0, core_bentley_1.assert)(undefined !== this._json3d.planProjections);
                this._planProjections.delete(modelId);
                delete this._json3d.planProjections[modelId];
                if (0 === this._planProjections.size) {
                    this._planProjections = undefined;
                    delete this._json3d.planProjections;
                }
            }
            return;
        }
        if (undefined === this._planProjections) {
            this._planProjections = new Map();
            this._json3d.planProjections = {};
        }
        this._planProjections.set(modelId, settings);
        (0, core_bentley_1.expectDefined)(this._json3d.planProjections)[modelId] = settings.toJSON();
    }
    /** An iterator over all of the defined plan projection settings. The iterator includes the Id of the model associated with each settings object. */
    get planProjectionSettings() {
        return undefined !== this._planProjections ? this._planProjections.entries() : undefined;
    }
}
exports.DisplayStyle3dSettings = DisplayStyle3dSettings;
//# sourceMappingURL=DisplayStyleSettings.js.map