"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 ElementAspects
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExternalSourceAspect = exports.SheetInformationAspect = exports.ChannelRootAspect = exports.ElementMultiAspect = exports.ElementUniqueAspect = exports.ElementAspect = void 0;
const core_common_1 = require("@itwin/core-common");
const Entity_1 = require("./Entity");
const core_bentley_1 = require("@itwin/core-bentley");
const Symbols_1 = require("./internal/Symbols");
const NavigationRelationship_1 = require("./NavigationRelationship");
const Element_1 = require("./Element");
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
/** An Element Aspect is a class that defines a set of properties that are related to (and owned by) a single element.
 * Semantically, an ElementAspect can be considered part of the Element. Thus, an ElementAspect is deleted if its owning Element is deleted.
 * BIS Guideline: Subclass ElementUniqueAspect or ElementMultiAspect rather than subclassing ElementAspect directly.
 * @public @preview
 */
class ElementAspect extends Entity_1.Entity {
    static get className() { return "ElementAspect"; }
    element;
    /** Construct an aspect from its JSON representation and its containing iModel. */
    constructor(props, iModel) {
        super(props, iModel);
        this.element = core_common_1.RelatedElement.fromJSON(props.element); // eslint-disable-line @typescript-eslint/no-non-null-assertion
    }
    toJSON() {
        const val = super.toJSON();
        val.element = this.element;
        return val;
    }
    /** Called before a new ElementAspect is inserted.
     * @note throw an exception to disallow the insert
     * @note If you override this method, you must call super.
     * @beta
     */
    static onInsert(arg) {
        const { props, iModel } = arg;
        iModel.channels[Symbols_1._verifyChannel](arg.model);
        iModel.locks.checkExclusiveLock(props.element.id, "element", "insert aspect");
    }
    /** Called after a new ElementAspect was inserted.
     * @note If you override this method, you must call super.
     * @beta
     */
    static onInserted(_arg) { }
    /** Called before an ElementAspect is updated.
     * @note throw an exception to disallow the update
     * @note If you override this method, you must call super.
     * @beta
     */
    static onUpdate(arg) {
        const { props, iModel } = arg;
        iModel.channels[Symbols_1._verifyChannel](arg.model);
        iModel.locks.checkExclusiveLock(props.element.id, "element", "update aspect");
    }
    /** Called after an ElementAspect was updated.
     * @note If you override this method, you must call super.
     * @beta
     */
    static onUpdated(_arg) { }
    /** Called before an ElementAspect is deleted.
     * @note throw an exception to disallow the delete
     * @note If you override this method, you must call super.
     * @beta
     */
    static onDelete(arg) {
        const { aspectId, iModel } = arg;
        iModel.channels[Symbols_1._verifyChannel](arg.model);
        const { element } = iModel.elements.getAspect(aspectId);
        iModel.locks.checkExclusiveLock(element.id, "element", "delete aspect");
    }
    /** Called after an ElementAspect was deleted.
     * @note If you override this method, you must call super.
     * @beta
     */
    static onDeleted(_arg) { }
}
exports.ElementAspect = ElementAspect;
/** An Element Unique Aspect is an ElementAspect where there can be only zero or one instance of the Element Aspect class per Element.
 * @public @preview
 */
class ElementUniqueAspect extends ElementAspect {
    static get className() { return "ElementUniqueAspect"; }
}
exports.ElementUniqueAspect = ElementUniqueAspect;
/** An Element Multi-Aspect is an ElementAspect where there can be **n** instances of the Element Aspect class per Element.
 * @public @preview
 */
class ElementMultiAspect extends ElementAspect {
    static get className() { return "ElementMultiAspect"; }
}
exports.ElementMultiAspect = ElementMultiAspect;
/**
 * @public @preview
 */
class ChannelRootAspect extends ElementUniqueAspect {
    static get className() { return "ChannelRootAspect"; }
    /** Insert a ChannelRootAspect on the specified element.
     * @deprecated in 4.0 - will not be removed until after 2026-06-13. Use [[ChannelControl.makeChannelRoot]]. This method does not enforce the rule that channels may not nest and is therefore dangerous.
     */
    static insert(iModel, ownerId, channelName) {
        const props = { classFullName: this.classFullName, element: { id: ownerId }, owner: channelName };
        iModel.elements.insertAspect(props);
    }
}
exports.ChannelRootAspect = ChannelRootAspect;
const minimumBisCoreVersion = new ecschema_metadata_1.ECVersion(1, 0, 25);
/** An [[ElementUniqueAspect]] that captures common metadata about a single [[Sheet]].
 * Use [[getSheetInformation]] to retrieve the metadata for a Sheet and [[setSheetInformation]] to create, update, or delete it.
 * @beta
 */
class SheetInformationAspect extends ElementUniqueAspect {
    static get className() { return "SheetInformationAspect"; }
    /** The sheet's metadata. */
    sheetInformation;
    static onInsert(arg) {
        super.onInsert(arg);
        const sheet = arg.iModel.elements.tryGetElement(arg.props.element);
        if (!(sheet instanceof Element_1.Sheet)) {
            throw new Error("SheetInformationAspect can only be applied to a Sheet element");
        }
    }
    constructor(props, iModel) {
        super(props, iModel);
        const designedDate = undefined !== props.designedDate ? new Date(props.designedDate) : undefined;
        this.sheetInformation = {
            designedBy: props.designedBy,
            designedDate,
            drawnBy: props.drawnBy,
            checkedBy: props.checkedBy,
        };
    }
    toJSON() {
        const props = super.toJSON();
        for (const key of ["designedBy", "drawnBy", "checkedBy"]) {
            const value = this.sheetInformation[key];
            if (undefined !== value) {
                props[key] = value;
            }
        }
        if (undefined !== this.sheetInformation.designedDate) {
            props.designedDate = this.sheetInformation.designedDate.toISOString();
        }
        return props;
    }
    static findForSheet(sheetId, iModel) {
        if (!iModel.meetsMinimumSchemaVersion("BisCore", minimumBisCoreVersion)) {
            return undefined;
        }
        const aspects = iModel.elements.getAspects(sheetId, this.classFullName);
        if (aspects[0]) {
            (0, core_bentley_1.assert)(aspects[0] instanceof SheetInformationAspect);
            return aspects[0];
        }
        return undefined;
    }
    /** Retrieves the metadata hosted by the aspect on the specified sheet, returning `undefined` if no such metadata could be retrieved.
     * @see [[setSheetInformation]] to create, update, or delete the aspect.
     */
    static getSheetInformation(sheetId, iModel) {
        const aspect = this.findForSheet(sheetId, iModel);
        return aspect?.sheetInformation;
    }
    /** Sets the `information` for the [[Sheet]] element specified by ``sheetId`.
     * If `information` is `undefined`, any existing aspect will be deleted.
     * Otherwise, a new aspect will be inserted, or an existing aspect will be updated with the new metadata.
     * @throws Error if the iModel contains a version of the BisCore schema older than 01.00.25.
     */
    static setSheetInformation(information, sheetId, iModel) {
        iModel.requireMinimumSchemaVersion("BisCore", minimumBisCoreVersion, "SheetInformationAspect");
        const aspect = this.findForSheet(sheetId, iModel);
        if (!information) {
            if (aspect) {
                iModel.elements.deleteAspect(aspect.id);
            }
            return;
        }
        if (aspect) {
            aspect.sheetInformation = { ...information };
            iModel.elements.updateAspect(aspect.toJSON());
        }
        else {
            const info = { ...information };
            for (const key of Object.keys(info)) {
                if (info[key] === undefined) {
                    delete info[key];
                }
            }
            if (undefined !== info.designedDate) {
                info.designedDate = info.designedDate.toISOString();
            }
            const props = {
                classFullName: this.classFullName,
                element: {
                    id: sheetId,
                    relClassName: NavigationRelationship_1.SheetOwnsSheetInformationAspect.classFullName,
                },
                ...info,
            };
            iModel.elements.insertAspect(props);
        }
    }
}
exports.SheetInformationAspect = SheetInformationAspect;
/** An ElementMultiAspect that stores synchronization information for an Element originating from an external source.
 * @note The associated ECClass was added to the BisCore schema in version 1.0.2
 * @public @preview
 */
class ExternalSourceAspect extends ElementMultiAspect {
    static get className() { return "ExternalSourceAspect"; }
    /** An element that scopes the combination of `kind` and `identifier` to uniquely identify the object from the external source.
     * @note Warning: in a future major release the `scope` property will be optional, since the scope is intended to be potentially invalid.
     *       all references should treat it as potentially undefined, but we cannot change the type yet since that is a breaking change.
     */
    scope;
    /** The identifier of the object in the source repository. */
    identifier;
    /** The kind of object within the source repository. */
    kind;
    /** The cryptographic hash (any algorithm) of the source object's content. If defined, it must be guaranteed to change when the source object's content changes. */
    checksum;
    /** An optional value that is typically a version number or a pseudo version number like last modified time.
     * It will be used by the synchronization process to detect that a source object is unchanged so that computing a cryptographic hash can be avoided.
     * If present, this value must be guaranteed to change when any of the source object's content changes.
     */
    version;
    /** A place where additional JSON properties can be stored. For example, provenance information or properties relating to the synchronization process.
     * @note Warning: in a future major release, the type of `jsonProperties` will be changed to object, and itwin.js will automatically stringify it when writing to the iModel.
     * This will be a breaking change, since application code will have to change from supplying a string to supplying an object.
     */
    jsonProperties;
    /** The source of the imported/synchronized object. Should point to an instance of [ExternalSource]($backend). */
    source;
    /** Construct an aspect from its JSON representation and its containing iModel. */
    constructor(props, iModel) {
        super(props, iModel);
        this.scope = core_common_1.RelatedElement.fromJSON(props.scope); // eslint-disable-line @typescript-eslint/no-non-null-assertion
        this.source = core_common_1.RelatedElement.fromJSON(props.source);
        this.identifier = props.identifier;
        this.kind = props.kind;
        this.checksum = props.checksum;
        this.version = props.version;
        this.jsonProperties = props.jsonProperties;
    }
    /** Look up the elements that contain one or more ExternalSourceAspect with the specified Scope, Kind, and Identifier.
     * The result of this function is an array of all of the ExternalSourceAspects that were found, each associated with the owning element.
     * A given element could have more than one ExternalSourceAspect with the given scope, kind, and identifier.
     * Also, many elements could have ExternalSourceAspect with the same scope, kind, and identifier.
     * Therefore, the result array could have more than one entry with the same elementId.
     * Aspects are never shared. Each aspect has its own unique ECInstanceId.
     * @param iModelDb The iModel to query
     * @param scope    The scope of the ExternalSourceAspects to find
     * @param kind     The kind of the ExternalSourceAspects to find
     * @param identifier The identifier of the ExternalSourceAspects to find
     * @returns the query results
    */
    static findAllBySource(iModelDb, scope, kind, identifier) {
        const sql = `SELECT Element.Id, ECInstanceId FROM ${ExternalSourceAspect.classFullName} WHERE (Scope.Id=:scope AND Kind=:kind AND Identifier=:identifier)`;
        const found = [];
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(sql, (statement) => {
            statement.bindId("scope", scope);
            statement.bindString("kind", kind);
            statement.bindString("identifier", identifier);
            while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
                found.push({ elementId: statement.getValue(0).getId(), aspectId: statement.getValue(1).getId() });
            }
        });
        return found;
    }
    toJSON() {
        const val = super.toJSON();
        val.scope = this.scope;
        val.source = this.source;
        val.identifier = this.identifier;
        val.kind = this.kind;
        val.checksum = this.checksum;
        val.version = this.version;
        val.jsonProperties = this.jsonProperties;
        return val;
    }
    collectReferenceIds(referenceIds) {
        super.collectReferenceIds(referenceIds);
        if (this.scope)
            referenceIds.addElement(this.scope.id);
        referenceIds.addElement(this.element.id);
        if (this.source)
            referenceIds.addElement(this.source.id);
    }
}
exports.ExternalSourceAspect = ExternalSourceAspect;
/** @public @preview */
(function (ExternalSourceAspect) {
    /** Standard values for the `Kind` property of `ExternalSourceAspect`.
     * @public @preview
     */
    let Kind;
    (function (Kind) {
        /** Indicates that the [[ExternalSourceAspect]] is storing [[Element]] provenance */
        Kind["Element"] = "Element";
        /** Indicates that the [[ExternalSourceAspect]] is storing [[Relationship]] provenance */
        Kind["Relationship"] = "Relationship";
        /** Indicates that the [[ExternalSourceAspect]] is storing *scope* provenance
         * @see [[ExternalSourceAspect.scope]]
         */
        Kind["Scope"] = "Scope";
    })(Kind = ExternalSourceAspect.Kind || (ExternalSourceAspect.Kind = {}));
})(ExternalSourceAspect || (exports.ExternalSourceAspect = ExternalSourceAspect = {}));
//# sourceMappingURL=ElementAspect.js.map