/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Buffer } from "node:buffer";
import * as chai from "chai";
import { assert, expect } from "chai";
import * as chaiAsPromised from "chai-as-promised";
import * as path from "path";
import { BeEvent, DbResult, Guid, Id64, IModelStatus, omit } from "@itwin/core-bentley";
import { withEditTxn } from "../EditTxn";
import { Base64EncodedString, Code, CodeScopeSpec, ColorDef, Environment, FontType, GeometryParams, GeometryStreamBuilder, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, IModelVersion, PlanProjectionSettings, RelatedElement, RpcConfiguration, RpcManager, RpcPendingResponse, SkyBoxImageType, SubCategoryAppearance, SubCategoryOverride, SyncMode, } from "@itwin/core-common";
import { Box, Cone, LineString3d, Point2d, Point3d, Range2d, Range3d, StandardViewIndex, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
import { CheckpointManager, V2CheckpointManager } from "../CheckpointManager";
import { ClassRegistry } from "../ClassRegistry";
import { _nativeDb, AuxCoordSystem2d, BriefcaseDb, BriefcaseLocalValue, BriefcaseManager, CategorySelector, ChannelControl, DisplayStyle2d, DisplayStyle3d, DrawingCategory, DrawingViewDefinition, Element, ElementOwnsChildElements, ElementOwnsMultiAspects, ElementOwnsUniqueAspect, ExternalSource, ExternalSourceIsInRepository, FunctionalModel, FunctionalSchema, GroupModel, IModelDb, IModelHost, IModelJsFs, InformationPartitionElement, Model, ModelSelector, OrthographicViewDefinition, PhysicalModel, PhysicalObject, PhysicalPartition, RenderMaterialElement, SnapshotDb, SpatialCategory, SubCategory, SubjectOwnsPartitionElements, Texture, ViewDefinition, } from "../core-backend";
import { _hubAccess } from "../internal/Symbols";
import { DefinitionPartition, Drawing, DrawingGraphic, GeometryPart, LinkElement, PhysicalElement, RepositoryLink, Subject } from "../Element";
import { DefinitionModel, DocumentListModel, DrawingModel, InformationRecordModel, SpatialLocationModel } from "../Model";
import { DrawingGraphicRepresentsElement, ElementDrivesElement } from "../Relationship";
import { RpcBriefcaseUtility } from "../rpc-impl/RpcBriefcaseUtility";
import { Schema, Schemas } from "../Schema";
import { HubMock } from "../internal/HubMock";
import { KnownTestLocations } from "./KnownTestLocations";
chai.use(chaiAsPromised);
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
RpcConfiguration.developmentMode = true;
// Initialize the RPC interface classes used by tests
RpcManager.initializeInterface(IModelReadRpcInterface);
export class TestBim extends Schema {
    static get schemaName() { return "TestBim"; }
}
export class TestElementDrivesElement extends ElementDrivesElement {
    static get className() { return "TestElementDrivesElement"; }
    static rootChanged = new BeEvent();
    static deletedDependency = new BeEvent();
    static onRootChangedArg(arg) { this.rootChanged.raiseEvent(arg); }
    static onDeletedDependencyArg(arg) { this.deletedDependency.raiseEvent(arg); }
}
export class TestPhysicalObject extends PhysicalElement {
    static get className() { return "TestPhysicalObject"; }
    static beforeOutputsHandled = new BeEvent();
    static allInputsHandled = new BeEvent();
    static onBeforeOutputsHandledArg(arg) { this.beforeOutputsHandled.raiseEvent(arg); }
    static onAllInputsHandledArg(arg) { this.allInputsHandled.raiseEvent(arg); }
}
/** the types of users available for tests */
export var TestUserType;
(function (TestUserType) {
    TestUserType[TestUserType["Regular"] = 0] = "Regular";
    TestUserType[TestUserType["Manager"] = 1] = "Manager";
    TestUserType[TestUserType["Super"] = 2] = "Super";
    TestUserType[TestUserType["SuperManager"] = 3] = "SuperManager";
})(TestUserType || (TestUserType = {}));
/** A wrapper around the BackendHubAccess API through IModelHost[_hubAccess].
 *
 * All methods in this class should be usable with any BackendHubAccess implementation (i.e. HubMock and IModelHubBackend).
 */
export class HubWrappers {
    static get hubMock() { return HubMock; }
    static isTransientBriefcaseOpenError(error) {
        const message = error instanceof Error ? error.message : String(error);
        return message.includes("EBUSY") || message.includes("EPERM") || message.includes("database is locked");
    }
    static async getAccessToken(user) {
        return TestUserType[user];
    }
    /** Create an iModel with the name provided if it does not already exist. If it does exist, the iModelId is returned. */
    static async createIModel(accessToken, iTwinId, iModelName) {
        assert.isTrue(this.hubMock.isValid, "Must use HubMock for tests that modify iModels");
        let iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName });
        if (!iModelId)
            iModelId = await IModelHost[_hubAccess].createNewIModel({ accessToken, iTwinId, iModelName, description: `Description for iModel` });
        return iModelId;
    }
    /** Deletes and re-creates an iModel with the provided name in the iTwin.
     * @returns the iModelId of the newly created iModel.
    */
    static async recreateIModel(...[arg]) {
        assert.isTrue(this.hubMock.isValid, "Must use HubMock for tests that modify iModels");
        const deleteIModel = await IModelHost[_hubAccess].queryIModelByName(arg);
        if (undefined !== deleteIModel)
            await IModelHost[_hubAccess].deleteIModel({ accessToken: arg.accessToken, iTwinId: arg.iTwinId, iModelId: deleteIModel });
        // Create a new iModel
        return IModelHost[_hubAccess].createNewIModel({ description: `Description for ${arg.iModelName}`, ...arg });
    }
    /** Delete an IModel from the hub */
    static async deleteIModel(accessToken, iTwinId, iModelName) {
        const iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName });
        if (undefined === iModelId)
            return;
        await IModelHost[_hubAccess].deleteIModel({ accessToken, iTwinId, iModelId });
    }
    /** Push an iModel to the Hub */
    static async pushIModel(accessToken, iTwinId, pathname, iModelName, overwrite) {
        // Delete any existing iModels with the same name as the required iModel
        const locIModelName = iModelName || path.basename(pathname, ".bim");
        const iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName: locIModelName });
        if (iModelId) {
            if (!overwrite)
                return iModelId;
            await IModelHost[_hubAccess].deleteIModel({ accessToken, iTwinId, iModelId });
        }
        // Upload a new iModel
        return IModelHost[_hubAccess].createNewIModel({ accessToken, iTwinId, iModelName: locIModelName, version0: pathname });
    }
    /** Helper to open a briefcase db directly with the BriefcaseManager API */
    static async downloadAndOpenBriefcase(args) {
        const maxAttempts = 10;
        for (let attempt = 1;; attempt++) {
            try {
                const props = await BriefcaseManager.downloadBriefcase(args);
                if (args.noLock) {
                    const briefcase = await BriefcaseDb.open({ fileName: props.fileName });
                    briefcase[_nativeDb].saveLocalValue(BriefcaseLocalValue.NoLocking, "true");
                    briefcase[_nativeDb].saveChanges();
                    briefcase.close();
                }
                return await BriefcaseDb.open({ fileName: props.fileName });
            }
            catch (error) {
                if (attempt >= maxAttempts || !this.isTransientBriefcaseOpenError(error))
                    throw error;
                await new Promise((resolve) => setTimeout(resolve, attempt * 250));
            }
        }
    }
    /** Opens the specific iModel as a Briefcase through the same workflow the IModelReadRpc.getConnectionProps method will use. Replicates the way a frontend would open the iModel. */
    static async openBriefcaseUsingRpc(args) {
        if (undefined === args.asOf)
            args.asOf = IModelVersion.latest().toJSON();
        const openArgs = {
            tokenProps: {
                iTwinId: args.iTwinId,
                iModelId: args.iModelId,
                changeset: (await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId })),
            },
            activity: { accessToken: args.accessToken, activityId: "", applicationId: "", applicationVersion: "", sessionId: "" },
            syncMode: args.briefcaseId === 0 ? SyncMode.PullOnly : SyncMode.PullAndPush,
            forceDownload: args.deleteFirst,
        };
        assert.isTrue(this.hubMock.isValid || openArgs.syncMode === SyncMode.PullOnly, "use HubMock to acquire briefcases");
        while (true) {
            try {
                // eslint-disable-next-line @typescript-eslint/no-deprecated
                return (await RpcBriefcaseUtility.open(openArgs));
            }
            catch (error) {
                if (!(error instanceof RpcPendingResponse))
                    throw error;
            }
        }
    }
    /** Downloads a checkpoint and opens it as a SnapShotDb */
    static async downloadAndOpenCheckpoint(args) {
        if (undefined === args.asOf)
            args.asOf = IModelVersion.latest().toJSON();
        const checkpoint = {
            iTwinId: args.iTwinId,
            iModelId: args.iModelId,
            accessToken: args.accessToken,
            changeset: (await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId })),
        };
        const folder = path.join(V2CheckpointManager.getFolder(), checkpoint.iModelId);
        const filename = path.join(folder, `${checkpoint.changeset.id === "" ? "first" : checkpoint.changeset.id}.bim`);
        const request = { checkpoint, localFile: filename };
        let db = SnapshotDb.tryFindByKey(CheckpointManager.getKey(request.checkpoint));
        if (undefined !== db)
            return db;
        db = IModelTestUtils.tryOpenLocalFile(request);
        if (db)
            return db;
        await V2CheckpointManager.downloadCheckpoint(request);
        await CheckpointManager.updateToRequestedVersion(request);
        return IModelTestUtils.openCheckpoint(request.localFile, request.checkpoint);
    }
    /** Opens the specific Checkpoint iModel, `SyncMode.FixedVersion`, through the same workflow the IModelReadRpc.getConnectionProps method will use. Replicates the way a frontend would open the iModel. */
    static async openCheckpointUsingRpc(args) {
        if (undefined === args.asOf)
            args.asOf = IModelVersion.latest().toJSON();
        const changeset = await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId });
        const openArgs = {
            tokenProps: {
                iTwinId: args.iTwinId,
                iModelId: args.iModelId,
                changeset,
            },
            activity: { accessToken: args.accessToken, activityId: "", applicationId: "", applicationVersion: "", sessionId: "" },
            syncMode: SyncMode.FixedVersion,
            forceDownload: args.deleteFirst,
        };
        while (true) {
            try {
                return (await RpcBriefcaseUtility.open(openArgs));
            }
            catch (error) {
                if (!(error instanceof RpcPendingResponse))
                    throw error;
            }
        }
    }
    /**
     * Purges all acquired briefcases for the specified iModel (and user), if the specified threshold of acquired briefcases is exceeded
     */
    static async purgeAcquiredBriefcasesById(accessToken, iModelId, onReachThreshold = () => { }, acquireThreshold = 16) {
        const briefcases = await IModelHost[_hubAccess].getMyBriefcaseIds({ accessToken, iModelId });
        if (briefcases.length > acquireThreshold) {
            if (undefined !== onReachThreshold)
                onReachThreshold();
            const promises = [];
            briefcases.forEach((briefcaseId) => {
                promises.push(IModelHost[_hubAccess].releaseBriefcase({ accessToken, iModelId, briefcaseId }));
            });
            await Promise.all(promises);
        }
    }
    static async closeAndDeleteBriefcaseDb(accessToken, briefcaseDb) {
        const fileName = briefcaseDb.pathName;
        const iModelId = briefcaseDb.iModelId;
        briefcaseDb.close();
        await BriefcaseManager.deleteBriefcaseFiles(fileName, accessToken);
        // try to clean up empty briefcase directories, and empty iModel directories.
        if (0 === BriefcaseManager.getCachedBriefcases(iModelId).length) {
            IModelJsFs.removeSync(BriefcaseManager.getBriefcaseBasePath(iModelId));
            const imodelPath = BriefcaseManager.getIModelPath(iModelId);
            if (0 === IModelJsFs.readdirSync(imodelPath).length) {
                IModelJsFs.removeSync(imodelPath);
            }
        }
    }
}
export class IModelTestUtils {
    static get knownTestLocations() { return KnownTestLocations; }
    /** Generate a name for an iModel that's unique using the baseName provided and appending a new GUID.  */
    static generateUniqueName(baseName) {
        return `${baseName} - ${Guid.createValue()}`;
    }
    static openCheckpoint(fileName, checkpoint) {
        const snapshot = SnapshotDb.openFile(fileName, { key: CheckpointManager.getKey(checkpoint) });
        snapshot._iTwinId = checkpoint.iTwinId;
        return snapshot;
    }
    /** try to open an existing local file to satisfy a download request */
    static tryOpenLocalFile(request) {
        const checkpoint = request.checkpoint;
        if (CheckpointManager.verifyCheckpoint(checkpoint, request.localFile))
            return this.openCheckpoint(request.localFile, checkpoint);
        return undefined;
    }
    /** Prepare for an output file by:
     * - Resolving the output file name under the known test output directory
     * - Making directories as necessary
     * - Removing a previous copy of the output file
     * @param subDirName Sub-directory under known test output directory. Should match the name of the test file minus the .test.ts file extension.
     * @param fileName Name of output fille
     */
    static prepareOutputFile(subDirName, fileName) {
        if (!IModelJsFs.existsSync(this.knownTestLocations.outputDir))
            IModelJsFs.mkdirSync(this.knownTestLocations.outputDir);
        const outputDir = path.join(this.knownTestLocations.outputDir, subDirName);
        if (!IModelJsFs.existsSync(outputDir))
            IModelJsFs.mkdirSync(outputDir);
        const outputFile = path.join(outputDir, fileName);
        if (IModelJsFs.existsSync(outputFile))
            IModelJsFs.unlinkSync(outputFile);
        return outputFile;
    }
    /** Resolve an asset file path from the asset name by looking in the known assets directory */
    static resolveAssetFile(assetName) {
        const assetFile = path.join(this.knownTestLocations.assetsDir, assetName);
        assert.isTrue(IModelJsFs.existsSync(assetFile));
        return assetFile;
    }
    static resolveFontFile(fontName) {
        const subDirs = ["Karla", "DejaVu", "Sitka"];
        const fontSubDirectory = subDirs.find((x) => fontName.startsWith(x));
        fontName = fontSubDirectory ? path.join(fontSubDirectory, fontName) : fontName;
        const assetName = path.join("Fonts", fontName);
        return this.resolveAssetFile(assetName);
    }
    /** Orchestrates the steps necessary to create a new snapshot iModel from a seed file. */
    static createSnapshotFromSeed(testFileName, seedFileName) {
        const seedDb = SnapshotDb.openFile(seedFileName);
        const testDb = SnapshotDb.createFrom(seedDb, testFileName);
        seedDb.close();
        testDb.channels.addAllowedChannel(ChannelControl.sharedChannelName);
        return testDb;
    }
    static getUniqueModelCode(testDb, newModelCodeBase) {
        let newModelCode = newModelCodeBase;
        let iter = 0;
        while (true) {
            const modelCode = InformationPartitionElement.createCode(testDb, IModel.rootSubjectId, newModelCode);
            if (testDb.elements.queryElementIdByCode(modelCode) === undefined)
                return modelCode;
            newModelCode = newModelCodeBase + iter;
            ++iter;
        }
    }
    static generateChangeSetId() {
        let result = "";
        for (let i = 0; i < 20; ++i) {
            result += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
        }
        return { id: result };
    }
    /** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
    static createAndInsertPhysicalPartition(txn, newModelCode, parentId) {
        const model = parentId ? txn.iModel.elements.getElement(parentId).model : IModel.repositoryModelId;
        const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
        const modeledElementProps = {
            classFullName: PhysicalPartition.classFullName,
            parent,
            model,
            code: newModelCode,
        };
        const modeledElement = txn.iModel.elements.createElement(modeledElementProps);
        return txn.insertElement(modeledElement.toJSON());
    }
    /** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
    static async createAndInsertPhysicalPartitionAsync(txn, newModelCode, parentId) {
        const model = parentId ? txn.iModel.elements.getElement(parentId).model : IModel.repositoryModelId;
        const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
        const modeledElementProps = {
            classFullName: PhysicalPartition.classFullName,
            parent,
            model,
            code: newModelCode,
        };
        const modeledElement = txn.iModel.elements.createElement(modeledElementProps);
        await txn.iModel.locks.acquireLocks({ shared: model });
        return txn.insertElement(modeledElement.toJSON());
    }
    /** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
    static createAndInsertPhysicalModel(txn, modeledElementRef, privateModel = false) {
        const newModel = txn.iModel.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName, isPrivate: privateModel });
        const newModelId = txn.insertModel(newModel.toJSON());
        newModel.id = newModelId;
        assert.isTrue(Id64.isValidId64(newModelId));
        assert.isTrue(Id64.isValidId64(newModel.id));
        assert.deepEqual(newModelId, newModel.id);
        return newModelId;
    }
    /** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
    static async createAndInsertPhysicalModelAsync(txn, modeledElementRef, privateModel = false) {
        const newModel = txn.iModel.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName, isPrivate: privateModel });
        const newModelId = txn.insertModel(newModel.toJSON());
        newModel.id = newModelId;
        assert.isTrue(Id64.isValidId64(newModelId));
        assert.isTrue(Id64.isValidId64(newModel.id));
        assert.deepEqual(newModelId, newModel.id);
        return newModelId;
    }
    /**
     * Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel.
     * @return [modeledElementId, modelId]
     */
    static createAndInsertPhysicalPartitionAndModel(txn, newModelCode, privateModel = false, parent) {
        const eid = IModelTestUtils.createAndInsertPhysicalPartition(txn, newModelCode, parent);
        const modeledElementRef = new RelatedElement({ id: eid });
        const mid = IModelTestUtils.createAndInsertPhysicalModel(txn, modeledElementRef, privateModel);
        return [eid, mid];
    }
    /**
     * Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel.
     * @return [modeledElementId, modelId]
     */
    static async createAndInsertPhysicalPartitionAndModelAsync(txn, newModelCode, privateModel = false, parentId) {
        const eid = await IModelTestUtils.createAndInsertPhysicalPartitionAsync(txn, newModelCode, parentId);
        const modeledElementRef = new RelatedElement({ id: eid });
        const mid = await IModelTestUtils.createAndInsertPhysicalModelAsync(txn, modeledElementRef, privateModel);
        return [eid, mid];
    }
    /** Create and insert a Drawing Partition element (in the repositoryModel). */
    static createAndInsertDrawingPartition(txn, newModelCode, parentId) {
        const model = parentId ? txn.iModel.elements.getElement(parentId).model : IModel.repositoryModelId;
        const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
        const modeledElementProps = {
            classFullName: Drawing.classFullName,
            parent,
            model,
            code: newModelCode,
        };
        const modeledElement = txn.iModel.elements.createElement(modeledElementProps);
        return txn.insertElement(modeledElement.toJSON());
    }
    /** Create and insert a DrawingModel associated with Drawing Partition. */
    static createAndInsertDrawingModel(txn, modeledElementRef, privateModel = false) {
        const newModel = txn.iModel.models.createModel({ modeledElement: modeledElementRef, classFullName: DrawingModel.classFullName, isPrivate: privateModel });
        const newModelId = txn.insertModel(newModel.toJSON());
        newModel.id = newModelId;
        assert.isTrue(Id64.isValidId64(newModelId));
        assert.isTrue(Id64.isValidId64(newModel.id));
        assert.deepEqual(newModelId, newModel.id);
        return newModelId;
    }
    /**
     * Create and insert a Drawing Partition element (in the repositoryModel) and an associated DrawingModel.
     * @return [modeledElementId, modelId]
     */
    static createAndInsertDrawingPartitionAndModel(txn, newModelCode, privateModel = false, parent) {
        const eid = IModelTestUtils.createAndInsertDrawingPartition(txn, newModelCode, parent);
        const modeledElementRef = new RelatedElement({ id: eid });
        const mid = IModelTestUtils.createAndInsertDrawingModel(txn, modeledElementRef, privateModel);
        return [eid, mid];
    }
    static getUniqueSpatialCategoryCode(scopeModel, newCodeBaseValue) {
        let newCodeValue = newCodeBaseValue;
        let iter = 0;
        while (true) {
            if (SpatialCategory.queryCategoryIdByName(scopeModel.iModel, scopeModel.id, newCodeValue) === undefined)
                return SpatialCategory.createCode(scopeModel.iModel, scopeModel.id, newCodeValue);
            newCodeValue = newCodeBaseValue + iter;
            ++iter;
        }
    }
    // Create a PhysicalObject. (Does not insert it.)
    static createPhysicalObject(testImodel, modelId, categoryId, elemCode) {
        const elementProps = {
            classFullName: "Generic:PhysicalObject",
            model: modelId,
            category: categoryId,
            code: elemCode ? elemCode : Code.createEmpty(),
        };
        return testImodel.elements.createElement(elementProps);
    }
    static registerTestBimSchema() {
        if (undefined === Schemas.getRegisteredSchema(TestBim.schemaName)) {
            Schemas.registerSchema(TestBim);
            ClassRegistry.register(TestPhysicalObject, TestBim);
            ClassRegistry.register(TestElementDrivesElement, TestBim);
        }
    }
    static executeQuery(db, ecsql, bindings) {
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return db.withPreparedStatement(ecsql, (stmt) => {
            if (bindings)
                stmt.bindValues(bindings);
            const rows = [];
            while (DbResult.BE_SQLITE_ROW === stmt.step()) {
                rows.push(stmt.getRow());
                if (rows.length > IModelDb.maxLimit)
                    throw new IModelError(IModelStatus.BadRequest, "Max LIMIT exceeded in SELECT statement");
            }
            return rows;
        });
    }
    static createJobSubjectElement(iModel, name) {
        const subj = Subject.create(iModel, iModel.elements.getRootSubject().id, name);
        subj.setJsonProperty("Subject", { Job: name }); // eslint-disable-line @typescript-eslint/naming-convention
        return subj;
    }
    /** Flushes the Txns in the TxnTable - this allows importing of schemas */
    static flushTxns(iModelDb) {
        iModelDb[_nativeDb].deleteAllTxns();
        return true;
    }
    static querySubjectId(iModelDb, subjectCodeValue) {
        const subjectId = iModelDb.elements.queryElementIdByCode(Subject.createCode(iModelDb, IModel.rootSubjectId, subjectCodeValue));
        assert.isTrue(Id64.isValidId64(subjectId));
        return subjectId;
    }
    static queryDefinitionPartitionId(iModelDb, parentSubjectId, suffix) {
        const partitionCode = DefinitionPartition.createCode(iModelDb, parentSubjectId, `Definition${suffix}`);
        const partitionId = iModelDb.elements.queryElementIdByCode(partitionCode);
        assert.isTrue(Id64.isValidId64(partitionId));
        return partitionId;
    }
    static querySpatialCategoryId(iModelDb, modelId, suffix) {
        const categoryCode = SpatialCategory.createCode(iModelDb, modelId, `SpatialCategory${suffix}`);
        const categoryId = iModelDb.elements.queryElementIdByCode(categoryCode);
        assert.isTrue(Id64.isValidId64(categoryId));
        return categoryId;
    }
    static queryPhysicalPartitionId(iModelDb, parentSubjectId, suffix) {
        const partitionCode = PhysicalPartition.createCode(iModelDb, parentSubjectId, `Physical${suffix}`);
        const partitionId = iModelDb.elements.queryElementIdByCode(partitionCode);
        assert.isTrue(Id64.isValidId64(partitionId));
        return partitionId;
    }
    static queryPhysicalElementId(iModelDb, modelId, categoryId, suffix) {
        const elementId = IModelTestUtils.queryByUserLabel(iModelDb, `PhysicalObject${suffix}`);
        assert.isTrue(Id64.isValidId64(elementId));
        const element = iModelDb.elements.getElement(elementId);
        assert.equal(element.model, modelId);
        assert.equal(element.category, categoryId);
        return elementId;
    }
    static insertSpatialCategory(iModelDb, modelId, categoryName, color) {
        const appearance = {
            color: color.toJSON(),
            transp: 0,
            invisible: false,
        };
        return withEditTxn(iModelDb, (txn) => SpatialCategory.insert(txn, modelId, categoryName, appearance));
    }
    static createBoxes(subCategoryIds) {
        const length = 1.0;
        const entryOrigin = Point3d.createZero();
        const geometryStreamBuilder = new GeometryStreamBuilder();
        geometryStreamBuilder.appendGeometry(Box.createDgnBox(entryOrigin, Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, length), length, length, length, length, true));
        for (const subCategoryId of subCategoryIds) {
            entryOrigin.addInPlace({ x: 1, y: 1, z: 1 });
            geometryStreamBuilder.appendSubCategoryChange(subCategoryId);
            geometryStreamBuilder.appendGeometry(Box.createDgnBox(entryOrigin, Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, length), length, length, length, length, true));
        }
        return geometryStreamBuilder.geometryStream;
    }
    static createBox(size, categoryId, subCategoryId, renderMaterialId, geometryPartId) {
        const geometryStreamBuilder = new GeometryStreamBuilder();
        if ((undefined !== categoryId) && (undefined !== subCategoryId)) {
            geometryStreamBuilder.appendSubCategoryChange(subCategoryId);
            if (undefined !== renderMaterialId) {
                const geometryParams = new GeometryParams(categoryId, subCategoryId);
                geometryParams.materialId = renderMaterialId;
                geometryStreamBuilder.appendGeometryParamsChange(geometryParams);
            }
        }
        geometryStreamBuilder.appendGeometry(Box.createDgnBox(Point3d.createZero(), Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, size.z), size.x, size.y, size.x, size.y, true));
        if (undefined !== geometryPartId) {
            geometryStreamBuilder.appendGeometryPart3d(geometryPartId);
        }
        return geometryStreamBuilder.geometryStream;
    }
    static insertTextureElement(txn, modelId, textureName) {
        // This is an encoded png containing a 3x3 square with white in top left pixel, blue in middle pixel, and green in bottom right pixel. The rest of the square is red.
        const pngData = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 3, 0, 0, 0, 3, 8, 2, 0, 0, 0, 217, 74, 34, 232, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 24, 73, 68, 65, 84, 24, 87, 99, 248, 15, 4, 12, 12, 64, 4, 198, 64, 46, 132, 5, 162, 254, 51, 0, 0, 195, 90, 10, 246, 127, 175, 154, 145, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130];
        return Texture.insertTexture(txn, modelId, textureName, ImageSourceFormat.Png, Buffer.from(pngData).toString("base64"), `Description for ${textureName}`);
    }
    static createCylinder(radius) {
        const pointA = Point3d.create(0, 0, 0);
        const pointB = Point3d.create(0, 0, 2 * radius);
        const cylinder = Cone.createBaseAndTarget(pointA, pointB, Vector3d.unitX(), Vector3d.unitY(), radius, radius, true);
        const geometryStreamBuilder = new GeometryStreamBuilder();
        geometryStreamBuilder.appendGeometry(cylinder);
        return geometryStreamBuilder.geometryStream;
    }
    static createRectangle(size) {
        const geometryStreamBuilder = new GeometryStreamBuilder();
        geometryStreamBuilder.appendGeometry(LineString3d.createPoints([
            new Point3d(0, 0),
            new Point3d(size.x, 0),
            new Point3d(size.x, size.y),
            new Point3d(0, size.y),
            new Point3d(0, 0),
        ]));
        return geometryStreamBuilder.geometryStream;
    }
    static queryByUserLabel(iModelDb, userLabel) {
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE UserLabel=:userLabel`, (statement) => {
            statement.bindString("userLabel", userLabel);
            return DbResult.BE_SQLITE_ROW === statement.step() ? statement.getValue(0).getId() : Id64.invalid;
        });
    }
    static queryByCodeValue(iModelDb, codeValue) {
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE CodeValue=:codeValue`, (statement) => {
            statement.bindString("codeValue", codeValue);
            return DbResult.BE_SQLITE_ROW === statement.step() ? statement.getValue(0).getId() : Id64.invalid;
        });
    }
    static insertRepositoryLink(txn, codeValue, url, format) {
        const repositoryLinkProps = {
            classFullName: RepositoryLink.classFullName,
            model: IModel.repositoryModelId,
            code: LinkElement.createCode(txn.iModel, IModel.repositoryModelId, codeValue),
            url,
            format,
        };
        return txn.insertElement(repositoryLinkProps);
    }
    static insertExternalSource(txn, repositoryId, userLabel) {
        const externalSourceProps = {
            classFullName: ExternalSource.classFullName,
            model: IModel.repositoryModelId,
            code: Code.createEmpty(),
            userLabel,
            repository: new ExternalSourceIsInRepository(repositoryId),
            connectorName: "Connector",
            connectorVersion: "0.0.1",
        };
        return txn.insertElement(externalSourceProps);
    }
    static dumpIModelInfo(iModelDb) {
        const outputFileName = `${iModelDb.pathName}.info.txt`;
        if (IModelJsFs.existsSync(outputFileName)) {
            IModelJsFs.removeSync(outputFileName);
        }
        IModelJsFs.appendFileSync(outputFileName, `${iModelDb.pathName}\n`);
        IModelJsFs.appendFileSync(outputFileName, "\n=== CodeSpecs ===\n");
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT ECInstanceId,Name FROM BisCore:CodeSpec ORDER BY ECInstanceId`, (statement) => {
            while (DbResult.BE_SQLITE_ROW === statement.step()) {
                const codeSpecId = statement.getValue(0).getId();
                const codeSpecName = statement.getValue(1).getString();
                IModelJsFs.appendFileSync(outputFileName, `${codeSpecId}, ${codeSpecName}\n`);
            }
        });
        IModelJsFs.appendFileSync(outputFileName, "\n=== Schemas ===\n");
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT Name FROM ECDbMeta.ECSchemaDef ORDER BY ECInstanceId`, (statement) => {
            while (DbResult.BE_SQLITE_ROW === statement.step()) {
                const schemaName = statement.getValue(0).getString();
                IModelJsFs.appendFileSync(outputFileName, `${schemaName}\n`);
            }
        });
        IModelJsFs.appendFileSync(outputFileName, "\n=== Models ===\n");
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Model.classFullName} ORDER BY ECInstanceId`, (statement) => {
            while (DbResult.BE_SQLITE_ROW === statement.step()) {
                const modelId = statement.getValue(0).getId();
                const model = iModelDb.models.getModel(modelId);
                IModelJsFs.appendFileSync(outputFileName, `${modelId}, ${model.name}, ${model.parentModel}, ${model.classFullName}\n`);
            }
        });
        IModelJsFs.appendFileSync(outputFileName, "\n=== ViewDefinitions ===\n");
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${ViewDefinition.classFullName} ORDER BY ECInstanceId`, (statement) => {
            while (DbResult.BE_SQLITE_ROW === statement.step()) {
                const viewDefinitionId = statement.getValue(0).getId();
                const viewDefinition = iModelDb.elements.getElement(viewDefinitionId);
                IModelJsFs.appendFileSync(outputFileName, `${viewDefinitionId}, ${viewDefinition.code.value}, ${viewDefinition.classFullName}\n`);
            }
        });
        IModelJsFs.appendFileSync(outputFileName, "\n=== Elements ===\n");
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${Element.classFullName}`, (statement) => {
            if (DbResult.BE_SQLITE_ROW === statement.step()) {
                const count = statement.getValue(0).getInteger();
                IModelJsFs.appendFileSync(outputFileName, `Count of ${Element.classFullName}=${count}\n`);
            }
        });
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${PhysicalObject.classFullName}`, (statement) => {
            if (DbResult.BE_SQLITE_ROW === statement.step()) {
                const count = statement.getValue(0).getInteger();
                IModelJsFs.appendFileSync(outputFileName, `Count of ${PhysicalObject.classFullName}=${count}\n`);
            }
        });
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${GeometryPart.classFullName}`, (statement) => {
            if (DbResult.BE_SQLITE_ROW === statement.step()) {
                const count = statement.getValue(0).getInteger();
                IModelJsFs.appendFileSync(outputFileName, `Count of ${GeometryPart.classFullName}=${count}\n`);
            }
        });
    }
}
export class ExtensiveTestScenario {
    static uniqueAspectGuid = Guid.createValue();
    static federationGuid3 = Guid.createValue();
    static async prepareDb(sourceDb) {
        // Import desired schemas
        const sourceSchemaFileName = path.join(KnownTestLocations.assetsDir, "ExtensiveTestScenario.ecschema.xml");
        await sourceDb.importSchemas([FunctionalSchema.schemaFilePath, sourceSchemaFileName]);
        FunctionalSchema.registerSchema();
    }
    static async populateDb(sourceDb) {
        // make sure Arial is in the font table
        const arialFontId = await sourceDb.fonts.acquireId({ name: "Arial", type: FontType.TrueType });
        expect(arialFontId).not.to.be.undefined;
        expect(arialFontId).greaterThan(0);
        // Initialize project extents
        const projectExtents = new Range3d(-1000, -1000, -1000, 1000, 1000, 1000);
        await withEditTxn(sourceDb, async (txn) => {
            txn.updateProjectExtents(projectExtents);
            // Insert CodeSpecs
            const codeSpecId1 = sourceDb.codeSpecs.insert(txn, "SourceCodeSpec", CodeScopeSpec.Type.Model);
            const codeSpecId2 = sourceDb.codeSpecs.insert(txn, "ExtraCodeSpec", CodeScopeSpec.Type.ParentElement);
            const codeSpecId3 = sourceDb.codeSpecs.insert(txn, "InformationRecords", CodeScopeSpec.Type.Model);
            assert.isTrue(Id64.isValidId64(codeSpecId1));
            assert.isTrue(Id64.isValidId64(codeSpecId2));
            assert.isTrue(Id64.isValidId64(codeSpecId3));
            // Insert RepositoryModel structure
            const subjectId = Subject.insert(txn, IModel.rootSubjectId, "Subject", "Subject Description");
            assert.isTrue(Id64.isValidId64(subjectId));
            const sourceOnlySubjectId = Subject.insert(txn, IModel.rootSubjectId, "Only in Source");
            assert.isTrue(Id64.isValidId64(sourceOnlySubjectId));
            const definitionModelId = DefinitionModel.insert(txn, subjectId, "Definition");
            assert.isTrue(Id64.isValidId64(definitionModelId));
            const informationModelId = InformationRecordModel.insert(txn, subjectId, "Information");
            assert.isTrue(Id64.isValidId64(informationModelId));
            const groupModelId = GroupModel.insert(txn, subjectId, "Group");
            assert.isTrue(Id64.isValidId64(groupModelId));
            const physicalModelId = PhysicalModel.insert(txn, subjectId, "Physical");
            assert.isTrue(Id64.isValidId64(physicalModelId));
            const spatialLocationModelId = SpatialLocationModel.insert(txn, subjectId, "SpatialLocation", true);
            assert.isTrue(Id64.isValidId64(spatialLocationModelId));
            const functionalModelId = FunctionalModel.insert(txn, subjectId, "Functional");
            assert.isTrue(Id64.isValidId64(functionalModelId));
            const documentListModelId = DocumentListModel.insert(txn, subjectId, "Document");
            assert.isTrue(Id64.isValidId64(documentListModelId));
            const drawingId = Drawing.insert(txn, documentListModelId, "Drawing");
            assert.isTrue(Id64.isValidId64(drawingId));
            // Insert DefinitionElements
            const modelSelectorId = ModelSelector.insert(txn, definitionModelId, "SpatialModels", [physicalModelId, spatialLocationModelId]);
            assert.isTrue(Id64.isValidId64(modelSelectorId));
            const spatialCategoryId = SpatialCategory.insert(txn, definitionModelId, "SpatialCategory", { color: ColorDef.green.toJSON(), transp: 0, invisible: false });
            assert.isTrue(Id64.isValidId64(spatialCategoryId));
            const sourcePhysicalCategoryId = SpatialCategory.insert(txn, definitionModelId, "SourcePhysicalCategory", { color: ColorDef.blue.toJSON(), transp: 0, invisible: false });
            assert.isTrue(Id64.isValidId64(sourcePhysicalCategoryId));
            const subCategoryId = SubCategory.insert(txn, spatialCategoryId, "SubCategory", { color: ColorDef.blue.toJSON() });
            assert.isTrue(Id64.isValidId64(subCategoryId));
            const filteredSubCategoryId = SubCategory.insert(txn, spatialCategoryId, "FilteredSubCategory", { color: ColorDef.green.toJSON() });
            assert.isTrue(Id64.isValidId64(filteredSubCategoryId));
            const drawingCategoryId = DrawingCategory.insert(txn, definitionModelId, "DrawingCategory", new SubCategoryAppearance());
            assert.isTrue(Id64.isValidId64(drawingCategoryId));
            const spatialCategorySelectorId = CategorySelector.insert(txn, definitionModelId, "SpatialCategories", [spatialCategoryId, sourcePhysicalCategoryId]);
            assert.isTrue(Id64.isValidId64(spatialCategorySelectorId));
            const drawingCategorySelectorId = CategorySelector.insert(txn, definitionModelId, "DrawingCategories", [drawingCategoryId]);
            assert.isTrue(Id64.isValidId64(drawingCategorySelectorId));
            const auxCoordSystemProps = {
                classFullName: AuxCoordSystem2d.classFullName,
                model: definitionModelId,
                code: AuxCoordSystem2d.createCode(sourceDb, definitionModelId, "AuxCoordSystem2d"),
            };
            const auxCoordSystemId = txn.insertElement(auxCoordSystemProps);
            assert.isTrue(Id64.isValidId64(auxCoordSystemId));
            const pngData = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 3, 0, 0, 0, 3, 8, 2, 0, 0, 0, 217, 74, 34, 232, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 24, 73, 68, 65, 84, 24, 87, 99, 248, 15, 4, 12, 12, 64, 4, 198, 64, 46, 132, 5, 162, 254, 51, 0, 0, 195, 90, 10, 246, 127, 175, 154, 145, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130];
            const textureId = Texture.insertTexture(txn, definitionModelId, "Texture", ImageSourceFormat.Png, Buffer.from(pngData).toString("base64"), "Description for Texture");
            assert.isTrue(Id64.isValidId64(textureId));
            const renderMaterialId = RenderMaterialElement.insert(txn, definitionModelId, "RenderMaterial", { paletteName: "PaletteName" });
            assert.isTrue(Id64.isValidId64(renderMaterialId));
            const geometryPartProps = {
                classFullName: GeometryPart.classFullName,
                model: definitionModelId,
                code: GeometryPart.createCode(sourceDb, definitionModelId, "GeometryPart"),
                geom: IModelTestUtils.createBox(Point3d.create(3, 3, 3)),
            };
            const geometryPartId = txn.insertElement(geometryPartProps);
            assert.isTrue(Id64.isValidId64(geometryPartId));
            // Insert InformationRecords
            const informationRecordProps1 = {
                classFullName: "ExtensiveTestScenario:SourceInformationRecord",
                model: informationModelId,
                code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord1" },
                commonString: "Common1",
                sourceString: "One",
            };
            const informationRecordId1 = txn.insertElement(informationRecordProps1);
            assert.isTrue(Id64.isValidId64(informationRecordId1));
            const informationRecordProps2 = {
                classFullName: "ExtensiveTestScenario:SourceInformationRecord",
                model: informationModelId,
                code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord2" },
                commonString: "Common2",
                sourceString: "Two",
            };
            const informationRecordId2 = txn.insertElement(informationRecordProps2);
            assert.isTrue(Id64.isValidId64(informationRecordId2));
            const informationRecordProps3 = {
                classFullName: "ExtensiveTestScenario:SourceInformationRecord",
                model: informationModelId,
                code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord3" },
                commonString: "Common3",
                sourceString: "Three",
            };
            const informationRecordId3 = txn.insertElement(informationRecordProps3);
            assert.isTrue(Id64.isValidId64(informationRecordId3));
            // Insert PhysicalObject1
            const physicalObjectProps1 = {
                classFullName: PhysicalObject.classFullName,
                model: physicalModelId,
                category: spatialCategoryId,
                code: Code.createEmpty(),
                userLabel: "PhysicalObject1",
                geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, subCategoryId, renderMaterialId, geometryPartId),
                placement: {
                    origin: Point3d.create(1, 1, 1),
                    angles: YawPitchRollAngles.createDegrees(0, 0, 0),
                },
            };
            const physicalObjectId1 = txn.insertElement(physicalObjectProps1);
            assert.isTrue(Id64.isValidId64(physicalObjectId1));
            // Insert PhysicalObject1 children
            const childObjectProps1A = physicalObjectProps1;
            childObjectProps1A.userLabel = "ChildObject1A";
            childObjectProps1A.parent = new ElementOwnsChildElements(physicalObjectId1);
            childObjectProps1A.placement.origin = Point3d.create(0, 1, 1);
            const childObjectId1A = txn.insertElement(childObjectProps1A);
            assert.isTrue(Id64.isValidId64(childObjectId1A));
            const childObjectProps1B = childObjectProps1A;
            childObjectProps1B.userLabel = "ChildObject1B";
            childObjectProps1B.placement.origin = Point3d.create(1, 0, 1);
            const childObjectId1B = txn.insertElement(childObjectProps1B);
            assert.isTrue(Id64.isValidId64(childObjectId1B));
            // Insert PhysicalObject2
            const physicalObjectProps2 = {
                classFullName: PhysicalObject.classFullName,
                model: physicalModelId,
                category: sourcePhysicalCategoryId,
                code: Code.createEmpty(),
                userLabel: "PhysicalObject2",
                geom: IModelTestUtils.createBox(Point3d.create(2, 2, 2)),
                placement: {
                    origin: Point3d.create(2, 2, 2),
                    angles: YawPitchRollAngles.createDegrees(0, 0, 0),
                },
            };
            const physicalObjectId2 = txn.insertElement(physicalObjectProps2);
            assert.isTrue(Id64.isValidId64(physicalObjectId2));
            // Insert PhysicalObject3
            const physicalObjectProps3 = {
                classFullName: PhysicalObject.classFullName,
                model: physicalModelId,
                category: sourcePhysicalCategoryId,
                code: Code.createEmpty(),
                federationGuid: ExtensiveTestScenario.federationGuid3,
                userLabel: "PhysicalObject3",
            };
            const physicalObjectId3 = txn.insertElement(physicalObjectProps3);
            assert.isTrue(Id64.isValidId64(physicalObjectId3));
            // Insert PhysicalObject4
            const physicalObjectProps4 = {
                classFullName: PhysicalObject.classFullName,
                model: physicalModelId,
                category: spatialCategoryId,
                code: Code.createEmpty(),
                userLabel: "PhysicalObject4",
                geom: IModelTestUtils.createBoxes([subCategoryId, filteredSubCategoryId]),
                placement: {
                    origin: Point3d.create(4, 4, 4),
                    angles: YawPitchRollAngles.createDegrees(0, 0, 0),
                },
            };
            const physicalObjectId4 = txn.insertElement(physicalObjectProps4);
            assert.isTrue(Id64.isValidId64(physicalObjectId4));
            // Insert PhysicalElement1
            const sourcePhysicalElementProps = {
                classFullName: "ExtensiveTestScenario:SourcePhysicalElement",
                model: physicalModelId,
                category: sourcePhysicalCategoryId,
                code: Code.createEmpty(),
                userLabel: "PhysicalElement1",
                geom: IModelTestUtils.createBox(Point3d.create(2, 2, 2)),
                placement: {
                    origin: Point3d.create(4, 4, 4),
                    angles: YawPitchRollAngles.createDegrees(0, 0, 0),
                },
                sourceString: "S1",
                sourceDouble: 1.1,
                sourceNavigation: { id: sourcePhysicalCategoryId, relClassName: "ExtensiveTestScenario:SourcePhysicalElementUsesSourceDefinition" },
                commonNavigation: { id: sourcePhysicalCategoryId },
                commonString: "Common",
                commonDouble: 7.3,
                sourceBinary: new Uint8Array([1, 3, 5, 7]),
                commonBinary: Base64EncodedString.fromUint8Array(new Uint8Array([2, 4, 6, 8])),
                extraString: "Extra",
            };
            const sourcePhysicalElementId = txn.insertElement(sourcePhysicalElementProps);
            assert.isTrue(Id64.isValidId64(sourcePhysicalElementId));
            assert.doesNotThrow(() => sourceDb.elements.getElement(sourcePhysicalElementId));
            // Insert ElementAspects
            const aspectProps = {
                classFullName: "ExtensiveTestScenario:SourceUniqueAspect",
                element: new ElementOwnsUniqueAspect(physicalObjectId1),
                commonDouble: 1.1,
                commonString: "Unique",
                commonLong: physicalObjectId1,
                commonBinary: Base64EncodedString.fromUint8Array(new Uint8Array([2, 4, 6, 8])),
                sourceDouble: 11.1,
                sourceString: "UniqueAspect",
                sourceLong: physicalObjectId1,
                sourceGuid: ExtensiveTestScenario.uniqueAspectGuid,
                extraString: "Extra",
            };
            txn.insertAspect(aspectProps);
            const sourceUniqueAspect = sourceDb.elements.getAspects(physicalObjectId1, "ExtensiveTestScenario:SourceUniqueAspect")[0];
            expect(sourceUniqueAspect).to.deep.subsetEqual(omit(aspectProps, ["commonBinary"]), { normalizeClassNameProps: true });
            txn.insertAspect({
                classFullName: "ExtensiveTestScenario:SourceMultiAspect",
                element: new ElementOwnsMultiAspects(physicalObjectId1),
                commonDouble: 2.2,
                commonString: "Multi",
                commonLong: physicalObjectId1,
                sourceDouble: 22.2,
                sourceString: "MultiAspect",
                sourceLong: physicalObjectId1,
                sourceGuid: Guid.createValue(),
                extraString: "Extra",
            });
            txn.insertAspect({
                classFullName: "ExtensiveTestScenario:SourceMultiAspect",
                element: new ElementOwnsMultiAspects(physicalObjectId1),
                commonDouble: 3.3,
                commonString: "Multi",
                commonLong: physicalObjectId1,
                sourceDouble: 33.3,
                sourceString: "MultiAspect",
                sourceLong: physicalObjectId1,
                sourceGuid: Guid.createValue(),
                extraString: "Extra",
            });
            txn.insertAspect({
                classFullName: "ExtensiveTestScenario:SourceUniqueAspectToExclude",
                element: new ElementOwnsUniqueAspect(physicalObjectId1),
                description: "SourceUniqueAspect1",
            });
            txn.insertAspect({
                classFullName: "ExtensiveTestScenario:SourceMultiAspectToExclude",
                element: new ElementOwnsMultiAspects(physicalObjectId1),
                description: "SourceMultiAspect1",
            });
            // Insert DrawingGraphics
            const drawingGraphicProps1 = {
                classFullName: DrawingGraphic.classFullName,
                model: drawingId,
                category: drawingCategoryId,
                code: Code.createEmpty(),
                userLabel: "DrawingGraphic1",
                geom: IModelTestUtils.createRectangle(Point2d.create(1, 1)),
                placement: { origin: Point2d.create(2, 2), angle: 0 },
            };
            const drawingGraphicId1 = txn.insertElement(drawingGraphicProps1);
            assert.isTrue(Id64.isValidId64(drawingGraphicId1));
            const drawingGraphicRepresentsId1 = DrawingGraphicRepresentsElement.insert(txn, drawingGraphicId1, physicalObjectId1);
            assert.isTrue(Id64.isValidId64(drawingGraphicRepresentsId1));
            const drawingGraphicProps2 = {
                classFullName: DrawingGraphic.classFullName,
                model: drawingId,
                category: drawingCategoryId,
                code: Code.createEmpty(),
                userLabel: "DrawingGraphic2",
                geom: IModelTestUtils.createRectangle(Point2d.create(1, 1)),
                placement: { origin: Point2d.create(3, 3), angle: 0 },
            };
            const drawingGraphicId2 = txn.insertElement(drawingGraphicProps2);
            assert.isTrue(Id64.isValidId64(drawingGraphicId2));
            const drawingGraphicRepresentsId2 = DrawingGraphicRepresentsElement.insert(txn, drawingGraphicId2, physicalObjectId1);
            assert.isTrue(Id64.isValidId64(drawingGraphicRepresentsId2));
            // Insert DisplayStyles
            const displayStyle2dId = DisplayStyle2d.insert(txn, definitionModelId, "DisplayStyle2d");
            assert.isTrue(Id64.isValidId64(displayStyle2dId));
            const displayStyle3d = DisplayStyle3d.create(sourceDb, definitionModelId, "DisplayStyle3d");
            const subCategoryOverride = SubCategoryOverride.fromJSON({ color: ColorDef.from(1, 2, 3).toJSON() });
            displayStyle3d.settings.overrideSubCategory(subCategoryId, subCategoryOverride);
            displayStyle3d.settings.addExcludedElements(physicalObjectId1);
            displayStyle3d.settings.setPlanProjectionSettings(spatialLocationModelId, PlanProjectionSettings.fromJSON({ elevation: 10.0 }));
            displayStyle3d.settings.environment = Environment.fromJSON({
                sky: {
                    image: {
                        type: SkyBoxImageType.Spherical,
                        texture: textureId,
                    },
                },
            });
            const displayStyle3dId = displayStyle3d.insert(txn);
            assert.isTrue(Id64.isValidId64(displayStyle3dId));
            // Insert ViewDefinitions
            const viewId = OrthographicViewDefinition.insert(txn, definitionModelId, "Orthographic View", modelSelectorId, spatialCategorySelectorId, displayStyle3dId, projectExtents, StandardViewIndex.Iso);
            assert.isTrue(Id64.isValidId64(viewId));
            const drawingViewRange = new Range2d(0, 0, 100, 100);
            const drawingViewId = DrawingViewDefinition.insert(txn, definitionModelId, "Drawing View", drawingId, drawingCategorySelectorId, displayStyle2dId, drawingViewRange);
            assert.isTrue(Id64.isValidId64(drawingViewId));
            // Insert instance of SourceRelToExclude to test relationship exclusion by class
            const relationship1 = sourceDb.relationships.createInstance({
                classFullName: "ExtensiveTestScenario:SourceRelToExclude",
                sourceId: spatialCategorySelectorId,
                targetId: drawingCategorySelectorId,
            });
            const relationshipId1 = txn.insertRelationship(relationship1.toJSON());
            assert.isTrue(Id64.isValidId64(relationshipId1));
            // Insert instance of RelWithProps to test relationship property remapping
            const relationship2 = sourceDb.relationships.createInstance({
                classFullName: "ExtensiveTestScenario:SourceRelWithProps",
                sourceId: spatialCategorySelectorId,
                targetId: drawingCategorySelectorId,
                sourceString: "One",
                sourceDouble: 1.1,
                sourceLong: spatialCategoryId,
                sourceGuid: Guid.createValue(),
            });
            const relationshipId2 = txn.insertRelationship(relationship2.toJSON());
            assert.isTrue(Id64.isValidId64(relationshipId2));
        }); // end withEditTxn
    }
    static updateDb(sourceDb) {
        withEditTxn(sourceDb, (txn) => {
            // Update Subject element
            const subjectId = sourceDb.elements.queryElementIdByCode(Subject.createCode(sourceDb, IModel.rootSubjectId, "Subject"));
            assert.isTrue(Id64.isValidId64(subjectId));
            const subject = sourceDb.elements.getElement(subjectId);
            subject.description = "Subject description (Updated)";
            txn.updateElement(subject.toJSON());
            // Update spatialCategory element
            const definitionModelId = sourceDb.elements.queryElementIdByCode(InformationPartitionElement.createCode(sourceDb, subjectId, "Definition"));
            assert.isTrue(Id64.isValidId64(definitionModelId));
            const spatialCategoryId = sourceDb.elements.queryElementIdByCode(SpatialCategory.createCode(sourceDb, definitionModelId, "SpatialCategory"));
            assert.isTrue(Id64.isValidId64(spatialCategoryId));
            const spatialCategory = sourceDb.elements.getElement(spatialCategoryId);
            spatialCategory.federationGuid = Guid.createValue();
            txn.updateElement(spatialCategory.toJSON());
            // Update relationship properties
            const spatialCategorySelectorId = sourceDb.elements.queryElementIdByCode(CategorySelector.createCode(sourceDb, definitionModelId, "SpatialCategories"));
            assert.isTrue(Id64.isValidId64(spatialCategorySelectorId));
            const drawingCategorySelectorId = sourceDb.elements.queryElementIdByCode(CategorySelector.createCode(sourceDb, definitionModelId, "DrawingCategories"));
            assert.isTrue(Id64.isValidId64(drawingCategorySelectorId));
            const relWithProps = sourceDb.relationships.getInstanceProps("ExtensiveTestScenario:SourceRelWithProps", { sourceId: spatialCategorySelectorId, targetId: drawingCategorySelectorId });
            assert.equal(relWithProps.sourceString, "One");
            assert.equal(relWithProps.sourceDouble, 1.1);
            relWithProps.sourceString += "-Updated";
            relWithProps.sourceDouble = 1.2;
            txn.updateRelationship(relWithProps);
            // Update ElementAspect properties
            const physicalObjectId1 = IModelTestUtils.queryByUserLabel(sourceDb, "PhysicalObject1");
            const sourceUniqueAspects = sourceDb.elements.getAspects(physicalObjectId1, "ExtensiveTestScenario:SourceUniqueAspect");
            assert.equal(sourceUniqueAspects.length, 1);
            sourceUniqueAspects[0].asAny.commonString += "-Updated";
            sourceUniqueAspects[0].asAny.sourceString += "-Updated";
            txn.updateAspect(sourceUniqueAspects[0].toJSON());
            const sourceMultiAspects = sourceDb.elements.getAspects(physicalObjectId1, "ExtensiveTestScenario:SourceMultiAspect");
            assert.equal(sourceMultiAspects.length, 2);
            sourceMultiAspects[1].asAny.commonString += "-Updated";
            sourceMultiAspects[1].asAny.sourceString += "-Updated";
            txn.updateAspect(sourceMultiAspects[1].toJSON());
            // clear NavigationProperty of PhysicalElement1
            const physicalElementId1 = IModelTestUtils.queryByUserLabel(sourceDb, "PhysicalElement1");
            let physicalElement1 = sourceDb.elements.getElement(physicalElementId1);
            physicalElement1.asAny.commonNavigation = RelatedElement.none;
            txn.updateElement(physicalElement1.toJSON());
            physicalElement1 = sourceDb.elements.getElement(physicalElementId1);
            assert.isUndefined(physicalElement1.asAny.commonNavigation);
            // delete PhysicalObject3
            const physicalObjectId3 = IModelTestUtils.queryByUserLabel(sourceDb, "PhysicalObject3");
            assert.isTrue(Id64.isValidId64(physicalObjectId3));
            txn.deleteElement(physicalObjectId3);
            assert.equal(Id64.invalid, IModelTestUtils.queryByUserLabel(sourceDb, "PhysicalObject3"));
            // Insert PhysicalObject5
            const physicalObjectProps5 = {
                classFullName: PhysicalObject.classFullName,
                model: physicalElement1.model,
                category: spatialCategoryId,
                code: Code.createEmpty(),
                userLabel: "PhysicalObject5",
                geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1)),
                placement: {
                    origin: Point3d.create(5, 5, 5),
                    angles: YawPitchRollAngles.createDegrees(0, 0, 0),
                },
            };
            const physicalObjectId5 = txn.insertElement(physicalObjectProps5);
            assert.isTrue(Id64.isValidId64(physicalObjectId5));
            // delete relationship
            const drawingGraphicId1 = IModelTestUtils.queryByUserLabel(sourceDb, "DrawingGraphic1");
            const drawingGraphicId2 = IModelTestUtils.queryByUserLabel(sourceDb, "DrawingGraphic2");
            const relationship = sourceDb.relationships.getInstance(DrawingGraphicRepresentsElement.classFullName, { sourceId: drawingGraphicId2, targetId: physicalObjectId1 });
            txn.deleteRelationship(relationship.toJSON());
            // insert relationships
            DrawingGraphicRepresentsElement.insert(txn, drawingGraphicId1, physicalObjectId5);
            DrawingGraphicRepresentsElement.insert(txn, drawingGraphicId2, physicalObjectId5);
            // update InformationRecord2
            const informationRecordCodeSpec = sourceDb.codeSpecs.getByName("InformationRecords");
            const informationModelId = sourceDb.elements.queryElementIdByCode(InformationPartitionElement.createCode(sourceDb, subjectId, "Information"));
            const informationRecodeCode2 = new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord2" });
            const informationRecordId2 = sourceDb.elements.queryElementIdByCode(informationRecodeCode2);
            assert.isTrue(Id64.isValidId64(informationRecordId2));
            const informationRecord2 = sourceDb.elements.getElement(informationRecordId2);
            informationRecord2.commonString = `${informationRecord2.commonString}-Updated`;
            informationRecord2.sourceString = `${informationRecord2.sourceString}-Updated`;
            txn.updateElement(informationRecord2);
            // delete InformationRecord3
            const informationRecodeCode3 = new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord3" });
            const informationRecordId3 = sourceDb.elements.queryElementIdByCode(informationRecodeCode3);
            assert.isTrue(Id64.isValidId64(informationRecordId3));
            txn.deleteElement(informationRecordId3);
        }); // end withEditTxn
    }
    static assertUpdatesInDb(iModelDb, assertDeletes = true) {
        // determine which schema was imported
        const testSourceSchema = iModelDb.querySchemaVersion("ExtensiveTestScenario") ? true : false;
        const testTargetSchema = iModelDb.querySchemaVersion("ExtensiveTestScenarioTarget") ? true : false;
        assert.notEqual(testSourceSchema, testTargetSchema);
        // assert Subject was updated
        const subjectId = iModelDb.elements.queryElementIdByCode(Subject.createCode(iModelDb, IModel.rootSubjectId, "Subject"));
        assert.isTrue(Id64.isValidId64(subjectId));
        const subject = iModelDb.elements.getElement(subjectId);
        assert.equal(subject.description, "Subject description (Updated)");
        // assert SpatialCategory was updated
        const definitionModelId = iModelDb.elements.queryElementIdByCode(InformationPartitionElement.createCode(iModelDb, subjectId, "Definition"));
        assert.isTrue(Id64.isValidId64(definitionModelId));
        const spatialCategoryId = iModelDb.elements.queryElementIdByCode(SpatialCategory.createCode(iModelDb, definitionModelId, "SpatialCategory"));
        assert.isTrue(Id64.isValidId64(spatialCategoryId));
        const spatialCategory = iModelDb.elements.getElement(spatialCategoryId);
        assert.exists(spatialCategory.federationGuid);
        // assert TargetRelWithProps was updated
        const spatialCategorySelectorId = iModelDb.elements.queryElementIdByCode(CategorySelector.createCode(iModelDb, definitionModelId, "SpatialCategories"));
        assert.isTrue(Id64.isValidId64(spatialCategorySelectorId));
        const drawingCategorySelectorId = iModelDb.elements.queryElementIdByCode(CategorySelector.createCode(iModelDb, definitionModelId, "DrawingCategories"));
        assert.isTrue(Id64.isValidId64(drawingCategorySelectorId));
        const relClassFullName = testTargetSchema ? "ExtensiveTestScenarioTarget:TargetRelWithProps" : "ExtensiveTestScenario:SourceRelWithProps";
        const relWithProps = iModelDb.relationships.getInstanceProps(relClassFullName, { sourceId: spatialCategorySelectorId, targetId: drawingCategorySelectorId });
        assert.equal(testTargetSchema ? relWithProps.targetString : relWithProps.sourceString, "One-Updated");
        assert.equal(testTargetSchema ? relWithProps.targetDouble : relWithProps.sourceDouble, 1.2);
        // assert ElementAspect properties
        const physicalObjectId1 = IModelTestUtils.queryByUserLabel(iModelDb, "PhysicalObject1");
        const uniqueAspectClassFullName = testTargetSchema ? "ExtensiveTestScenarioTarget:TargetUniqueAspect" : "ExtensiveTestScenario:SourceUniqueAspect";
        const uniqueAspects = iModelDb.elements.getAspects(physicalObjectId1, uniqueAspectClassFullName);
        assert.equal(uniqueAspects.length, 1);
        const uniqueAspect = uniqueAspects[0].asAny;
        expect(uniqueAspect).to.deep.subsetEqual({
            commonDouble: 1.1,
            commonString: "Unique-Updated",
            commonLong: physicalObjectId1,
        });
        if (testTargetSchema) {
            expect(uniqueAspect).to.deep.subsetEqual({
                targetDouble: 11.1,
                targetString: "UniqueAspect-Updated",
                targetLong: physicalObjectId1,
            });
        }
        else {
            expect(uniqueAspect).to.deep.subsetEqual({
                sourceDouble: 11.1,
                sourceString: "UniqueAspect-Updated",
                sourceLong: physicalObjectId1,
            });
        }
        const multiAspectClassFullName = testTargetSchema ? "ExtensiveTestScenarioTarget:TargetMultiAspect" : "ExtensiveTestScenario:SourceMultiAspect";
        const multiAspects = iModelDb.elements.getAspects(physicalObjectId1, multiAspectClassFullName);
        assert.equal(multiAspects.length, 2);
        const multiAspect0 = multiAspects[0].asAny;
        const multiAspect1 = multiAspects[1].asAny;
        assert.equal(multiAspect0.commonDouble, 2.2);
        assert.equal(multiAspect0.commonString, "Multi");
        assert.equal(multiAspect0.commonLong, physicalObjectId1);
        assert.equal(testTargetSchema ? multiAspect0.targetDouble : multiAspect0.sourceDouble, 22.2);
        assert.equal(testTargetSchema ? multiAspect0.targetString : multiAspect0.sourceString, "MultiAspect");
        assert.equal(testTargetSchema ? multiAspect0.targetLong : multiAspect0.sourceLong, physicalObjectId1);
        assert.equal(multiAspect1.commonDouble, 3.3);
        assert.equal(multiAspect1.commonString, "Multi-Updated");
        assert.equal(multiAspect1.commonLong, physicalObjectId1);
        assert.equal(testTargetSchema ? multiAspect1.targetDouble : multiAspect1.sourceDouble, 33.3);
        assert.equal(testTargetSchema ? multiAspect1.targetString : multiAspect1.sourceString, "MultiAspect-Updated");
        assert.equal(testTargetSchema ? multiAspect1.targetLong : multiAspect1.sourceLong, physicalObjectId1);
        // assert NavigationProperty of PhysicalElement1 was cleared
        const physicalElementId = IModelTestUtils.queryByUserLabel(iModelDb, "PhysicalElement1");
        const physicalElement = iModelDb.elements.getElement(physicalElementId);
        assert.isUndefined(physicalElement.asAny.commonNavigation);
        // assert PhysicalObject5 was inserted
        const physicalObjectId5 = IModelTestUtils.queryByUserLabel(iModelDb, "PhysicalObject5");
        assert.isTrue(Id64.isValidId64(physicalObjectId5));
        // assert relationships were inserted
        const drawingGraphicId1 = IModelTestUtils.queryByUserLabel(iModelDb, "DrawingGraphic1");
        const drawingGraphicId2 = IModelTestUtils.queryByUserLabel(iModelDb, "DrawingGraphic2");
        iModelDb.relationships.getInstance(DrawingGraphicRepresentsElement.classFullName, { sourceId: drawingGraphicId1, targetId: physicalObjectId5 });
        iModelDb.relationships.getInstance(DrawingGraphicRepresentsElement.classFullName, { sourceId: drawingGraphicId2, targetId: physicalObjectId5 });
        // assert InformationRecord2 was updated
        const informationRecordCodeSpec = iModelDb.codeSpecs.getByName("InformationRecords");
        const informationModelId = iModelDb.elements.queryElementIdByCode(InformationPartitionElement.createCode(iModelDb, subjectId, "Information"));
        const informationRecordId2 = iModelDb.elements.queryElementIdByCode(new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord2" }));
        assert.isTrue(Id64.isValidId64(informationRecordId2));
        const informationRecord2 = iModelDb.elements.getElement(informationRecordId2);
        assert.equal(informationRecord2.commonString, "Common2-Updated");
        assert.equal(testTargetSchema ? informationRecord2.targetString : informationRecord2.sourceString, "Two-Updated");
        // assert InformationRecord3 was deleted
        assert.isDefined(iModelDb.elements.queryElementIdByCode(new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord1" })));
        assert.isDefined(iModelDb.elements.queryElementIdByCode(new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord2" })));
        // detect deletes if possible - cannot detect during processAll when isReverseSynchronization is true
        if (assertDeletes) {
            assert.equal(Id64.invalid, IModelTestUtils.queryByUserLabel(iModelDb, "PhysicalObject3"));
            assert.throws(() => iModelDb.relationships.getInstanceProps(DrawingGraphicRepresentsElement.classFullName, { sourceId: drawingGraphicId2, targetId: physicalObjectId1 }));
            assert.isUndefined(iModelDb.elements.queryElementIdByCode(new Code({ spec: informationRecordCodeSpec.id, scope: informationModelId, value: "InformationRecord3" })));
        }
    }
}
//# sourceMappingURL=IModelTestUtils.js.map