/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Models
 */
// cspell:ignore elid
import { JsonUtils } from "@itwin/core-bentley";
import { Point2d, Range3d } from "@itwin/core-geometry";
import { IModel, RelatedElement, } from "@itwin/core-common";
import { DefinitionPartition, DocumentPartition, InformationRecordPartition, PhysicalPartition, SheetIndexPartition, SpatialLocationPartition } from "./Element";
import { Entity } from "./Entity";
import { SubjectOwnsPartitionElements } from "./NavigationRelationship";
import { _cache, _nativeDb, _verifyChannel } from "./internal/Symbols";
/** A Model is a container for persisting a collection of related elements within an iModel.
 * See [[IModelDb.Models]] for how to query and manage the Models in an IModelDb.
 * See [Creating models]($docs/learning/backend/CreateModels.md)
 * @public @preview
 */
export class Model extends Entity {
    static get className() { return "Model"; }
    /** @internal */
    static get protectedOperations() { return ["onInsert", "onUpdate", "onDelete"]; }
    modeledElement;
    name;
    parentModel;
    jsonProperties;
    isPrivate;
    isTemplate;
    constructor(props, iModel) {
        super(props, iModel);
        this.modeledElement = new RelatedElement(props.modeledElement);
        this.name = props.name ? props.name : ""; // NB this isn't really a property of Model (it's the code.value of the modeled element), but it comes in ModelProps because it's often needed
        this.parentModel = props.parentModel;
        this.isPrivate = JsonUtils.asBool(props.isPrivate);
        this.isTemplate = JsonUtils.asBool(props.isTemplate);
        this.jsonProperties = { ...props.jsonProperties }; // make sure we have our own copy
    }
    /**
     * Model custom HandledProps includes 'isPrivate', 'isTemplate', and 'lastMod'.
     * @inheritdoc
     * @beta
     */
    static _customHandledProps = [
        { propertyName: "isPrivate", source: "Class" },
        { propertyName: "isTemplate", source: "Class" },
        { propertyName: "lastMod", source: "Class" },
    ];
    /**
     * Model deserializes 'isPrivate', and 'isTemplate', and sets the proper parentModel.
     * @inheritdoc
     * @beta
     */
    static deserialize(props) {
        const instance = props.row;
        const modelProps = super.deserialize(props);
        const modeledElementProps = props.iModel.elements.tryGetElementProps(instance.modeledElement.id);
        if (modeledElementProps) {
            // ModeledElement may be undefined in the case of root Element
            modelProps.name = JsonUtils.asString(modeledElementProps.code.value);
            if (instance.parentModel !== undefined)
                modelProps.parentModel = instance.parentModel.id;
            else
                modelProps.parentModel = modeledElementProps.model;
        }
        if (instance.isPrivate === true)
            modelProps.isPrivate = true;
        if (instance.isTemplate === true)
            modelProps.isTemplate = true;
        return modelProps;
    }
    /**
     * Model serializes 'isPrivate', and 'isTemplate'.
     * @inheritdoc
     * @beta
     */
    static serialize(props, _iModel) {
        const inst = super.serialize(props, _iModel);
        inst.isPrivate = props.isPrivate ?? false;
        inst.isTemplate = props.isTemplate ?? false;
        return inst;
    }
    toJSON() {
        const val = super.toJSON();
        val.name = this.name; // for cloning
        return val;
    }
    /** Called before a new Model is inserted.
     * @note throw an exception to disallow the insert
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model to be inserted
     * @beta
     */
    static onInsert(arg) {
        const { props, iModel } = arg;
        iModel.channels[_verifyChannel](props.modeledElement.id);
        if (props.parentModel) // inserting requires shared lock on parent, if present
            iModel.locks.checkSharedLock(props.parentModel, "parent model", "insert");
    }
    /** Called after a new Model is inserted.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model that was inserted
     * @beta
     */
    static onInserted(_arg) {
        // we don't need to tell LockControl about models being created - their ModeledElement does that
    }
    /** Called before a Model is updated.
     * @note throw an exception to disallow the update
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model to be updated
     * @beta
     */
    static onUpdate(arg) {
        const id = arg.props.id; // eslint-disable-line @typescript-eslint/no-non-null-assertion
        arg.iModel.channels[_verifyChannel](id);
        arg.iModel.locks.checkExclusiveLock(id, "model", "update");
    }
    /** Called after a Model is updated.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model that was updated.
     * @beta
     */
    static onUpdated(arg) {
        arg.iModel.models[_cache].delete(arg.id);
    }
    /** Called before a Model is deleted.
     * @note throw an exception to disallow the delete
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model to be deleted
     * @beta
     */
    static onDelete(arg) {
        arg.iModel.channels[_verifyChannel](arg.id);
        arg.iModel.locks.checkExclusiveLock(arg.id, "model", "delete");
    }
    /** Called after a Model was deleted.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model that was deleted
     * @beta
     */
    static onDeleted(arg) {
        arg.iModel.models[_cache].delete(arg.id);
        arg.iModel.elements[_cache].deleteWithModel(arg.id);
    }
    /** Called before a prospective Element is to be inserted into an instance of a Model of this class.
     * @note throw an exception to disallow the insert
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model to hold the element
     * @beta
     */
    static onInsertElement(_arg) { }
    /** Called after an Element has been inserted into an instance of a Model of this class.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model holding the element
     * @beta
     */
    static onInsertedElement(arg) {
        arg.iModel.models[_cache].delete(arg.id);
    }
    /** Called when an Element in an instance of a Model of this class is about to be updated.
     * @note throw an exception to disallow the update
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model holding the element
     * @beta
     */
    static onUpdateElement(_arg) { }
    /** Called after an Element in an instance of a Model of this class has been updated.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model holding the element
     * @beta
     */
    static onUpdatedElement(arg) {
        arg.iModel.models[_cache].delete(arg.id);
    }
    /** Called when an Element in an instance of a Model of this class is about to be deleted.
     * @note throw an exception to disallow the delete
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model holding the element
     * @beta
     */
    static onDeleteElement(_arg) { }
    /** Called after an Element in an instance of a Model of this class has been deleted.
     * @note If you override this method, you must call super.
     * @note `this` is the class of the Model that held the element
     * @beta
     */
    static onDeletedElement(arg) {
        arg.iModel.models[_cache].delete(arg.id);
    }
    getAllUserProperties() {
        if (!this.jsonProperties.UserProps)
            this.jsonProperties.UserProps = new Object();
        return this.jsonProperties.UserProps;
    }
    /** Get a set of JSON user properties by namespace */
    getUserProperties(namespace) { return this.getAllUserProperties()[namespace]; }
    /** Change a set of user JSON properties of this Element by namespace. */
    setUserProperties(nameSpace, value) { this.getAllUserProperties()[nameSpace] = value; }
    /** Remove a set of JSON user properties, specified by namespace, from this Element */
    removeUserProperties(nameSpace) { delete this.getAllUserProperties()[nameSpace]; }
    getJsonProperty(name) { return this.jsonProperties[name]; }
    setJsonProperty(name, value) { this.jsonProperties[name] = value; }
    /** Insert this Model in the iModel */
    insert() { return this.id = this.iModel.models.insertModel(this.toJSON()); }
    /** Update this Model in the iModel. */
    update() { this.iModel.models.updateModel(this.toJSON()); }
    /** Delete this Model from the iModel. */
    delete() { this.iModel.models.deleteModel(this.id); }
    collectReferenceIds(referenceIds) {
        super.collectReferenceIds(referenceIds);
        if (this.parentModel)
            referenceIds.addModel(this.parentModel);
        referenceIds.addElement(this.modeledElement.id);
    }
}
/** A container for persisting geometric elements.
 * @public @preview
 */
export class GeometricModel extends Model {
    geometryGuid;
    static get className() { return "GeometricModel"; }
    constructor(props, iModel) {
        super(props, iModel);
        this.geometryGuid = props.geometryGuid;
    }
    /** Query for the union of the extents of the elements contained by this model.
     * @note This function blocks the JavaScript event loop. Consider using [[queryRange]] instead.
     */
    queryExtents() {
        const extents = this.iModel[_nativeDb].queryModelExtents({ id: this.id }).modelExtents;
        return Range3d.fromJSON(extents);
    }
    /** Query for the union of the extents of all elements contained within this model. */
    async queryRange() {
        return this.iModel.models.queryRange(this.id);
    }
}
/** A container for persisting 3d geometric elements.
 * @public @preview
 */
export class GeometricModel3d extends GeometricModel {
    /** If true, then the elements in this GeometricModel3d are expected to be in an XY plane.
     * @note The associated ECProperty was added to the BisCore schema in version 1.0.8
     */
    isPlanProjection;
    /** If true, then the elements in this GeometricModel3d are not in real-world coordinates and will not be in the spatial index.
     * @note The associated ECProperty was added to the BisCore schema in version 1.0.8
     */
    isNotSpatiallyLocated;
    /** If true, then the elements in this GeometricModel3d are in real-world coordinates and will be in the spatial index. */
    get isSpatiallyLocated() { return !this.isNotSpatiallyLocated; }
    static get className() { return "GeometricModel3d"; }
    constructor(props, iModel) {
        super(props, iModel);
        this.isNotSpatiallyLocated = JsonUtils.asBool(props.isNotSpatiallyLocated);
        this.isPlanProjection = JsonUtils.asBool(props.isPlanProjection);
    }
    /**
     * GeometricModel3d custom HandledProps includes 'isPlanProjection', and 'isNotSpatiallyLocated'.
     * @inheritdoc
     * @beta
     */
    static _customHandledProps = [
        { propertyName: "isPlanProjection", source: "Class" },
        { propertyName: "isNotSpatiallyLocated", source: "Class" },
    ];
    /**
     * GeometricModel3d deserializes 'isPlanProjection', and 'isNotSpatiallyLocated'.
     * @inheritdoc
     * @beta
     */
    static deserialize(props) {
        const modelProps = super.deserialize(props);
        const instance = props.row;
        if (instance.isNotSpatiallyLocated === true || instance.isTemplate === true)
            modelProps.isNotSpatiallyLocated = true;
        if (instance.isPlanProjection === true)
            modelProps.isPlanProjection = true;
        return modelProps;
    }
    /**
     * GeometricModel3d serializes 'isPlanProjection', and 'isNotSpatiallyLocated'.
     * @inheritdoc
     * @beta
     */
    static serialize(props, _iModel) {
        const inst = super.serialize(props, _iModel);
        inst.isNotSpatiallyLocated = props.isNotSpatiallyLocated ?? false;
        inst.isPlanProjection = props.isPlanProjection ?? false;
        return inst;
    }
    toJSON() {
        const val = super.toJSON();
        if (this.isNotSpatiallyLocated)
            val.isNotSpatiallyLocated = true;
        if (this.isPlanProjection)
            val.isPlanProjection = true;
        return val;
    }
}
/** A container for persisting 2d geometric elements.
 * @public @preview
 */
export class GeometricModel2d extends GeometricModel {
    /** The actual coordinates of (0,0) in modeling coordinates. An offset applied to all modeling coordinates. */
    globalOrigin;
    static get className() { return "GeometricModel2d"; }
    constructor(props, iModel) {
        super(props, iModel);
        this.globalOrigin = props.globalOrigin ? Point2d.fromJSON(props.globalOrigin) : undefined;
    }
    toJSON() {
        const val = super.toJSON();
        if (undefined !== this.globalOrigin)
            val.globalOrigin = Point2d.fromJSON(this.globalOrigin);
        return val;
    }
}
/** A container for persisting 2d graphical elements.
 * @public @preview
 */
export class GraphicalModel2d extends GeometricModel2d {
    static get className() { return "GraphicalModel2d"; }
}
/** A container for persisting GraphicalElement3d instances.
 * @note The associated ECClass was added to the BisCore schema in version 1.0.8
 * @see [[GraphicalPartition3d]]
 * @public @preview
 */
export class GraphicalModel3d extends GeometricModel3d {
    static get className() { return "GraphicalModel3d"; }
}
/** A container for persisting 3d geometric elements that are spatially located.
 * @public @preview
 */
export class SpatialModel extends GeometricModel3d {
    static get className() { return "SpatialModel"; }
}
/** A container for persisting physical elements that model physical space.
 * @see [[PhysicalPartition]]
 * @public @preview
 */
export class PhysicalModel extends SpatialModel {
    static get className() { return "PhysicalModel"; }
    /** Insert a PhysicalPartition and a PhysicalModel that sub-models it.
     * @param iModelDb Insert into this iModel
     * @param parentSubjectId The PhysicalPartition will be inserted as a child of this Subject element.
     * @param name The name of the PhysicalPartition that the new PhysicalModel will sub-model.
     * @param isPlanProjection Optional value (default is false) that indicates if the contents of this model are expected to be in an XY plane.
     * @returns The Id of the newly inserted PhysicalPartition and PhysicalModel (same value).
     * @throws [[IModelError]] if there is an insert problem.
     */
    static insert(iModelDb, parentSubjectId, name, isPlanProjection) {
        const partitionProps = {
            classFullName: PhysicalPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: PhysicalPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(partitionProps);
        const modelProps = {
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
            isPlanProjection,
        };
        return iModelDb.models.insertModel(modelProps);
    }
}
/** A container for persisting spatial location elements.
 * @see [[SpatialLocationPartition]]
 * @public @preview
 */
export class SpatialLocationModel extends SpatialModel {
    static get className() { return "SpatialLocationModel"; }
    /** Insert a SpatialLocationPartition and a SpatialLocationModel that sub-models it.
     * @param iModelDb Insert into this iModel
     * @param parentSubjectId The SpatialLocationPartition will be inserted as a child of this Subject element.
     * @param name The name of the SpatialLocationPartition that the new SpatialLocationModel will sub-model.
     * @param isPlanProjection Optional value (default is false) that indicates if the contents of this model are expected to be in an XY plane.
     * @returns The Id of the newly inserted SpatialLocationPartition and SpatialLocationModel (same value).
     * @throws [[IModelError]] if there is an insert problem.
     */
    static insert(iModelDb, parentSubjectId, name, isPlanProjection) {
        const partitionProps = {
            classFullName: SpatialLocationPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: SpatialLocationPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(partitionProps);
        const modelProps = {
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
            isPlanProjection,
        };
        return iModelDb.models.insertModel(modelProps);
    }
}
/** A 2d model that holds [[DrawingGraphic]]s. DrawingModels may be dimensional or non-dimensional.
 * @public @preview
 */
export class DrawingModel extends GraphicalModel2d {
    static get className() { return "DrawingModel"; }
}
/** A container for persisting section [[DrawingGraphic]]s.
 * @public @preview
 */
export class SectionDrawingModel extends DrawingModel {
    static get className() { return "SectionDrawingModel"; }
}
/** A container for persisting [[ViewAttachment]]s and [[DrawingGraphic]]s.
 * A SheetModel is a digital representation of a *sheet of paper*. SheetModels are 2d models in bounded paper coordinates.
 * SheetModels may contain annotation Elements as well as references to 2d or 3d Views.
 * @public @preview
 */
export class SheetModel extends GraphicalModel2d {
    static get className() { return "SheetModel"; }
}
/** A container for persisting role elements.
 * @public @preview
 */
export class RoleModel extends Model {
    static get className() { return "RoleModel"; }
}
/** A container for persisting information elements.
 * @public @preview
 */
export class InformationModel extends Model {
    static get className() { return "InformationModel"; }
}
/** A container for persisting group information elements.
 * @see [[GroupInformationPartition]]
 * @public @preview
 */
export class GroupInformationModel extends InformationModel {
    static get className() { return "GroupInformationModel"; }
}
/** A sub-model of a [[SheetIndexPartition]] serving as a container for persisting [[SheetIndexEntry]] and [[SheetIndex]] elements.
 * @beta
 */
export class SheetIndexModel extends InformationModel {
    static get className() { return "SheetIndexModel"; }
    /** Insert a SheetIndex and a SheetIndexModel that sub-models it.
   * @param iModelDb Insert into this iModel
   * @param parentSubjectId The SheetIndex will be inserted as a child of this Subject element.
   * @param name The name of the SheetIndex that the new SheetIndexModel will sub-model.
   * @returns The Id of the newly inserted SheetIndexModel.
   * @throws [[IModelError]] if there is an insert problem.
   */
    static insert(iModelDb, parentSubjectId, name) {
        const sheetIndex = {
            classFullName: SheetIndexPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: SheetIndexPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(sheetIndex);
        return iModelDb.models.insertModel({
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
        });
    }
}
/** A container for persisting Information Record Elements
 * @see [[InformationRecordPartition]]
 * @public @preview
 */
export class InformationRecordModel extends InformationModel {
    static get className() { return "InformationRecordModel"; }
    /** Insert a InformationRecordPartition and a InformationRecordModel that sub-models it.
     * @param iModelDb Insert into this iModel
     * @param parentSubjectId The InformationRecordPartition will be inserted as a child of this Subject element.
     * @param name The name of the InformationRecordPartition that the new InformationRecordModel will sub-model.
     * @returns The Id of the newly inserted InformationRecordModel.
     * @throws [[IModelError]] if there is an insert problem.
     */
    static insert(iModelDb, parentSubjectId, name) {
        const partitionProps = {
            classFullName: InformationRecordPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: InformationRecordPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(partitionProps);
        return iModelDb.models.insertModel({
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
        });
    }
}
/** A container for persisting definition elements.
 * @see [[DefinitionPartition]]
 * @public @preview
 */
export class DefinitionModel extends InformationModel {
    static get className() { return "DefinitionModel"; }
    /** Insert a DefinitionPartition and a DefinitionModel that sub-models it.
     * @param iModelDb Insert into this iModel
     * @param parentSubjectId The DefinitionPartition will be inserted as a child of this Subject element.
     * @param name The name of the DefinitionPartition that the new DefinitionModel will sub-model.
     * @returns The Id of the newly inserted DefinitionModel.
     * @throws [[IModelError]] if there is an insert problem.
     */
    static insert(iModelDb, parentSubjectId, name) {
        const partitionProps = {
            classFullName: DefinitionPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: DefinitionPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(partitionProps);
        return iModelDb.models.insertModel({
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
        });
    }
}
/** The singleton container of repository-related information elements.
 * @public @preview
 */
export class RepositoryModel extends DefinitionModel {
    static get className() { return "RepositoryModel"; }
}
/** Contains a list of document elements.
 * @see [[DocumentPartition]]
 * @public @preview
 */
export class DocumentListModel extends InformationModel {
    static get className() { return "DocumentListModel"; }
    /** Insert a DocumentPartition and a DocumentListModel that sub-models it.
     * @param iModelDb Insert into this iModel
     * @param parentSubjectId The DocumentPartition will be inserted as a child of this Subject element.
     * @param name The name of the DocumentPartition that the new DocumentListModel will sub-model.
     * @returns The Id of the newly inserted DocumentPartition and DocumentListModel (same value)
     * @throws [[IModelError]] if there is an insert problem.
     */
    static insert(iModelDb, parentSubjectId, name) {
        const partitionProps = {
            classFullName: DocumentPartition.classFullName,
            model: IModel.repositoryModelId,
            parent: new SubjectOwnsPartitionElements(parentSubjectId),
            code: DocumentPartition.createCode(iModelDb, parentSubjectId, name),
        };
        const partitionId = iModelDb.elements.insertElement(partitionProps);
        return iModelDb.models.insertModel({
            classFullName: this.classFullName,
            modeledElement: { id: partitionId },
        });
    }
}
/** A container for persisting link elements.
 * @see [[LinkPartition]]
 * @public @preview
 */
export class LinkModel extends InformationModel {
    static get className() { return "LinkModel"; }
}
/** The singleton container for repository-specific definition elements.
 * @public @preview
 */
export class DictionaryModel extends DefinitionModel {
    static get className() { return "DictionaryModel"; }
}
/** Obtains and displays multi-resolution tiled raster organized according to the WebMercator tiling system.
 * @public @preview
 */
export class WebMercatorModel extends SpatialModel {
    static get className() { return "WebMercatorModel"; }
}
//# sourceMappingURL=Model.js.map