/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import { DbResult, Id64 } from "@itwin/core-bentley";
import { EditTxn, withEditTxn } from "../../EditTxn";
import { BriefcaseIdValue, Code, ColorDef, ElementGeometry, IModel, QueryRowFormat, SubCategoryAppearance, } from "@itwin/core-common";
import { Angle, Arc3d, Cone, IModelJson as GeomJson, LineSegment3d, Point2d, Point3d, Range2d, Range3d } from "@itwin/core-geometry";
import { _nativeDb, CategorySelector, DefinitionModel, DisplayStyle2d, DisplayStyle3d, DrawingCategory, DrawingViewDefinition, IModelDb, IModelJsFs, ModelSelector, OrthographicViewDefinition, PhysicalModel, PhysicalObject, SnapshotDb, SpatialCategory, SpatialViewDefinition } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";
import { EntityClass, RelationshipClass } from "@itwin/ecschema-metadata";
function verifyPrimitiveBase(actualValue, expectedValue) {
    if (expectedValue.i !== undefined)
        assert.equal(actualValue.i, expectedValue.i, "'integer' type property did not roundtrip as expected");
    if (expectedValue.l !== undefined)
        assert.equal(actualValue.l, expectedValue.l, "'long' type property did not roundtrip as expected");
    if (expectedValue.d !== undefined)
        assert.equal(actualValue.d, expectedValue.d, "'double' type property did not roundtrip as expected");
    if (expectedValue.b !== undefined)
        assert.equal(actualValue.b, expectedValue.b, "'boolean' type property did not roundtrip as expected");
    if (expectedValue.dt !== undefined)
        assert.equal(actualValue.dt, expectedValue.dt, "'dateTime' type property did not roundtrip as expected");
    if (expectedValue.s !== undefined)
        assert.equal(actualValue.s, expectedValue.s, "'string' type property did not roundtrip as expected");
    if (expectedValue.p2d) {
        assert.equal(actualValue.p2d?.x, expectedValue.p2d.x, "'Point2d.x' type property did not roundtrip as expected");
        assert.equal(actualValue.p2d?.y, expectedValue.p2d.y, "'Point2d.y' type property did not roundtrip as expected");
    }
    else if (expectedValue.p2d === null) {
        assert.equal(actualValue.p2d, expectedValue.p2d, "'Point2d' type property did not roundtrip as expected.");
    }
    if (expectedValue.p3d) {
        assert.equal(actualValue.p3d?.x, expectedValue.p3d.x, "'Point3d.x' type property did not roundtrip as expected");
        assert.equal(actualValue.p3d?.y, expectedValue.p3d.y, "'Point3d.y' type property did not roundtrip as expected");
        assert.equal(actualValue.p3d?.z, expectedValue.p3d.z, "'Point3d.z' type property did not roundtrip as expected");
    }
    else if (expectedValue.p3d === null) {
        assert.equal(actualValue.p3d, expectedValue.p3d, "'Point3d' type property did not roundtrip as expected.");
    }
    if (expectedValue.bin) {
        assert.isTrue(blobEqual(actualValue.bin, expectedValue.bin), "'binary' type property did not roundtrip as expected");
    }
    else if (expectedValue.bin === null) {
        assert.equal(actualValue.bin, expectedValue.bin, "'binary' type property did not roundtrip as expected.");
    }
    if (expectedValue.g) {
        expect(actualValue.g, "'geometry' type property did not roundtrip as expected.").to.deep.equal(expectedValue.g);
    }
    else if (expectedValue.g === null) {
        assert.equal(actualValue.g, expectedValue.g, "'geometry' type property did not roundtrip as expected.");
    }
}
function verifyPrimitiveArrayBase(actualValue, expectedValue) {
    if (expectedValue.array_bin) {
        assert.equal(actualValue.array_bin.length, expectedValue.array_bin.length, "'binary[].length' array length mismatch");
        expectedValue.array_bin.forEach((value, index) => {
            if (value) {
                assert.isTrue(blobEqual(actualValue.array_bin[index], value), "'binary[]' type property did not roundtrip as expected");
            }
            else if (value === null) {
                assert.equal(actualValue.array_bin[index], value, "'binary[]' type property did not roundtrip as expected");
            }
        });
    }
    else if (expectedValue.array_bin === null) {
        assert.equal(actualValue.array_bin, expectedValue.array_bin, "'binary[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_i) {
        assert.equal(actualValue.array_i.length, expectedValue.array_i.length, "'integer[].length' array length mismatch");
        expectedValue.array_i.forEach((value, index) => {
            assert.equal(actualValue.array_i[index], value, "'integer[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_i === null) {
        assert.equal(actualValue.array_i, expectedValue.array_i, "'integer[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_l) {
        assert.equal(actualValue.array_l.length, expectedValue.array_l.length, "'long[].length' array length mismatch");
        expectedValue.array_l.forEach((value, index) => {
            assert.equal(actualValue.array_l[index], value, "'long[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_l === null) {
        assert.equal(actualValue.array_l, expectedValue.array_l, "'long[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_d) {
        assert.equal(actualValue.array_d.length, expectedValue.array_d.length, "'double[].length' array length mismatch");
        expectedValue.array_d.forEach((value, index) => {
            assert.equal(actualValue.array_d[index], value, "'double[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_d === null) {
        assert.equal(actualValue.array_d, expectedValue.array_d, "'double[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_b) {
        assert.equal(actualValue.array_b.length, expectedValue.array_b.length, "'boolean[].length' array length mismatch");
        expectedValue.array_b.forEach((value, index) => {
            assert.equal(actualValue.array_b[index], value, "'boolean[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_b === null) {
        assert.equal(actualValue.array_b, expectedValue.array_b, "'boolean[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_dt) {
        assert.equal(actualValue.array_dt.length, expectedValue.array_dt.length, "'dateTime[].length' array length mismatch");
        expectedValue.array_dt.forEach((value, index) => {
            assert.equal(actualValue.array_dt[index], value, "'dateTime[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_dt === null) {
        assert.equal(actualValue.array_dt, expectedValue.array_dt, "'dateTime[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_g) {
        assert.equal(actualValue.array_g.length, expectedValue.array_g.length, "'geometry[].length' array length mismatch");
        expectedValue.array_g.forEach((value, index) => {
            if (value) {
                expect(actualValue.array_g[index], "'geometry[]' type property did not roundtrip as expected").to.deep.equal(value);
            }
            else if (value === null) {
                assert.equal(actualValue.array_g[index], value, "'geometry[]' type property did not roundtrip as expected");
            }
        });
    }
    else if (expectedValue.array_g === null) {
        assert.equal(actualValue.array_g, expectedValue.array_g, "'geometry[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_s) {
        assert.equal(actualValue.array_s.length, expectedValue.array_s.length, "'string[].length' array length mismatch");
        expectedValue.array_s.forEach((value, index) => {
            assert.equal(actualValue.array_s[index], value, "'string[]' type property did not roundtrip as expected");
        });
    }
    else if (expectedValue.array_s === null) {
        assert.equal(actualValue.array_s, expectedValue.array_s, "'string[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_p2d) {
        assert.equal(actualValue.array_p2d.length, expectedValue.array_p2d.length, "'point2d[].length' array length mismatch");
        expectedValue.array_p2d.forEach((value, index) => {
            if (value) {
                assert.equal(actualValue.array_p2d[index].x, value.x, "'point2d[].x' type property did not roundtrip as expected");
                assert.equal(actualValue.array_p2d[index].y, value.y, "'point2d[].y' type property did not roundtrip as expected");
            }
            else if (value === null) {
                assert.equal(actualValue.array_p2d[index], value, "'point2d[]' type property did not roundtrip as expected.");
            }
        });
    }
    else if (expectedValue.array_p2d === null) {
        assert.equal(actualValue.array_p2d, expectedValue.array_p2d, "'point2d[]' type property did not roundtrip as expected.");
    }
    if (expectedValue.array_p3d) {
        assert.equal(actualValue.array_p3d.length, expectedValue.array_p3d.length, "'point3d[].length' array length mismatch");
        expectedValue.array_p3d.forEach((value, index) => {
            if (value) {
                assert.equal(actualValue.array_p3d[index].x, value.x, "'point3d[].x' type property did not roundtrip as expected");
                assert.equal(actualValue.array_p3d[index].y, value.y, "'point3d[].y' type property did not roundtrip as expected");
                assert.equal(actualValue.array_p3d[index].z, value.z, "'point3d[].z' type property did not roundtrip as expected");
            }
            else if (value === null) {
                assert.equal(actualValue.array_p3d[index], value, "'point3d[]' type property did not roundtrip as expected.");
            }
        });
    }
    else if (expectedValue.array_p3d === null) {
        assert.equal(actualValue.array_p3d, expectedValue.array_p3d, "'point3d[]' type property did not roundtrip as expected.");
    }
}
function verifyPrimitive(actualValue, expectedValue) {
    verifyPrimitiveBase(actualValue, expectedValue);
    if (expectedValue.st) {
        verifyPrimitive(actualValue.st, expectedValue.st);
        verifyPrimitiveArray(actualValue.st, expectedValue.st);
    }
    else if (expectedValue.st === null) {
        assert.equal(actualValue.st, expectedValue.st, "'ComplexStruct' type property did not roundtrip as expected.");
    }
}
function verifyPrimitiveArray(actualValue, expectedValue) {
    verifyPrimitiveArrayBase(actualValue, expectedValue);
    if (expectedValue.array_st) {
        assert.equal(actualValue.array_st.length, expectedValue.array_st.length, "'struct[].length' array length mismatch");
        actualValue.array_st.forEach((lhs, i) => {
            verifyPrimitiveBase(lhs, expectedValue.array_st[i]);
            verifyPrimitiveArrayBase(lhs, expectedValue.array_st[i]);
        });
    }
    else if (expectedValue.array_st === null) {
        assert.equal(actualValue.array_st, expectedValue.array_st, "'ComplexStruct[]' type property did not roundtrip as expected.");
    }
}
function verifySystemProperty(actualValue, expectedValue) {
    assert.deepEqual(actualValue, expectedValue, "System property did not roundtrip as expected");
}
function verifyTestElement(actualValue, expectedValue) {
    verifyPrimitive(actualValue, expectedValue);
    verifyPrimitiveArray(actualValue, expectedValue);
}
function verifyTestElementAspect(actualValue, expectedValue) {
    verifyPrimitive(actualValue, expectedValue);
    verifyPrimitiveArray(actualValue, expectedValue);
}
function initElemProps(className, _iModelName, modId, catId, autoHandledProp) {
    // add Geometry
    const geomArray = [
        Arc3d.createXY(Point3d.create(0, 0), 5),
        Arc3d.createXY(Point3d.create(5, 5), 2),
        Arc3d.createXY(Point3d.create(-5, -5), 20),
    ];
    const geometryStream = [];
    for (const geom of geomArray) {
        const arcData = GeomJson.Writer.toIModelJson(geom);
        geometryStream.push(arcData);
    }
    // Create props
    const elementProps = {
        classFullName: `ElementRoundTripTest:${className}`,
        model: modId,
        category: catId,
        code: Code.createEmpty(),
        geom: geometryStream,
    };
    if (autoHandledProp)
        Object.assign(elementProps, autoHandledProp);
    return elementProps;
}
function initElementAspectProps(className, _iModelName, elId, autoHandledProp) {
    // Create props
    const elementProps = {
        classFullName: `ElementRoundTripTest:${className}`,
        element: { id: elId },
    };
    if (autoHandledProp)
        Object.assign(elementProps, autoHandledProp);
    return elementProps;
}
function blobEqual(lhs, rhs) {
    if (!(lhs instanceof Uint8Array) || !(rhs instanceof Uint8Array))
        throw new Error("expecting uint8array");
    if (lhs.byteLength !== rhs.byteLength)
        return false;
    for (let i = 0; i < lhs.byteLength; i++) {
        if (lhs[i] !== rhs[i])
            return false;
    }
    return true;
}
function initElementRefersToElementsProps(className, _iModelName, elId1, elId2, autoHandledProp) {
    // Create props
    const result = {
        classFullName: `ElementRoundTripTest:${className}`,
        sourceId: elId1,
        targetId: elId2,
    };
    if (autoHandledProp)
        Object.assign(result, autoHandledProp);
    return result;
}
describe("Element and ElementAspect roundtrip test for all type of properties", () => {
    const testSchema = `<?xml version="1.0" encoding="UTF-8"?>
    <ECSchema schemaName="ElementRoundTripTest" alias="ts" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
      <ECSchemaReference name="BisCore" version="01.00.04" alias="bis"/>
      <ECSchemaReference name="CoreCustomAttributes" version="01.00.03" alias="CoreCA"/>
      <ECEntityClass typeName="TestElement" modifier="None">
        <BaseClass>bis:PhysicalElement</BaseClass>
        <BaseClass>IPrimitive</BaseClass>
        <BaseClass>IPrimitiveArray</BaseClass>
      </ECEntityClass>
      <ECEntityClass typeName="TestElementAspect" modifier="None">
        <BaseClass>bis:ElementUniqueAspect</BaseClass>
        <BaseClass>IPrimitiveAspect</BaseClass>
        <BaseClass>IPrimitiveArrayAspect</BaseClass>
      </ECEntityClass>
      <ECRelationshipClass typeName="TestElementRefersToElements" strength="referencing" modifier="Sealed">
        <BaseClass>bis:ElementRefersToElements</BaseClass>
        <Source multiplicity="(0..*)" roleLabel="refers to" polymorphic="true">
            <Class class="TestElement"/>
        </Source>
        <Target multiplicity="(0..*)" roleLabel="is referenced by" polymorphic="true">
            <Class class="TestElement"/>
        </Target>
        <ECProperty propertyName="i" typeName="int"/>
        <ECProperty propertyName="l" typeName="long"/>
        <ECProperty propertyName="d" typeName="double"/>
        <ECProperty propertyName="b" typeName="boolean"/>
        <ECProperty propertyName="dt" typeName="dateTime"/>
        <ECProperty propertyName="s" typeName="string"/>
        <ECProperty propertyName="bin" typeName="binary"/>
        <ECProperty propertyName="p2d" typeName="point2d"/>
        <ECProperty propertyName="p3d" typeName="point3d"/>
        <ECProperty propertyName="g" typeName="Bentley.Geometry.Common.IGeometry"/>
        <!--<ECStructProperty propertyName="st" typeName="ComplexStruct"/>-->
        <ECArrayProperty propertyName="array_i" typeName="int"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_l" typeName="long"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_d" typeName="double"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_b" typeName="boolean"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_dt" typeName="dateTime"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_s" typeName="string"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_bin" typeName="binary"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p2d" typeName="point2d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p3d" typeName="point3d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_g" typeName="Bentley.Geometry.Common.IGeometry"  minOccurs="0" maxOccurs="unbounded"/>
        <!--<ECStructArrayProperty propertyName="array_st" typeName="ComplexStruct"  minOccurs="0" maxOccurs="unbounded"/>-->
      </ECRelationshipClass>
      <ECEntityClass typeName="IPrimitive" modifier="Abstract">
        <ECCustomAttributes>
          <IsMixin xmlns="CoreCustomAttributes.01.00.03">
            <AppliesToEntityClass>bis:PhysicalElement</AppliesToEntityClass>
          </IsMixin>
        </ECCustomAttributes>
        <ECProperty propertyName="i" typeName="int"/>
        <ECProperty propertyName="l" typeName="long"/>
        <ECProperty propertyName="d" typeName="double"/>
        <ECProperty propertyName="b" typeName="boolean"/>
        <ECProperty propertyName="dt" typeName="dateTime"/>
        <ECProperty propertyName="s" typeName="string"/>
        <ECProperty propertyName="bin" typeName="binary"/>
        <ECProperty propertyName="p2d" typeName="point2d"/>
        <ECProperty propertyName="p3d" typeName="point3d"/>
        <ECProperty propertyName="g" typeName="Bentley.Geometry.Common.IGeometry"/>
        <ECStructProperty propertyName="st" typeName="ComplexStruct"/>
      </ECEntityClass>
      <ECEntityClass typeName="IPrimitiveAspect" modifier="Abstract">
        <ECCustomAttributes>
          <IsMixin xmlns="CoreCustomAttributes.01.00.03">
            <AppliesToEntityClass>bis:ElementUniqueAspect</AppliesToEntityClass>
          </IsMixin>
        </ECCustomAttributes>
        <ECProperty propertyName="i" typeName="int"/>
        <ECProperty propertyName="l" typeName="long"/>
        <ECProperty propertyName="d" typeName="double"/>
        <ECProperty propertyName="b" typeName="boolean"/>
        <ECProperty propertyName="dt" typeName="dateTime"/>
        <ECProperty propertyName="s" typeName="string"/>
        <ECProperty propertyName="bin" typeName="binary"/>
        <ECProperty propertyName="p2d" typeName="point2d"/>
        <ECProperty propertyName="p3d" typeName="point3d"/>
        <ECProperty propertyName="g" typeName="Bentley.Geometry.Common.IGeometry"/>
        <ECStructProperty propertyName="st" typeName="ComplexStruct"/>
      </ECEntityClass>
      <ECEntityClass typeName="IPrimitiveArray" modifier="Abstract">
        <ECCustomAttributes>
          <IsMixin xmlns="CoreCustomAttributes.01.00.03">
            <AppliesToEntityClass>bis:PhysicalElement</AppliesToEntityClass>
          </IsMixin>
        </ECCustomAttributes>
        <ECArrayProperty propertyName="array_i" typeName="int"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_l" typeName="long"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_d" typeName="double"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_b" typeName="boolean"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_dt" typeName="dateTime"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_s" typeName="string"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_bin" typeName="binary"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p2d" typeName="point2d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p3d" typeName="point3d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_g" typeName="Bentley.Geometry.Common.IGeometry"  minOccurs="0" maxOccurs="unbounded"/>
        <ECStructArrayProperty propertyName="array_st" typeName="ComplexStruct"  minOccurs="0" maxOccurs="unbounded"/>
      </ECEntityClass>
      <ECEntityClass typeName="IPrimitiveArrayAspect" modifier="Abstract">
        <ECCustomAttributes>
          <IsMixin xmlns="CoreCustomAttributes.01.00.03">
            <AppliesToEntityClass>bis:ElementUniqueAspect</AppliesToEntityClass>
          </IsMixin>
        </ECCustomAttributes>
        <ECArrayProperty propertyName="array_i" typeName="int"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_l" typeName="long"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_d" typeName="double"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_b" typeName="boolean"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_dt" typeName="dateTime"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_s" typeName="string"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_bin" typeName="binary"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p2d" typeName="point2d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p3d" typeName="point3d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_g" typeName="Bentley.Geometry.Common.IGeometry"  minOccurs="0" maxOccurs="unbounded"/>
        <ECStructArrayProperty propertyName="array_st" typeName="ComplexStruct"  minOccurs="0" maxOccurs="unbounded"/>
      </ECEntityClass>
      <ECStructClass typeName="ComplexStruct" modifier="None">
        <ECProperty propertyName="i" typeName="int"/>
        <ECProperty propertyName="l" typeName="long"/>
        <ECProperty propertyName="d" typeName="double"/>
        <ECProperty propertyName="b" typeName="boolean"/>
        <ECProperty propertyName="dt" typeName="dateTime"/>
        <ECProperty propertyName="s" typeName="string"/>
        <ECProperty propertyName="bin" typeName="binary"/>
        <ECProperty propertyName="p2d" typeName="point2d"/>
        <ECProperty propertyName="p3d" typeName="point3d"/>
        <ECProperty propertyName="g" typeName="Bentley.Geometry.Common.IGeometry"/>
        <ECArrayProperty propertyName="array_i" typeName="int"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_l" typeName="long"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_d" typeName="double"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_b" typeName="boolean"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_dt" typeName="dateTime"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_s" typeName="string"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_bin" typeName="binary"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p2d" typeName="point2d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_p3d" typeName="point3d"  minOccurs="0" maxOccurs="unbounded"/>
        <ECArrayProperty propertyName="array_g" typeName="Bentley.Geometry.Common.IGeometry"  minOccurs="0" maxOccurs="unbounded"/>
      </ECStructClass>
    </ECSchema>`;
    const schemaFileName = "ElementRoundTripTest.01.00.00.xml";
    const iModelFileName = "ElementRoundTripTest.bim";
    const categoryName = "RoundTripCategory";
    const subDirName = "ElementRoundTrip";
    const iModelPath = IModelTestUtils.prepareOutputFile(subDirName, iModelFileName);
    const primInst1 = {
        i: 101,
        l: 12334343434,
        d: 1023.34,
        b: true,
        dt: "2017-01-01T00:00:00.000",
        s: "Test string Inst1",
        bin: new Uint8Array([1, 2, 3]),
        g: GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 0, 0), Point3d.create(0, 0, 1), 0.5, 0.5, false)),
        p2d: new Point2d(1.034, 2.034),
        p3d: new Point3d(-1.0, 2.3, 3.0001),
    };
    const primInst2 = {
        i: 4322,
        l: 98283333,
        d: -2343.342,
        b: false,
        dt: "2010-01-01T11:11:11.000",
        s: "Test string Inst2",
        bin: new Uint8Array([11, 21, 31, 34, 53, 21, 14, 14, 55, 22]),
        g: GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 1, 0), Point3d.create(0, 0, 1), 0.5, 0.5, false)),
        p2d: new Point2d(1111.11, 2222.22),
        p3d: new Point3d(-111.11, -222.22, -333.33),
    };
    const primArrInst1 = {
        array_i: [101, 202, -345],
        array_l: [12334343434, 3434343434, 12],
        array_d: [1023.34, 3023.34, -3432.033],
        array_b: [true, false, true, false],
        array_dt: ["2017-01-01T00:00:00.000", "2018-01-01T00:00:00.000"],
        array_s: ["Test string 1", "Test string 2", "Test string 3"],
        array_bin: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 2, 3, 3, 4, 55, 6, 65])],
        array_p2d: [new Point2d(1, 2), new Point2d(2, 4)],
        array_p3d: [new Point3d(1, 2, 3), new Point3d(4, 5, 6)],
        array_g: [
            GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 1, 0), Point3d.create(0, 0, 2), 0.5, 0.5, false)),
            GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 1, 1), Point3d.create(0, 0, 2), 0.5, 0.5, false)),
        ],
    };
    const primArrInst2 = {
        array_i: [0, 1, 2, 3, 4, 5, 6666],
        array_l: [-23422, -343343434, -12333434, 23423423],
        array_d: [-21023.34, -33023.34, -34432.033],
        array_b: [false, true],
        array_dt: ["2017-01-01T00:00:00.000", "2018-01-01T00:00:00.000", "2011-01-01T00:00:00.000"],
        array_s: ["Test string 1 - inst2", "Test string 2 - inst2", "Test string 3 - inst2"],
        array_bin: [new Uint8Array([1, 2, 3, 3, 4]), new Uint8Array([0, 0, 0, 0]), new Uint8Array([1, 2, 3, 4])],
        array_p2d: [new Point2d(-123, 244.23232), new Point2d(232, 324.2323), new Point2d(322, 2324.23322)],
        array_p3d: [new Point3d(133, 2333, 333), new Point3d(4123, 5123, 6123)],
        array_g: [
            GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 1, 0), Point3d.create(0, 0, 2), 0.5, 0.5, false)),
            GeomJson.Writer.toIModelJson(Cone.createAxisPoints(Point3d.create(0, 1, 1), Point3d.create(0, 0, 2), 0.5, 0.5, false)),
        ],
    };
    before(async () => {
        // write schema to disk as we do not have api to import xml directly
        const testSchemaPath = IModelTestUtils.prepareOutputFile(subDirName, schemaFileName);
        IModelJsFs.writeFileSync(testSchemaPath, testSchema);
        const imodel = SnapshotDb.createEmpty(iModelPath, { rootSubject: { name: "RoundTripTest" } });
        await imodel.importSchemas([testSchemaPath]);
        imodel[_nativeDb].resetBriefcaseId(BriefcaseIdValue.Unassigned);
        withEditTxn(imodel, (txn) => IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true));
        const spatialCategoryId = SpatialCategory.queryCategoryIdByName(imodel, IModel.dictionaryId, categoryName);
        if (undefined === spatialCategoryId)
            withEditTxn(imodel, (txn) => SpatialCategory.insert(txn, IModel.dictionaryId, categoryName, new SubCategoryAppearance({ color: ColorDef.create("rgb(255,0,0)").toJSON() })));
        imodel.close();
    });
    it("Roundtrip all type of properties via ElementApi, ConcurrentQuery and ECSqlStatement via insert and update", async () => {
        const testFileName = IModelTestUtils.prepareOutputFile(subDirName, "roundtrip_correct_data.bim");
        const imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, iModelPath);
        const spatialCategoryId = SpatialCategory.queryCategoryIdByName(imodel, IModel.dictionaryId, categoryName);
        const [, newModelId] = withEditTxn(imodel, (txn) => IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true));
        // create element with auto handled properties
        const expectedValue = initElemProps("TestElement", imodel, newModelId, spatialCategoryId, {
            ...primInst1,
            ...primArrInst1,
            st: { ...primArrInst2, ...primInst1 },
            array_st: [{ ...primInst1, ...primArrInst2 }, { ...primInst2, ...primArrInst1 }],
        });
        // insert a element
        const geomElement = imodel.elements.createElement(expectedValue);
        const id = withEditTxn(imodel, (txn) => txn.insertElement(geomElement.toJSON()));
        assert.isTrue(Id64.isValidId64(id), "insert worked");
        const expectedSystemProperty = {
            id,
            className: `ElementRoundTripTest.TestElement`,
            model: {
                id: newModelId,
                relClassName: `BisCore.ModelContainsElements`,
            }
        };
        // verify inserted element properties
        const actualValue = imodel.elements.getElementProps(id);
        verifyTestElement(actualValue, expectedValue);
        // verify via concurrent query
        let rowCount = 0;
        for await (const row of imodel.createQueryReader("SELECT * FROM ts.TestElement", undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
            verifyTestElement(row.toRow(), expectedValue);
            rowCount++;
        }
        assert.equal(rowCount, 1);
        // verify via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT * FROM ts.TestElement", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            const stmtRow = stmt.getRow();
            verifyTestElement(stmtRow, expectedValue);
        });
        // Verify system properties via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("select ECInstanceId, ECClassId, Model from ts.TestElement", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            verifySystemProperty(stmt.getRow(), expectedSystemProperty);
        });
        const testElementMetaData = imodel.schemaContext.getSchemaItemSync("ElementRoundTripTest", "TestElement", EntityClass);
        assert.isDefined(testElementMetaData);
        const relClassMetaData = imodel.schemaContext.getSchemaItemSync("BisCore", "ModelContainsElements", RelationshipClass);
        assert.isDefined(relClassMetaData);
        // Verify system properties via concurrent query
        let reader = imodel.createQueryReader("SELECT ECInstanceId, ec_classname(ECClassId, 's.c') as className, Model.Id, ec_classname(Model.RelECClassId, 's.c') as relClassName FROM ts.TestElement", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
        assert.isTrue(await reader.step());
        assert.equal(reader.current.ECInstanceId, id);
        assert.equal(reader.current.className, testElementMetaData?.fullName);
        assert.equal(reader.current.Id, newModelId);
        assert.equal(reader.current.relClassName, relClassMetaData?.fullName);
        assert.isFalse(await reader.step());
        // update the element autohandled properties
        Object.assign(actualValue, {
            ...primInst2,
            ...primArrInst2,
            st: { ...primArrInst1, ...primInst2 },
            array_st: [{ ...primInst2, ...primArrInst2 }, { ...primInst1, ...primArrInst1 }],
        });
        // update element
        withEditTxn(imodel, (txn) => txn.updateElement(actualValue));
        // verify updated values
        const updatedValue = imodel.elements.getElementProps(id);
        verifyTestElement(updatedValue, actualValue);
        // verify via concurrent query
        rowCount = 0;
        for await (const row of imodel.createQueryReader("SELECT * FROM ts.TestElement", undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
            verifyTestElement(row.toRow(), actualValue);
            rowCount++;
        }
        assert.equal(rowCount, 1);
        // verify via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT * FROM ts.TestElement", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            const stmtRow = stmt.getRow();
            verifyTestElement(stmtRow, actualValue);
        });
        // Verify system properties via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("select ECInstanceId, ECClassId, Model from ts.TestElement", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            verifySystemProperty(stmt.getRow(), expectedSystemProperty);
        });
        // Verify system properties via concurrent query
        reader = imodel.createQueryReader("SELECT ECInstanceId, ec_classname(ECClassId, 's.c') as className, Model.Id, ec_classname(Model.RelECClassId, 's.c') as relClassName FROM ts.TestElement", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
        assert.isTrue(await reader.step());
        assert.equal(reader.current.ECInstanceId, id);
        assert.equal(reader.current.className, testElementMetaData?.fullName);
        assert.equal(reader.current.Id, newModelId);
        assert.equal(reader.current.relClassName, relClassMetaData?.fullName);
        assert.isFalse(await reader.step());
        imodel.close();
    });
    async function verifyElementAspect(elementAspectId, elementAspect, elementId, expectedAspectFullName, iModel) {
        // Verify updated values
        const updatedAspectValue = iModel.elements.getAspects(elementId, expectedAspectFullName).map((x) => x.toJSON());
        assert.equal(updatedAspectValue.length, 1);
        verifyTestElementAspect(updatedAspectValue[0], elementAspect);
        // Verify via a concurrent query
        let rowCount = 0;
        for await (const row of iModel.createQueryReader("SELECT * FROM ts.TestElementAspect", undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
            verifyTestElementAspect(row.toRow(), elementAspect);
            rowCount++;
        }
        assert.equal(rowCount, 1);
        // Verify via an ECSql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await iModel.withPreparedStatement("SELECT * FROM ts.TestElementAspect", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            const stmtRow = stmt.getRow();
            verifyTestElementAspect(stmtRow, elementAspect);
        });
        const expectedSystemProperty = {
            id: elementAspectId,
            className: `ElementRoundTripTest.TestElementAspect`,
            element: {
                id: elementId,
                relClassName: `BisCore.ElementOwnsUniqueAspect`,
            },
        };
        const aspectMetaData = await iModel.schemaContext.getSchemaItem("ElementRoundTripTest.TestElementAspect", EntityClass);
        assert.isDefined(aspectMetaData);
        const relMetaData = await iModel.schemaContext.getSchemaItem("BisCore.ElementOwnsUniqueAspect", RelationshipClass);
        assert.isDefined(relMetaData);
        // Verify via a concurrent query
        const reader = iModel.createQueryReader("SELECT ECInstanceId, ec_classname(ECClassId, 's.c') as className, Element.Id, ec_classname(Element.RelECClassId, 's.c') as relClassName FROM ts.TestElementAspect", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
        assert.isTrue(await reader.step());
        assert.equal(reader.current.ECInstanceId, elementAspectId);
        assert.equal(reader.current.className, aspectMetaData?.fullName);
        assert.equal(reader.current.Id, elementId);
        assert.equal(reader.current.relClassName, relMetaData?.fullName);
        assert.isFalse(await reader.step());
        // Verify via an ECSql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await iModel.withPreparedStatement("SELECT ECInstanceId, ECClassId, Element FROM ts.TestElementAspect", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            verifySystemProperty(stmt.getRow(), expectedSystemProperty);
        });
        return updatedAspectValue;
    }
    it("Roundtrip all type of properties via ElementAspectApi, ConcurrentQuery and ECSqlStatement via insert and update", async () => {
        const testFileName = IModelTestUtils.prepareOutputFile(subDirName, "roundtrip_apsect_correct_data.bim");
        const imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, iModelPath);
        const spatialCategoryId = SpatialCategory.queryCategoryIdByName(imodel, IModel.dictionaryId, categoryName);
        const [, newModelId] = withEditTxn(imodel, (txn) => IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true));
        // Create an element to use with the ElementAspects
        const expectedValue = initElemProps("TestElement", imodel, newModelId, spatialCategoryId, {});
        // Insert an element
        const geomElement = imodel.elements.createElement(expectedValue);
        const elId = withEditTxn(imodel, (txn) => txn.insertElement(geomElement.toJSON()));
        assert.isTrue(Id64.isValidId64(elId), "Element insertion succeeded");
        const expectedAspectValue = initElementAspectProps("TestElementAspect", imodel, elId, {
            ...primInst1,
            ...primArrInst1,
            st: { ...primArrInst2, ...primInst1 },
            array_st: [{ ...primInst1, ...primArrInst2 }, { ...primInst2, ...primArrInst1 }],
        });
        // Insert an element aspect
        const elementAspectId = withEditTxn(imodel, (txn) => {
            return txn.insertAspect(expectedAspectValue);
        });
        // Verify inserted element aspect properties
        const actualAspectValue = await verifyElementAspect(elementAspectId, expectedAspectValue, elId, expectedAspectValue.classFullName, imodel);
        // Update the element's autohandled properties
        Object.assign(actualAspectValue[0], {
            ...primInst2,
            ...primArrInst2,
            st: { ...primArrInst1, ...primInst2 },
            array_st: [{ ...primInst2, ...primArrInst2 }, { ...primInst1, ...primArrInst1 }],
        });
        // Update the element
        withEditTxn(imodel, (txn) => {
            txn.updateAspect(actualAspectValue[0]);
        });
        // Verify updated element aspect properties
        await verifyElementAspect(elementAspectId, actualAspectValue[0], elId, expectedAspectValue.classFullName, imodel);
        imodel.close();
    });
    function verifyTestElementRefersToElements(actualValue, expectedValue) {
        assert.equal(actualValue.sourceId, expectedValue.sourceId, "'sourceId' type property did not roundtrip as expected");
        assert.equal(actualValue.targetId, expectedValue.targetId, "'targetId' type property did not roundtrip as expected");
        verifyPrimitiveBase(actualValue, expectedValue);
        verifyPrimitiveArrayBase(actualValue, expectedValue);
    }
    it("Roundtrip all type of properties via ElementRefersToElements, ConcurrentQuery and ECSqlStatement via insert and update", async () => {
        const testFileName = IModelTestUtils.prepareOutputFile(subDirName, "roundtrip_relationships_correct_data.bim");
        const imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, iModelPath);
        const spatialCategoryId = SpatialCategory.queryCategoryIdByName(imodel, IModel.dictionaryId, categoryName);
        const [, newModelId] = withEditTxn(imodel, (txn) => IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true));
        // create elements to use
        const element1 = initElemProps("TestElement", imodel, newModelId, spatialCategoryId, {});
        const element2 = initElemProps("TestElement", imodel, newModelId, spatialCategoryId, {});
        const geomElement1 = imodel.elements.createElement(element1);
        const elId1 = withEditTxn(imodel, (txn) => txn.insertElement(geomElement1.toJSON()));
        assert.isTrue(Id64.isValidId64(elId1), "insert of element 1 worked");
        const geomElement2 = imodel.elements.createElement(element2);
        const elId2 = withEditTxn(imodel, (txn) => txn.insertElement(geomElement2.toJSON()));
        assert.isTrue(Id64.isValidId64(elId2), "insert of element 2 worked");
        // TODO: Skipping structs here, because of a bug that prevents querying from link tables that have an overflow table, by skipping the struct we reduce the amount of used columns
        const expectedRelationshipValue = initElementRefersToElementsProps("TestElementRefersToElements", imodel, elId1, elId2, {
            ...primInst1,
            ...primArrInst1,
            /* st: { ...primArrInst2, ...primInst1 },
            array_st: [{ ...primInst1, ...primArrInst2 }, { ...primInst2, ...primArrInst1 }], */
        });
        const instance = expectedRelationshipValue; // imodel.relationships.createInstance(expectedRelationshipValue);
        const relationshipId = withEditTxn(imodel, (txn) => txn.insertRelationship(instance)); // initElementRefersToElementsProps lies about return type.
        // verify inserted properties
        const actualRelationshipValue = imodel.relationships.getInstance(expectedRelationshipValue.classFullName, relationshipId);
        assert.exists(actualRelationshipValue);
        verifyTestElementRefersToElements(actualRelationshipValue, expectedRelationshipValue);
        // verify via concurrent query
        let rowCount = 0;
        for await (const row of imodel.createQueryReader("SELECT * FROM ts.TestElementRefersToElements", undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
            const val = row.toRow();
            verifyTestElementRefersToElements(val, expectedRelationshipValue);
            rowCount++;
        }
        assert.equal(rowCount, 1);
        // verify via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT * FROM ts.TestElementRefersToElements", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            const stmtRow = stmt.getRow();
            verifyTestElementRefersToElements(stmtRow, expectedRelationshipValue);
        });
        const expectedSystemProperties = {
            id: expectedRelationshipValue.id,
            className: `ElementRoundTripTest.TestElementRefersToElements`,
            sourceId: elId1,
            sourceClassName: `ElementRoundTripTest.TestElement`,
            targetId: elId2,
            targetClassName: `ElementRoundTripTest.TestElement`,
        };
        const classMetaData = await imodel.schemaContext.getSchemaItem("ElementRoundTripTest.TestElementRefersToElements", RelationshipClass);
        assert.isDefined(classMetaData);
        const elementMetaData = await imodel.schemaContext.getSchemaItem("ElementRoundTripTest.TestElement", EntityClass);
        assert.isDefined(elementMetaData);
        // verify system properties via concurrent query
        let reader = imodel.createQueryReader("SELECT ECInstanceId, ec_classname(ECClassId, 's.c') as className, SourceECInstanceId, ec_classname(SourceECClassId, 's.c') as srcClassName, TargetECInstanceid, ec_classname(TargetECClassId, 's.c') as trgtClassName FROM ts.TestElementRefersToElements", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
        assert.isTrue(await reader.step());
        assert.equal(reader.current.ECInstanceId, relationshipId);
        assert.equal(reader.current.className, classMetaData?.fullName);
        assert.equal(reader.current.SourceECInstanceId, elId1);
        assert.equal(reader.current.srcClassName, elementMetaData?.fullName);
        assert.equal(reader.current.TargetECInstanceid, elId2);
        assert.equal(reader.current.trgtClassName, elementMetaData?.fullName);
        assert.isFalse(await reader.step());
        // verify system properties via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT ECInstanceId, ECClassId, SourceECInstanceId, SourceECClassId, TargetECInstanceid, TargetECClassId FROM ts.TestElementRefersToElements", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            verifySystemProperty(stmt.getRow(), expectedSystemProperties);
        });
        const updatedExpectedValue = actualRelationshipValue;
        // update the element autohandled properties
        Object.assign(updatedExpectedValue, {
            ...primInst2,
            ...primArrInst2,
            /*      st: { ...primArrInst1, ...primInst2 },
                  array_st: [{ ...primInst2, ...primArrInst2 }, { ...primInst1, ...primArrInst1 }],*/
        });
        // update
        withEditTxn(imodel, (txn) => txn.updateRelationship(updatedExpectedValue.toJSON()));
        // verify updated values
        const updatedValue = imodel.relationships.getInstance(expectedRelationshipValue.classFullName, relationshipId);
        verifyTestElementRefersToElements(updatedValue, updatedExpectedValue);
        // verify via concurrent query
        rowCount = 0;
        for await (const row of imodel.createQueryReader("SELECT * FROM ts.TestElementRefersToElements", undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
            verifyTestElementRefersToElements(row.toRow(), updatedExpectedValue);
            rowCount++;
        }
        assert.equal(rowCount, 1);
        // verify via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT * FROM ts.TestElementRefersToElements", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            const stmtRow = stmt.getRow();
            verifyTestElementRefersToElements(stmtRow, updatedExpectedValue);
        });
        // verify system properties via concurrent query
        reader = imodel.createQueryReader("SELECT ECInstanceId, ec_classname(ECClassId, 's.c') as className, SourceECInstanceId, ec_classname(SourceECClassId, 's.c') as srcClassName, TargetECInstanceid, ec_classname(TargetECClassId, 's.c') as trgtClassName FROM ts.TestElementRefersToElements", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
        assert.isTrue(await reader.step());
        assert.equal(reader.current.ECInstanceId, relationshipId);
        assert.equal(reader.current.className, classMetaData?.fullName);
        assert.equal(reader.current.SourceECInstanceId, elId1);
        assert.equal(reader.current.srcClassName, elementMetaData?.fullName);
        assert.equal(reader.current.TargetECInstanceid, elId2);
        assert.equal(reader.current.trgtClassName, elementMetaData?.fullName);
        assert.isFalse(await reader.step());
        // verify system properties via ecsql statement
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        await imodel.withPreparedStatement("SELECT ECInstanceId, ECClassId, SourceECInstanceId, SourceECClassId, TargetECInstanceid, TargetECClassId FROM ts.TestElementRefersToElements", async (stmt) => {
            assert.equal(stmt.step(), DbResult.BE_SQLITE_ROW);
            verifySystemProperty(stmt.getRow(), expectedSystemProperties);
        });
        imodel.close();
    });
    it("Roundtrip placement when geom is undefined", async () => {
        const placement = {
            origin: { x: 10, y: 20, z: 30 },
            angles: {
                yaw: Angle.createDegrees(90),
                pitch: Angle.createDegrees(180),
                roll: Angle.createDegrees(270),
            },
            bbox: {
                low: { x: -1, y: -2, z: -3 },
                high: { x: 1, y: 2, z: 3 },
            },
        };
        const insertAndVerifyPlacement = (name, extraProps = {}, { 
        /**
         * setting some geometry will override the passed bounding box with a calculated one,
         * so we need to be able to override parts of the expected placement based on the geometry used
         */
        expectedPlacementOverrides = {}, } = {}) => {
            const imodelPath = IModelTestUtils.prepareOutputFile(subDirName, `roundtrip_placement-${name}.bim`);
            let imodel = IModelTestUtils.createSnapshotFromSeed(imodelPath, iModelPath);
            const { objId } = withEditTxn(imodel, (txn) => {
                const modelId = PhysicalModel.insert(txn, IModelDb.rootSubjectId, "model");
                const categoryId = SpatialCategory.insert(txn, IModelDb.dictionaryId, "model", {});
                return {
                    objId: txn.insertElement({
                        classFullName: PhysicalObject.classFullName,
                        code: Code.createEmpty(),
                        model: modelId,
                        placement,
                        category: categoryId,
                        ...extraProps,
                    }),
                };
            });
            const expectedPlacement = { ...placement, ...expectedPlacementOverrides };
            const inMemoryCopy = imodel.elements.getElement({ id: objId, wantGeometry: true }, PhysicalObject);
            expect(inMemoryCopy.placement).to.deep.advancedEqual(expectedPlacement);
            // reload db since there is a different path for loading properties not in memory that we want to force
            imodel.close();
            imodel = SnapshotDb.openFile(imodelPath);
            const readFromDbCopy = imodel.elements.getElement({ id: objId, wantGeometry: true }, PhysicalObject);
            expect(readFromDbCopy.placement).to.deep.advancedEqual(expectedPlacement);
            imodel.close();
        };
        insertAndVerifyPlacement("no-geom", {
            geom: undefined,
            elementGeometryBuilderParams: undefined,
        });
        const pts = [Point3d.create(5, 10, 0), Point3d.create(10, 10, 0)];
        const geomEntry = ElementGeometry.fromGeometryQuery(LineSegment3d.create(pts[0], pts[1]));
        assert(geomEntry !== undefined);
        const elementGeometryBuilderParams = { entryArray: [geomEntry] };
        insertAndVerifyPlacement("geom-through-elementGeometryBuilderParams", {
            geom: undefined,
            elementGeometryBuilderParams,
        }, {
            expectedPlacementOverrides: {
                bbox: {
                    low: { x: 5, y: 10, z: 0 },
                    high: { x: 10, y: 10, z: 0 },
                },
            },
        });
        // Previously, TypeScript BoxProps defined "origin" but native code only understood "baseOrigin".
        // Now, native code accepts either, preferring "origin" if both are specified.
        const testBox = (props, expectedXOffset) => {
            const box = {};
            if (undefined !== props.originX)
                box.origin = [props.originX, 1, 2];
            if (undefined !== props.baseOriginX)
                box.baseOrigin = [props.baseOriginX, 1, 2];
            const geom = [
                { header: { flags: 0 } },
                { box: { ...box, baseX: 10, baseY: 20 } },
            ];
            insertAndVerifyPlacement("geom-through-json", {
                geom,
                elementGeometryBuilderParams: undefined,
            }, {
                expectedPlacementOverrides: {
                    bbox: {
                        low: { x: expectedXOffset, y: 1, z: 2 },
                        high: { x: expectedXOffset + 10, y: 21, z: 12 },
                    },
                },
            });
        };
        testBox({ originX: 0 }, 0);
        testBox({ baseOriginX: 5 }, 5);
        testBox({ originX: 2, baseOriginX: 4 }, 2);
    });
    it("Roundtrip updating properties to null", async () => {
        const testFileName = IModelTestUtils.prepareOutputFile(subDirName, "roundtrip_properties_null_update.bim");
        const imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, iModelPath);
        const spatialCategoryId = SpatialCategory.queryCategoryIdByName(imodel, IModel.dictionaryId, categoryName);
        const [, newModelId] = withEditTxn(imodel, (txn) => IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), true));
        // Create an element to be used
        const expectedValue = initElemProps("TestElement", imodel, newModelId, spatialCategoryId, {
            ...primInst1,
            ...primArrInst1,
            st: { ...primArrInst2, ...primInst1 },
            array_st: [{ ...primInst1, ...primArrInst2 }, { ...primInst2, ...primArrInst1 }],
        });
        // Insert an element
        const geomElement = imodel.elements.createElement(expectedValue);
        const id = withEditTxn(imodel, (txn) => txn.insertElement(geomElement.toJSON()));
        assert.isTrue(Id64.isValidId64(id), "insert worked");
        // Verify inserted element properties
        const actualValue = imodel.elements.getElementProps(id);
        verifyTestElement(actualValue, expectedValue);
        // Update all properties to null
        {
            Object.assign(actualValue, {
                i: null,
                l: null,
                d: null,
                b: null,
                dt: null,
                s: null,
                bin: null,
                p2d: null,
                p3d: null,
                g: null,
                st: null,
                array_i: null,
                array_l: null,
                array_d: null,
                array_b: null,
                array_dt: null,
                array_s: null,
                array_bin: null,
                array_p2d: null,
                array_p3d: null,
                array_g: null,
                array_st: null,
            });
            // Update the element
            withEditTxn(imodel, (txn) => txn.updateElement(actualValue));
            // Verify updated value properties
            const updatedValue = imodel.elements.getElementProps(id);
            verifyTestElement(updatedValue, actualValue);
        }
        imodel.close();
    });
    describe("ECSQL to JS property name roundtrip tests", () => {
        const testIModelPath = IModelTestUtils.prepareOutputFile(subDirName, "viewDefinition_nav_roundtrip.bim");
        let imodel;
        beforeEach(() => {
            if (IModelJsFs.existsSync(testIModelPath))
                IModelJsFs.removeSync(testIModelPath);
            imodel = SnapshotDb.createEmpty(testIModelPath, { rootSubject: { name: "ViewDefinitionNavPropRoundtrip" } });
        });
        afterEach(() => {
            if (imodel.isOpen) {
                imodel.close();
            }
        });
        function parseFullInstanceColumn(rawJson) {
            const parsed = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
            parsed.classFullName = parsed.className.replace(".", ":");
            return parsed;
        }
        function setupSpatialViewDefinitions() {
            const txn = new EditTxn(imodel, "roundTrip test");
            txn.start();
            // Set up required definition elements
            const definitionModelId = DefinitionModel.insert(txn, IModelDb.rootSubjectId, "Definition");
            const categoryId = SpatialCategory.insert(txn, definitionModelId, "SpatialCat", new SubCategoryAppearance());
            const [, physicalModelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(txn, Code.createEmpty(), false, IModelDb.rootSubjectId);
            const modelSelectorId = ModelSelector.insert(txn, definitionModelId, "ModelSelector", [physicalModelId]);
            const displayStyleId = DisplayStyle3d.insert(txn, definitionModelId, "DisplayStyle3d");
            const categorySelectorId = CategorySelector.insert(txn, definitionModelId, "CategorySelector", [categoryId]);
            const viewRange = new Range3d(0, 0, 0, 500, 500, 500);
            txn.end("save");
            return { definitionModelId, modelSelectorId, displayStyleId, categorySelectorId, viewRange };
        }
        it("roundtrip categorySelector, displayStyle, and modelSelector properties from SpatialViewDefinition", async () => {
            const { definitionModelId, modelSelectorId, displayStyleId, categorySelectorId, viewRange } = setupSpatialViewDefinitions();
            const txn = new EditTxn(imodel, "roundTrip test");
            txn.start();
            const spatialView = SpatialViewDefinition.createWithCamera(imodel, definitionModelId, "Test Spatial View", modelSelectorId, categorySelectorId, displayStyleId, viewRange);
            const elementId = txn.insertElement(spatialView.toJSON());
            txn.end("save");
            for await (const row of imodel.createQueryReader(`SELECT $ FROM bis.Element WHERE ECInstanceId = ${elementId} OPTIONS USE_JS_PROP_NAMES`)) {
                const parsed = parseFullInstanceColumn(row.toRow().$);
                // Validate navigation properties in the ECSql result
                assert.isDefined(parsed.categorySelector, "ECSQL $ should contain 'categorySelector'");
                assert.isDefined(parsed.displayStyle, "ECSQL $ should contain 'displayStyle'");
                assert.isDefined(parsed.modelSelector, "ECSQL $ should contain 'modelSelector'");
                // Validate navigation property IDs
                assert.isTrue(Id64.isValidId64(parsed.categorySelector.id), "categorySelector.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.displayStyle.id), "displayStyle.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.modelSelector.id), "modelSelector.id should be a valid Id64");
                // Try to reconstruct an element from the returned properties
                const reconstructed = imodel.constructEntity(parsed);
                assert.deepEqual(reconstructed.categorySelector, parsed.categorySelector);
                assert.deepEqual(reconstructed.displayStyle, parsed.displayStyle);
                assert.deepEqual(reconstructed.modelSelector, parsed.modelSelector);
                // Deprecated Id64String props should also be valid (Backward compatibility)
                assert.equal(reconstructed.categorySelectorId, categorySelectorId);
                assert.equal(reconstructed.displayStyleId, displayStyleId);
                assert.equal(reconstructed.modelSelectorId, modelSelectorId);
                // Do a quick check of the setter to ensure RelClassName is preserved and only id gets overridden
                reconstructed.categorySelectorId = "Id123";
                reconstructed.modelSelectorId = "Id456";
                reconstructed.displayStyleId = "Id789";
                assert.deepEqual(reconstructed.categorySelector, { id: "Id123", relClassName: "BisCore.ViewDefinitionUsesCategorySelector" });
                assert.deepEqual(reconstructed.modelSelector, { id: "Id456", relClassName: "BisCore.SpatialViewDefinitionUsesModelSelector" });
                assert.deepEqual(reconstructed.displayStyle, { id: "Id789", relClassName: "BisCore.ViewDefinitionUsesDisplayStyle" });
            }
        });
        it("roundtrip categorySelector, displayStyle, and modelSelector properties from OrthographicViewDefinition", async () => {
            const { definitionModelId, modelSelectorId, displayStyleId, categorySelectorId, viewRange } = setupSpatialViewDefinitions();
            const txn = new EditTxn(imodel, "roundTrip test");
            txn.start();
            const orthoViewId = OrthographicViewDefinition.insert(txn, definitionModelId, "Test Ortho View", modelSelectorId, categorySelectorId, displayStyleId, viewRange);
            txn.end("save");
            for await (const row of imodel.createQueryReader(`SELECT $ FROM bis.Element WHERE ECInstanceId = ${orthoViewId} OPTIONS USE_JS_PROP_NAMES`)) {
                const parsed = parseFullInstanceColumn(row.toRow().$);
                // Validate navigation properties in the ECSql result
                assert.isDefined(parsed.categorySelector, "ECSQL $ should contain 'categorySelector'");
                assert.isDefined(parsed.displayStyle, "ECSQL $ should contain 'displayStyle'");
                assert.isDefined(parsed.modelSelector, "ECSQL $ should contain 'modelSelector'");
                // Validate navigation property IDs
                assert.isTrue(Id64.isValidId64(parsed.categorySelector.id), "categorySelector.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.displayStyle.id), "displayStyle.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.modelSelector.id), "modelSelector.id should be a valid Id64");
                // Try to reconstruct an element from the returned properties
                const reconstructed = imodel.constructEntity(parsed);
                assert.deepEqual(reconstructed.categorySelector, parsed.categorySelector);
                assert.deepEqual(reconstructed.displayStyle, parsed.displayStyle);
                assert.deepEqual(reconstructed.modelSelector, parsed.modelSelector);
                // Deprecated Id64String props should also be valid (Backward compatibility)
                assert.equal(reconstructed.categorySelectorId, categorySelectorId);
                assert.equal(reconstructed.displayStyleId, displayStyleId);
                assert.equal(reconstructed.modelSelectorId, modelSelectorId);
                // Do a quick check of the setter to ensure RelClassName is preserved and only id gets overridden
                reconstructed.categorySelectorId = "Id123";
                reconstructed.modelSelectorId = "Id456";
                reconstructed.displayStyleId = "Id789";
                assert.deepEqual(reconstructed.categorySelector, { id: "Id123", relClassName: "BisCore.ViewDefinitionUsesCategorySelector" });
                assert.deepEqual(reconstructed.modelSelector, { id: "Id456", relClassName: "BisCore.SpatialViewDefinitionUsesModelSelector" });
                assert.deepEqual(reconstructed.displayStyle, { id: "Id789", relClassName: "BisCore.ViewDefinitionUsesDisplayStyle" });
            }
        });
        it("roundtrip categorySelector, displayStyle, and modelSelector properties from DrawingViewDefinition", async () => {
            const txn = new EditTxn(imodel, "roundTrip test");
            txn.start();
            const definitionModelId = DefinitionModel.insert(txn, IModelDb.rootSubjectId, "Definition");
            const drawingCategoryId = DrawingCategory.insert(txn, definitionModelId, "DrawingCat", new SubCategoryAppearance());
            const [, drawingModelId] = IModelTestUtils.createAndInsertDrawingPartitionAndModel(txn, Code.createEmpty(), false, IModelDb.rootSubjectId);
            const displayStyle2dId = DisplayStyle2d.insert(txn, definitionModelId, "DisplayStyle2d");
            const categorySelectorId = CategorySelector.insert(txn, definitionModelId, "CategorySelector", [drawingCategoryId]);
            const viewRange = new Range2d(0, 0, 100, 100);
            const drawingViewId = DrawingViewDefinition.insert(txn, definitionModelId, "Test Drawing View", drawingModelId, categorySelectorId, displayStyle2dId, viewRange);
            txn.end("save");
            for await (const row of imodel.createQueryReader(`SELECT $ FROM bis.Element WHERE ECInstanceId = ${drawingViewId} OPTIONS USE_JS_PROP_NAMES`)) {
                const parsed = parseFullInstanceColumn(row.toRow().$);
                // Validate navigation properties in the ECSql result
                assert.isDefined(parsed.categorySelector, "ECSQL $ should contain 'categorySelector'");
                assert.isDefined(parsed.displayStyle, "ECSQL $ should contain 'displayStyle'");
                assert.isDefined(parsed.baseModel, "ECSQL $ should contain 'baseModel'");
                // Validate navigation property IDs
                assert.isTrue(Id64.isValidId64(parsed.categorySelector.id), "categorySelector.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.displayStyle.id), "displayStyle.id should be a valid Id64");
                assert.isTrue(Id64.isValidId64(parsed.baseModel.id), "baseModel.id should be a valid Id64");
                // Try to reconstruct an element from the returned properties
                const reconstructed = imodel.constructEntity(parsed);
                assert.deepEqual(reconstructed.categorySelector, parsed.categorySelector);
                assert.deepEqual(reconstructed.displayStyle, parsed.displayStyle);
                assert.deepEqual(reconstructed.baseModel, parsed.baseModel);
                // Deprecated Id64String props should also be valid (Backward compatibility)
                assert.equal(reconstructed.categorySelectorId, categorySelectorId);
                assert.equal(reconstructed.displayStyleId, displayStyle2dId);
                assert.equal(reconstructed.baseModelId, drawingModelId);
                // Do a quick check of the setter to ensure RelClassName is preserved and only id gets overridden
                reconstructed.categorySelectorId = "Id123";
                reconstructed.baseModelId = "Id456";
                reconstructed.displayStyleId = "Id789";
                assert.deepEqual(reconstructed.categorySelector, { id: "Id123", relClassName: "BisCore.ViewDefinitionUsesCategorySelector" });
                assert.deepEqual(reconstructed.baseModel, { id: "Id456", relClassName: "BisCore.BaseModelForView2d" });
                assert.deepEqual(reconstructed.displayStyle, { id: "Id789", relClassName: "BisCore.ViewDefinitionUsesDisplayStyle" });
            }
        });
    });
});
//# sourceMappingURL=ElementRoundTrip.test.js.map