/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Elements
 */
import { RelatedElement, traverseTextBlockComponent } from "@itwin/core-common";
import { ElementDrivesElement } from "../Relationship";
import { createUpdateContext, updateAllFields, updateElementFields, updateFields } from "../internal/annotations/fields";
import { DbResult, Id64 } from "@itwin/core-bentley";
import { ECVersion } from "@itwin/ecschema-metadata";
// ElementDrivesTextAnnotation was introduced in this version of BisCore - iModels with earlier versions cannot support field dependencies.
const minBisCoreVersion = new ECVersion(1, 0, 22);
/** Returns `true` if the specified `element` implements [[ITextAnnotation]].
 * @beta
 */
export function isITextAnnotation(element) {
    return ["getTextBlocks", "updateTextBlocks"].every((x) => x in element && typeof element[x] === "function");
}
/** A relationship in which the source element hosts one or more properties that are displayed by a target [[ITextAnnotation]] element.
 * This relationship is used to automatically update the [FieldRun]($common)s contained in the target element when the source element is modified.
 * An [[ITextAnnotation]] element should invoke [[updateFieldDependencies]] from its [[Element.onInserted]] and [[Element.onUpdated]] functions to
 * establish or update the relationships required for the [FieldRun]($common)s it contains.
 * @note This relationship was introduced in version 01.00.22 of the BisCore schema. [FieldRun]($common)s created in iModels that have not been upgraded to
 * that version or newer will not automatically update. Use [[isSupportedForIModel]] to check.
 * @beta
 */
export class ElementDrivesTextAnnotation extends ElementDrivesElement {
    static get className() { return "ElementDrivesTextAnnotation"; }
    /** @internal */
    static onRootChanged(props, iModel) {
        updateElementFields(props, iModel, false);
    }
    /** @internal */
    static onDeletedDependency(props, iModel) {
        updateElementFields(props, iModel, true);
    }
    /** Returns true if `iModel` contains a version of the BisCore schema new enough to support this relationship.
     * If not, the schema should be updated before inserting any [FieldRun]($common)s, or those runs will not
     * update when the source element changes.
     */
    static isSupportedForIModel(iModel) {
        return iModel.meetsMinimumSchemaVersion("BisCore", minBisCoreVersion);
    }
    /** Examines all of the [FieldRun]($common)s within the specified [[ITextAnnotation]] and ensures that the appropriate
     * `ElementDrivesTextAnnotation` relationships exist between the fields' source elements and this target element.
     * It also deletes any stale relationships left over from fields that were deleted or whose source elements changed.
     */
    static updateFieldDependencies(annotationElementId, iModel) {
        const annotationElement = iModel.elements.tryGetElement(annotationElementId);
        if (!annotationElement || !isITextAnnotation(annotationElement)) {
            return;
        }
        // The native layer will allow us to insert relationships to invalid or non-existent source elements...errors will arise later. Prevent it.
        function isValidSourceId(id) {
            if (!Id64.isValidId64(id)) {
                return false;
            }
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            return iModel.withPreparedStatement("SELECT CodeValue FROM BisCore.Element WHERE ECInstanceId=?", (stmt) => {
                stmt.bindId(1, id);
                return DbResult.BE_SQLITE_ROW === stmt.step();
            });
        }
        const sourceToRelationship = new Map();
        const blocks = annotationElement.getTextBlocks();
        let haveFields = false;
        for (const block of blocks) {
            for (const { child } of traverseTextBlockComponent(block.textBlock)) {
                if (child.type === "field") {
                    haveFields = true;
                    if (isValidSourceId(child.propertyHost.elementId)) {
                        sourceToRelationship.set(child.propertyHost.elementId, null);
                    }
                }
            }
        }
        if (haveFields) {
            iModel.requireMinimumSchemaVersion("BisCore", minBisCoreVersion, "Text fields");
            updateAllFields(annotationElementId, iModel);
        }
        const staleRelationships = new Set();
        if (this.isSupportedForIModel(iModel)) {
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            annotationElement.iModel.withPreparedStatement(`SELECT ECInstanceId, SourceECInstanceId FROM BisCore.ElementDrivesTextAnnotation WHERE TargetECInstanceId=${annotationElement.id}`, (stmt) => {
                while (DbResult.BE_SQLITE_ROW === stmt.step()) {
                    const relationshipId = stmt.getValue(0).getId();
                    const sourceId = stmt.getValue(1).getId();
                    if (sourceToRelationship.has(sourceId)) {
                        sourceToRelationship.set(sourceId, relationshipId);
                    }
                    else {
                        staleRelationships.add(relationshipId);
                    }
                }
            });
        }
        for (const [sourceId, relationshipId] of sourceToRelationship) {
            if (relationshipId === null) {
                ElementDrivesTextAnnotation.create(annotationElement.iModel, sourceId, annotationElement.id).insert();
            }
        }
        if (staleRelationships.size > 0) {
            const staleRelationshipProps = Array.from(staleRelationships).map(relationshipId => annotationElement.iModel.relationships.getInstanceProps("BisCore.ElementDrivesTextAnnotation", relationshipId));
            annotationElement.iModel.relationships.deleteInstances(staleRelationshipProps);
        }
    }
    /** Recompute the display strings of all [FieldRun]($common)s in a [TextBlock]($common).
     * @returns the number of fields whose display strings were modified.
     * @throws Error if evaluation of any field fails.
     */
    static evaluateFields(args) {
        return updateFields(args.block, createUpdateContext(undefined, args.iModel, false));
    }
    /** When copying an [[ITextAnnotation]] from one iModel into another, remaps the element Ids in any [FieldPropertyHost]($common) within the cloned element
     * so that they refer to elements in the `context`'s target iModel, and sets any Ids that cannot be remapped to [Id64.invalid]($bentley).
     * Implementations of `ITextAnnotation` should invoke this function from their implementations of [[Element.onCloned]].
     */
    static remapFields(clone, context) {
        if (!context.isBetweenIModels) {
            return;
        }
        const updatedBlocks = [];
        for (const block of clone.getTextBlocks()) {
            let anyUpdated = false;
            for (const { child } of traverseTextBlockComponent(block.textBlock)) {
                if (child.type === "field") {
                    child.propertyHost.elementId = context.findTargetElementId(child.propertyHost.elementId);
                    anyUpdated = true;
                }
            }
            if (anyUpdated) {
                updatedBlocks.push(block);
            }
        }
        if (updatedBlocks.length > 0) {
            clone.updateTextBlocks(updatedBlocks);
        }
    }
}
/** Relationship indicating that the [[AnnotationTextStyle]] is being used as the default style for the [[ITextAnnotation]].
 * @beta
 */
export class TextAnnotationUsesTextStyleByDefault extends RelatedElement {
    static classFullName = "BisCore:TextAnnotationUsesTextStyleByDefault";
    constructor(annotationTextStyleId, relClassName = TextAnnotationUsesTextStyleByDefault.classFullName) {
        super({ id: annotationTextStyleId, relClassName });
    }
}
//# sourceMappingURL=ElementDrivesTextAnnotation.js.map