"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.SpatialClassifiers = exports.SpatialClassifier = exports.SpatialClassifierFlags = exports.SpatialClassifierOutsideDisplay = exports.SpatialClassifierInsideDisplay = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
/** Describes how a [[SpatialClassifier]] affects the display of classified geometry - that is, geometry intersecting
 * the classifier.
 * @public
 * @extensions
 */
var SpatialClassifierInsideDisplay;
(function (SpatialClassifierInsideDisplay) {
    /** The geometry is not displayed. */
    SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Off"] = 0] = "Off";
    /** The geometry is displayed without alteration. */
    SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["On"] = 1] = "On";
    /** The geometry is darkened. */
    SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Dimmed"] = 2] = "Dimmed";
    /** The geometry is tinted by the [Viewport.hilite]($frontend) color. */
    SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["Hilite"] = 3] = "Hilite";
    /** The geometry is tinted with the colors of the classifier elements. */
    SpatialClassifierInsideDisplay[SpatialClassifierInsideDisplay["ElementColor"] = 4] = "ElementColor";
})(SpatialClassifierInsideDisplay || (exports.SpatialClassifierInsideDisplay = SpatialClassifierInsideDisplay = {}));
/** Describes how a [[SpatialClassifier]] affects the display of unclassified geometry - that is, geometry not intersecting
 * the classifier.
 * @public
 * @extensions
 */
var SpatialClassifierOutsideDisplay;
(function (SpatialClassifierOutsideDisplay) {
    /** The geometry is not displayed. */
    SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["Off"] = 0] = "Off";
    /** The geometry is displayed without alteration. */
    SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["On"] = 1] = "On";
    /** The geometry is darkened. */
    SpatialClassifierOutsideDisplay[SpatialClassifierOutsideDisplay["Dimmed"] = 2] = "Dimmed";
})(SpatialClassifierOutsideDisplay || (exports.SpatialClassifierOutsideDisplay = SpatialClassifierOutsideDisplay = {}));
/** Flags affecting how a [[SpatialClassifier]] is applied.
 * @public
 */
class SpatialClassifierFlags {
    /** How geometry intersecting the classifier should be displayed. */
    inside;
    /** How geometry not intersecting the classifier should be displayed. */
    outside;
    /** True for volume classification; false for planar classification. */
    isVolumeClassifier;
    /** Construct new flags. */
    constructor(inside = SpatialClassifierInsideDisplay.ElementColor, outside = SpatialClassifierOutsideDisplay.Dimmed, isVolumeClassifier = false) {
        this.inside = insideDisplay(inside);
        this.outside = outsideDisplay(outside);
        this.isVolumeClassifier = isVolumeClassifier;
    }
    /** Construct from JSON representation. */
    static fromJSON(props) {
        return new SpatialClassifierFlags(props.inside, props.outside, true === props.isVolumeClassifier);
    }
    /** Convert to JSON representation. */
    toJSON() {
        const props = {
            inside: this.inside,
            outside: this.outside,
        };
        if (this.isVolumeClassifier)
            props.isVolumeClassifier = true;
        return props;
    }
    /** Create flags indentical to these ones except for any properties explicitly specified by `changedProps`. */
    clone(changedProps) {
        if (!changedProps)
            return this;
        return SpatialClassifierFlags.fromJSON({ ...this.toJSON(), ...changedProps });
    }
    /** Return true if these flags are equivalent to `other`. */
    equals(other) {
        if (other === this)
            return true;
        return other.inside === this.inside && other.outside === this.outside && other.isVolumeClassifier === this.isVolumeClassifier;
    }
    /** Return true if these flags are equivalent to `props`. */
    equalsProps(props) {
        return this.inside === props.inside && this.outside === props.outside && this.isVolumeClassifier === (true === props.isVolumeClassifier);
    }
}
exports.SpatialClassifierFlags = SpatialClassifierFlags;
/** Describes how to use the geometry of one [GeometricModel]($backend) to classify the contents of other models - most typically, reality models.
 * Applying a classifier divides the geometry of the classified model into two groups:
 *  - Classified (intersecting the classifier); and
 *  - Unclassified (not intersecting the classifier).
 * For example, a model containing the building footprints for a city block could be used to classify a reality mesh captured from photographs of the
 * real-world block. Then, buildings within the reality mesh can be selected individually, and present the properties of the classifier geometry (e.g.,
 * the address of the building). The appearance of the geometry can also be customized based using [[SpatialClassifierInsideDisplay]] and [[SpatialClassifierOutsideDisplay]].
 * Two types of classification are supported:
 *  - Planar classification, in which the geometry of the classifier model is projected onto a plane to classify geometry within a region extruded perpendicular
 * the plane (e.g., the building footprints example); and
 *  - Volume classification, in which closed, non-intersecting volumes within the classifier classify geometry that intersects (i.e. is contained within) those same volumes (e.g., imagine using boxes instead
 * of footprints to classify buildings, or floors of buildings).
 * @see this (interactive example)[https://www.itwinjs.org/sample-showcase/?group=Viewer+Features&sample=classifier-sample].
 * @see [[SpatialClassifiers]] to define a set of classifiers.
 * @see [[ContextRealityModel.classifiers]] to classify a context reality model.
 * @see [SpatialModelState.classifiers]($frontend) to classify a persistent reality model.
 * @public
 */
class SpatialClassifier {
    /** The Id of the [GeometricModel]($backend) whose geometry is used to produce the classifier. */
    modelId;
    /** A distance in meters by which to expand the classifier geometry. For example, if line strings are used to represent streets,
     * you might expand them to the average width of a street.
     */
    expand;
    /** Flags controlling how to apply the classifier. */
    flags;
    /** A user-friendly name, useful for identifying individual classifiers within a [[SpatialClassifiers]]. */
    name;
    /** Construct a new classifier. */
    constructor(modelId, name, flags = new SpatialClassifierFlags(), expand = 0) {
        this.modelId = modelId;
        this.expand = expand;
        this.flags = flags;
        this.name = name;
    }
    /** Construct from JSON representation. */
    static fromJSON(props) {
        return new SpatialClassifier(props.modelId, props.name, SpatialClassifierFlags.fromJSON(props.flags), props.expand);
    }
    /** Convert to JSON representation.
     * @note This method always sets the [[SpatialClassifierProps.isActive]] property to `false`.
     */
    toJSON() {
        return {
            modelId: this.modelId,
            expand: this.expand,
            flags: this.flags.toJSON(),
            name: this.name,
            isActive: false,
        };
    }
    /** Construct from Model Map Layer.
     * @beta
     */
    static fromModelMapLayer(mapLayer) {
        const flags = SpatialClassifierFlags.fromJSON({ inside: SpatialClassifierInsideDisplay.Off, outside: SpatialClassifierOutsideDisplay.Off });
        return new SpatialClassifier(mapLayer.modelId, mapLayer.name, flags);
    }
    /** Create a classifier identical to this one except for any properties explicitly specified by `changedProps`. */
    clone(changedProps) {
        if (!changedProps)
            return this;
        return SpatialClassifier.fromJSON({ ...this.toJSON(), ...changedProps });
    }
    /** Return true if this classifier is equivalent to `other`. */
    equals(other) {
        if (other === this)
            return true;
        return this.modelId === other.modelId && this.expand === other.expand && this.name === other.name && this.flags.equals(other.flags);
    }
    /** Return true if this classifier is equivalent to `props`. */
    equalsProps(props) {
        return this.modelId === props.modelId && this.expand === props.expand && this.name === props.name && this.flags.equalsProps(props.flags);
    }
}
exports.SpatialClassifier = SpatialClassifier;
/** A set of [[SpatialClassifier]]s for a given reality model. At most one of the classifiers can be actively classifying the model at any given time.
 * The set of classifiers can be presented to the user, listed by name, so that the active classifier can be changed.
 * The set of classifiers is populated from its JSON representation and that representation is kept in sync as the set of classifiers is modified.
 * @see this (interactive example)[https://www.itwinjs.org/sample-showcase/?group=Viewer+Features&sample=classifier-sample].
 * @see [[SpatialClassifier]] for details on how spatial classification works.
 * @see [[ContextRealityModel.classifiers]] to define classifiers for a context reality model.
 * @see [SpatialModelState.classifiers]($frontend) to define classifiers for a persistent reality model.
 * @public
 */
class SpatialClassifiers {
    _json;
    _classifiers = [];
    _active;
    /** Construct a new set of classifiers from the JSON representation. The set will be initialized from `container.classifiers` and that JSON representation
     * will be kept in sync with changes made to the set. The caller should not directly modify `container.classifiers` or its contents as that will cause the set to become out
     * of sync with the JSON representation.
     * The [[active]] classifier will be determined by the first [[SpatialClassifierProps]] whose `isActive` property is set to `true`, if any.
     */
    constructor(container) {
        this._json = container;
        const json = this._array;
        if (!json)
            return;
        for (const props of json) {
            const classifier = SpatialClassifier.fromJSON(props);
            this._classifiers.push(classifier);
            if (props.isActive) {
                if (!this._active)
                    this._active = classifier;
                else
                    props.isActive = false;
            }
        }
    }
    /** The classifier currently classifying the target reality model. The classifier passed to the setter must be one obtained from this set, or one equivalent to
     * one contained in this set; in the latter case, the equivalent classifier contained in this set becomes active.
     */
    /** The classifier currently classifying the target reality model, if any.
     * @see [[setActive]] to change the active classifier.
     */
    get active() {
        return this._active;
    }
    /** Change the [[active]] classifier. The input must be a classifier belonging to this set, or equivalent to one in the set.
     * If no equivalent classifier exists in the set, the active classifier remains unchanged.
     * @param The classifier to set as active, or `undefined` to clear the active classifier.
     * @returns the active classifier.
     */
    setActive(active) {
        const array = this._array;
        if (!array)
            return this.active;
        if (active) {
            active = this.findEquivalent(active);
            if (!active)
                return this.active;
        }
        if (active === this.active)
            return this.active;
        let propsIndex = -1;
        if (active) {
            propsIndex = array.findIndex((x) => active.equalsProps(x));
            if (-1 === propsIndex)
                return this.active;
        }
        this._active = active;
        for (let i = 0; i < array.length; i++)
            array[i].isActive = (i === propsIndex);
        return this.active;
    }
    /** Obtain an iterator over the classifiers contained in this set. */
    [Symbol.iterator]() {
        return this._classifiers[Symbol.iterator]();
    }
    /** The number of classifiers in this set. */
    get size() {
        return this._array?.length ?? 0;
    }
    /** Returns the first classifier that satisfies `criterion`, or `undefined` if no classifier satisfies it. */
    find(criterion) {
        return this._classifiers.find(criterion);
    }
    /** Find the first classifier that is equivalent to the supplied classifier, or `undefined` if no equivalent classifier exists in this set. */
    findEquivalent(classifier) {
        return this.find((x) => x.equals(classifier));
    }
    /** Return true if the specified classifier or one equivalent to it exists in this set. */
    has(classifier) {
        return undefined !== this.findEquivalent(classifier);
    }
    /** Add a classifier to this set. If an equivalent classifier already exists, the supplied classifier is not added.
     * @param classifier The classifier to add.
     * @returns The equivalent pre-existing classifier, if one existed; or the supplied classifier, if it was added to the set.
     */
    add(classifier) {
        const existing = this.findEquivalent(classifier);
        if (existing)
            return existing;
        let array = this._array;
        if (!array)
            array = this._json.classifiers = [];
        this._classifiers.push(classifier);
        array.push(classifier.toJSON());
        return classifier;
    }
    /** Replace an existing classifier with a different one.
     * @param toReplace The classifier to be replaced.
     * @param replacement The classifier to replace `toReplace`.
     * @returns true if a classifier equivalent to `toReplace` existed in the set and was replaced by `replacement`.
     * @note If `toReplace` was the [[active]] classifier, `replacement` will become active.
     */
    replace(toReplace, replacement) {
        const list = this._array;
        if (!list)
            return false;
        const classifierIndex = this._classifiers.findIndex((x) => x.equals(toReplace));
        if (-1 === classifierIndex)
            return false;
        const propsIndex = list.findIndex((x) => toReplace.equalsProps(x));
        (0, core_bentley_1.assert)(propsIndex === classifierIndex);
        if (-1 === propsIndex)
            return false;
        toReplace = this._classifiers[classifierIndex];
        const wasActive = this.active === toReplace;
        this._classifiers[classifierIndex] = replacement;
        const props = list[propsIndex] = replacement.toJSON();
        if (wasActive) {
            props.isActive = true;
            this._active = replacement;
        }
        return true;
    }
    /** Remove the first classifier equivalent to `classifier` from this set.
     * @param classifier The classifier to remove.
     * @returns The classifier that was actually removed, or `undefined` if none was removed.
     */
    delete(classifier) {
        const list = this._array;
        if (!list)
            return undefined;
        const classifierIndex = this._classifiers.findIndex((x) => x.equals(classifier));
        if (-1 === classifierIndex)
            return undefined;
        classifier = this._classifiers[classifierIndex];
        const propsIndex = list.findIndex((x) => classifier.equalsProps(x));
        (0, core_bentley_1.assert)(propsIndex === classifierIndex);
        if (-1 === propsIndex)
            return undefined;
        list.splice(propsIndex, 1);
        this._classifiers.splice(classifierIndex, 1);
        if (list.length === 0)
            this._json.classifiers = undefined;
        if (classifier === this.active)
            this._active = undefined;
        return classifier;
    }
    /** Remove all classifiers from this set. */
    clear() {
        this._classifiers.length = 0;
        this._json.classifiers = undefined;
        this._active = undefined;
    }
    get _array() {
        return Array.isArray(this._json.classifiers) ? this._json.classifiers : undefined;
    }
}
exports.SpatialClassifiers = SpatialClassifiers;
function insideDisplay(display) {
    switch (display) {
        case SpatialClassifierInsideDisplay.Off:
        case SpatialClassifierInsideDisplay.On:
        case SpatialClassifierInsideDisplay.Dimmed:
        case SpatialClassifierInsideDisplay.Hilite:
        case SpatialClassifierInsideDisplay.ElementColor:
            return display;
        default:
            return SpatialClassifierInsideDisplay.ElementColor;
    }
}
function outsideDisplay(display) {
    switch (display) {
        case SpatialClassifierOutsideDisplay.Off:
        case SpatialClassifierOutsideDisplay.On:
        case SpatialClassifierOutsideDisplay.Dimmed:
            return display;
        default:
            return SpatialClassifierOutsideDisplay.Dimmed;
    }
}
//# sourceMappingURL=SpatialClassification.js.map