"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createUpdateContext = createUpdateContext;
exports.updateField = updateField;
exports.updateFields = updateFields;
exports.updateElementFields = updateElementFields;
exports.updateAllFields = updateAllFields;
const core_common_1 = require("@itwin/core-common");
const core_bentley_1 = require("@itwin/core-bentley");
const BackendLoggerCategory_1 = require("../../BackendLoggerCategory");
const ElementDrivesTextAnnotation_1 = require("../../annotations/ElementDrivesTextAnnotation");
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
// Resolve the raw primitive value of the property that a field points to.
function getFieldPropertyValue(field, iModel) {
    const host = field.propertyHost;
    const schemaItem = iModel.schemaContext.getSchemaItemSync(host.schemaName, host.className);
    if (!ecschema_metadata_1.EntityClass.isEntityClass(schemaItem)) {
        return undefined;
    }
    let ecClass = schemaItem;
    const { propertyName, accessors } = field.propertyPath;
    let ecProp = ecClass.getPropertySync(propertyName);
    if (!ecProp) {
        return undefined;
    }
    const isAspect = ecClass.isSync("ElementAspect", "BisCore");
    const where = ` WHERE ${isAspect ? "Element.Id" : "ECInstanceId"}=${host.elementId}`;
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    let curValue = iModel.withPreparedStatement(`SELECT ${propertyName} FROM ${host.schemaName}.${host.className} ${where}`, (stmt) => {
        if (stmt.step() !== core_bentley_1.DbResult.BE_SQLITE_ROW) {
            return undefined;
        }
        const rootValue = stmt.getValue(0);
        if (undefined === rootValue || rootValue.isNull) {
            return undefined;
        }
        switch (rootValue.columnInfo.getType()) {
            case core_common_1.ECSqlValueType.Blob:
                return { primitive: rootValue.getBlob() };
            case core_common_1.ECSqlValueType.Boolean:
                return { primitive: rootValue.getBoolean() };
            case core_common_1.ECSqlValueType.DateTime:
                return { primitive: new Date(rootValue.getDateTime()) };
            case core_common_1.ECSqlValueType.Double:
                return { primitive: rootValue.getDouble() };
            case core_common_1.ECSqlValueType.Guid:
                return { primitive: rootValue.getGuid() };
            case core_common_1.ECSqlValueType.Int:
            case core_common_1.ECSqlValueType.Int64:
                return { primitive: rootValue.getInteger() };
            case core_common_1.ECSqlValueType.Point2d:
                return { primitive: rootValue.getXAndY() };
            case core_common_1.ECSqlValueType.Point3d:
                return { primitive: rootValue.getXYAndZ() };
            case core_common_1.ECSqlValueType.String:
                return { primitive: rootValue.getString() };
            case core_common_1.ECSqlValueType.Struct: {
                ecProp = (0, core_bentley_1.expectDefined)(ecProp);
                (0, core_bentley_1.assert)(ecProp.isStruct());
                ecClass = ecProp.structClass;
                return { struct: rootValue.getStruct() };
            }
            case core_common_1.ECSqlValueType.PrimitiveArray: {
                return { primitiveArray: rootValue.getArray() };
            }
            case core_common_1.ECSqlValueType.StructArray: {
                return { structArray: rootValue.getArray() };
            }
            // Unsupported:
            // case ECSqlValueType.Geometry:
            // case ECSqlValueType.Navigation:
            // case ECSqlValueType.Id:
        }
        return undefined;
    });
    if (undefined === curValue) {
        return undefined;
    }
    if (accessors) {
        for (const accessor of accessors) {
            if (undefined !== curValue.primitive) {
                // Can't index into a primitive.
                return undefined;
            }
            if (typeof accessor === "number") {
                const array = curValue.primitiveArray ?? curValue.structArray;
                if (!array) {
                    return undefined;
                }
                const index = accessor < 0 ? (array.length + accessor) : accessor;
                const item = array[index];
                if (undefined === item) {
                    return undefined;
                }
                else if (curValue.primitiveArray) {
                    curValue = { primitive: curValue.primitiveArray[index] };
                }
                else {
                    (0, core_bentley_1.assert)(undefined !== curValue.structArray);
                    (0, core_bentley_1.assert)(ecProp instanceof ecschema_metadata_1.StructArrayProperty);
                    ecClass = ecProp.structClass;
                    curValue = { struct: curValue.structArray[index] };
                }
            }
            else {
                if (undefined === curValue.struct) {
                    return undefined;
                }
                const item = curValue.struct[accessor];
                if (undefined === item) {
                    return undefined;
                }
                ecProp = ecClass.getPropertySync(accessor);
                if (!ecProp) {
                    return undefined;
                }
                if (ecProp.isArray()) {
                    curValue = ecProp.isStruct() ? { structArray: item } : { primitiveArray: item };
                }
                else if (ecProp.isStruct()) {
                    ecClass = ecProp.structClass;
                    curValue = { struct: item };
                }
                else if (ecProp.isPrimitive()) {
                    curValue = { primitive: item };
                }
                else {
                    return undefined;
                }
            }
        }
    }
    const propertyType = determineFieldPropertyType(ecProp);
    if (!propertyType) {
        return undefined;
    }
    // The ultimate result must be a primitive value.
    if (undefined === curValue.primitive) {
        return undefined;
    }
    return { value: curValue.primitive, type: propertyType };
}
function determineFieldPropertyType(prop) {
    if (prop.isEnumeration()) {
        switch (prop.propertyType) {
            case ecschema_metadata_1.PropertyType.Integer_Enumeration:
                return "int-enum";
            case ecschema_metadata_1.PropertyType.String_Enumeration:
                return "string-enum";
            default:
                return undefined;
        }
    }
    if (prop.isPrimitive()) {
        switch (prop.primitiveType) {
            case ecschema_metadata_1.PrimitiveType.Boolean:
                return "boolean";
            case ecschema_metadata_1.PrimitiveType.String:
                return prop.extendedTypeName === "DateTime" ? "datetime" : "string";
            case ecschema_metadata_1.PrimitiveType.DateTime:
                return "datetime";
            case ecschema_metadata_1.PrimitiveType.Double:
            case ecschema_metadata_1.PrimitiveType.Long:
                return "quantity";
            case ecschema_metadata_1.PrimitiveType.Point2d:
            case ecschema_metadata_1.PrimitiveType.Point3d:
                return "coordinate";
            case ecschema_metadata_1.PrimitiveType.Binary:
                return prop.extendedTypeName === "BeGuid" ? "string" : undefined;
            case ecschema_metadata_1.PrimitiveType.Integer:
            case ecschema_metadata_1.PrimitiveType.Long:
                return "string";
            default:
                return undefined;
        }
    }
    return undefined;
}
function createUpdateContext(hostElementId, iModel, deleted) {
    return {
        hostElementId,
        getProperty: deleted ? () => undefined : (field) => getFieldPropertyValue(field, iModel),
    };
}
// Recompute the display value of a single field, return false if it couldn't be evaluated.
function updateField(field, context) {
    if (context.hostElementId && context.hostElementId !== field.propertyHost.elementId) {
        return false;
    }
    let newContent;
    try {
        const propValue = context.getProperty(field);
        if (undefined !== propValue) {
            newContent = (0, core_common_1.formatFieldValue)(propValue, field.formatOptions);
        }
    }
    catch (err) {
        core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, err);
    }
    newContent = newContent ?? core_common_1.FieldRun.invalidContentIndicator;
    if (newContent === field.cachedContent) {
        return false;
    }
    field.setCachedContent(newContent);
    return true;
}
// Re-evaluates the display strings for all fields that target the element specified by `context` and returns the number
// of fields whose display strings changed as a result.
function updateFields(textBlock, context) {
    let numUpdated = 0;
    for (const { child } of (0, core_common_1.traverseTextBlockComponent)(textBlock)) {
        if (child.type === "field" && updateField(child, context)) {
            ++numUpdated;
        }
    }
    return numUpdated;
}
function doUpdateFields(txn, annotationId, sourceId, deleted) {
    const iModel = txn.iModel;
    try {
        const target = iModel.elements.getElement(annotationId);
        if ((0, ElementDrivesTextAnnotation_1.isITextAnnotation)(target)) {
            const context = createUpdateContext(sourceId, iModel, deleted);
            const updatedBlocks = [];
            for (const block of target.getTextBlocks()) {
                if (updateFields(block.textBlock, context)) {
                    updatedBlocks.push(block);
                }
            }
            if (updatedBlocks.length > 0) {
                target.updateTextBlocks(updatedBlocks);
                target.update(txn);
            }
        }
    }
    catch (err) {
        core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.IModelDb, err);
    }
}
// Invoked by ElementDrivesTextAnnotation to update fields in target element when source element changes or is deleted.
function updateElementFields(props, txn, deleted) {
    doUpdateFields(txn, props.targetId, props.sourceId, deleted);
}
function updateAllFields(annotationElementId, txn) {
    doUpdateFields(txn, annotationElementId, undefined, false);
}
//# sourceMappingURL=fields.js.map