"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module IModelConnection
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SnapshotConnection = exports.BlankConnection = exports.IModelConnection = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const core_geometry_1 = require("@itwin/core-geometry");
const FrontendLoggerCategory_1 = require("./common/FrontendLoggerCategory");
const GeoServices_1 = require("./GeoServices");
const IModelApp_1 = require("./IModelApp");
const IModelRoutingContext_1 = require("./IModelRoutingContext");
const ModelState_1 = require("./ModelState");
const SelectionSet_1 = require("./SelectionSet");
const SubCategoriesCache_1 = require("./SubCategoriesCache");
const internal_1 = require("./tile/internal");
const Tiles_1 = require("./Tiles");
const ViewState_1 = require("./ViewState");
const Symbols_1 = require("./common/internal/Symbols");
const IpcApp_1 = require("./IpcApp");
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
const ecschema_rpcinterface_common_1 = require("@itwin/ecschema-rpcinterface-common");
const loggerCategory = FrontendLoggerCategory_1.FrontendLoggerCategory.IModelConnection;
/** A connection to a [IModelDb]($backend) hosted on the backend.
 * @public
 * @extensions
 */
class IModelConnection extends core_common_1.IModel {
    /** The [[ModelState]]s in this IModelConnection. */
    models;
    /** The [[ElementState]]s in this IModelConnection. */
    elements;
    /** The [[CodeSpec]]s in this IModelConnection. */
    codeSpecs;
    /** The [[ViewState]]s in this IModelConnection. */
    views;
    /** The set of currently hilited elements for this IModelConnection. */
    hilited;
    /** The set of currently selected elements for this IModelConnection. */
    selectionSet;
    /** The set of Tiles for this IModelConnection. */
    tiles;
    /** The set of [Category]($backend)'s in this IModelConnection. */
    categories;
    /** A cache of information about SubCategories chiefly used for rendering.
     * @internal
     */
    get subcategories() { return this.categories.cache; }
    /** Generator for unique Ids of transient graphics for this IModelConnection. */
    transientIds = new core_bentley_1.TransientIdSequence();
    /** The Geographic location services available for this iModelConnection. */
    geoServices;
    /** @internal Whether GCS has been disabled for this iModelConnection. */
    _gcsDisabled = false;
    /** @internal Return true if a GCS is not defined for this iModelConnection; also returns true if GCS is defined but disabled. */
    get noGcsDefined() { return this._gcsDisabled || undefined === this.geographicCoordinateSystem; }
    /** @internal */
    disableGCS(disable) { this._gcsDisabled = disable; }
    /** The maximum time (in milliseconds) to wait before timing out the request to open a connection to a new iModel */
    static connectionTimeout = 10 * 60 * 1000;
    /** The RPC routing for this connection. */
    routingContext = IModelRoutingContext_1.IModelRoutingContext.default;
    /** Type guard for instanceof [[BriefcaseConnection]] */
    isBriefcaseConnection() { return false; }
    /** Type guard for instanceof [[CheckpointConnection]]
     * @beta
    */
    isCheckpointConnection() { return false; }
    /** Type guard for instanceof [[SnapshotConnection]] */
    isSnapshotConnection() { return false; }
    /** Type guard for instanceof [[BlankConnection]] */
    isBlankConnection() { return false; }
    /** Returns `true` if this is a briefcase copy of an iModel that is synchronized with iModelHub. */
    get isBriefcase() { return this.isBriefcaseConnection(); }
    /** Returns `true` if this is a *snapshot* iModel.
     * @see [[SnapshotConnection.openSnapshot]]
     */
    get isSnapshot() { return this.isSnapshotConnection(); }
    /** True if this is a [Blank Connection]($docs/learning/frontend/BlankConnection). */
    get isBlank() { return this.isBlankConnection(); }
    /** Check the [[openMode]] of this IModelConnection to see if it was opened read-only. */
    get isReadonly() { return this.openMode === core_bentley_1.OpenMode.Readonly; }
    /** Check if the IModelConnection is open (i.e. it has a *connection* to a backend server).
     * Returns false for [[BlankConnection]] instances and after [[IModelConnection.close]] has been called.
     * @note no RPC operations are valid on this IModelConnection if this method returns false.
     */
    get isOpen() { return !this.isClosed; }
    /** Event raised immediately before *any* IModelConnection is [[close]]d.
     * @note This static event is raised when *any* IModelConnection is closed, and the specific IModelConnection is passed as its argument. To
     * monitor closing a specific IModelConnection, listen for the `onClose` instance event instead.
     * @note Be careful not to perform any asynchronous operations on the IModelConnection because it will close before they are processed.
     */
    static onClose = new core_bentley_1.BeEvent();
    /** Event called immediately after *any* IModelConnection is opened. */
    static onOpen = new core_bentley_1.BeEvent();
    /** Event raised immediately before this IModelConnection is [[close]]d.
     * @note This event is raised only for this specific IModelConnection. To monitor *all* IModelConnections, listen for the static `onClose` event instead.
     * @note Be careful not to perform any asynchronous operations on the IModelConnection because it will close before they are processed.
     */
    onClose = new core_bentley_1.BeEvent();
    /** The font map for this IModelConnection. Only valid after calling #loadFontMap and waiting for the returned promise to be fulfilled.
     * @deprecated in 5.0.0 - will not be removed until after 2026-06-13. If you need font Ids on the front-end for some reason, write an Ipc method that queries [IModelDb.fonts]($backend).
     */
    fontMap; // eslint-disable-line @typescript-eslint/no-deprecated
    _schemaContext;
    _schemasPromise;
    /** Load the FontMap for this IModelConnection.
     * @returns Returns a Promise<FontMap> that is fulfilled when the FontMap member of this IModelConnection is valid.
     * @deprecated in 5.0.0 - will not be removed until after 2026-06-13. If you need font Ids on the front-end for some reason, write an Ipc method that queries [IModelDb.fonts]($backend).
     */
    async loadFontMap() {
        if (undefined === this.fontMap) { // eslint-disable-line @typescript-eslint/no-deprecated
            this.fontMap = new core_common_1.FontMap(); // eslint-disable-line @typescript-eslint/no-deprecated
            if (this.isOpen) {
                const fontProps = await core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).readFontJson(this.getRpcProps());
                this.fontMap.addFonts(fontProps.fonts); // eslint-disable-line @typescript-eslint/no-deprecated
            }
        }
        return this.fontMap; // eslint-disable-line @typescript-eslint/no-deprecated
    }
    /** Find the first registered base class of the given EntityState className. This class will "handle" the State for the supplied className.
     * @param className The full name of the class of interest.
     * @param defaultClass If no base class of the className is registered, return this value.
     * @note this method is async since it may have to query the server to get the class hierarchy.
     */
    async findClassFor(className, defaultClass) {
        let ctor = IModelApp_1.IModelApp.lookupEntityClass(className);
        if (undefined !== ctor)
            return ctor;
        // it's not registered, we need to query its class hierarchy.
        // wait until we get the full list of base classes from backend
        if (this.isOpen) {
            const baseClasses = await core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getClassHierarchy(this.getRpcProps(), className);
            // Make sure some other async code didn't register this class while we were await-ing above
            ctor = IModelApp_1.IModelApp.lookupEntityClass(className);
            if (undefined !== ctor)
                return ctor;
            // walk through the list until we find a registered base class
            baseClasses.some((baseClass) => {
                const test = IModelApp_1.IModelApp.lookupEntityClass(baseClass);
                if (test === undefined)
                    return false; // nope, not registered
                ctor = test; // found it, save it
                IModelApp_1.IModelApp.registerEntityState(className, ctor); // and register the fact that our starting class is handled by this subclass.
                return true; // stop
            });
        }
        return ctor ?? defaultClass; // either the baseClass handler or defaultClass if we didn't find a registered baseClass
    }
    /** @internal */
    constructor(iModelProps) {
        super(iModelProps);
        super.initialize(iModelProps.name ?? "<undefined>", iModelProps);
        this.models = new IModelConnection.Models(this);
        this.elements = new IModelConnection.Elements(this);
        this.codeSpecs = new IModelConnection.CodeSpecs(this);
        this.views = new IModelConnection.Views(this);
        this.categories = new IModelConnection.Categories(this);
        this.selectionSet = new SelectionSet_1.SelectionSet(this);
        this.hilited = new SelectionSet_1.HiliteSet(this);
        this.tiles = new Tiles_1.Tiles(this);
        this.geoServices = GeoServices_1.GeoServices.createForIModel(this);
        this.hilited.onModelSubCategoryModeChanged.addListener(() => {
            IModelApp_1.IModelApp.viewManager.onSelectionSetChanged(this);
        });
    }
    /** Called prior to connection closing. Raises close events and calls tiles.dispose.
     * @internal
     */
    beforeClose() {
        this.onClose.raiseEvent(this); // event for this connection
        IModelConnection.onClose.raiseEvent(this); // event for all connections
        this.tiles[Symbol.dispose]();
        this.subcategories.onIModelConnectionClose();
    }
    /** Allow to execute query and read results along with meta data. The result are streamed.
     *
     * See also:
     * - [ECSQL Overview]($docs/learning/frontend/ExecutingECSQL)
     * - [Code Examples]($docs/learning/frontend/ECSQLCodeExamples)
     * - [ECSQL Row Format]($docs/learning/ECSQLRowFormat)
     *
     * @param params The values to bind to the parameters (if the ECSQL has any).
     * @param config Allow to specify certain flags which control how query is executed.
     * @returns Returns an [ECSqlReader]($common) which helps iterate over the result set and also give access to metadata.
     * @public
     * */
    createQueryReader(ecsql, params, config) {
        const executor = {
            execute: async (request) => {
                return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryRows(this.getRpcProps(), request);
            },
        };
        return new core_common_1.ECSqlReader(executor, ecsql, params, config);
    }
    /**
     * queries the BisCore.SubCategory table for the entries that are children of the passed categoryIds
     * @param compressedCategoryIds compressed category Ids
     * @returns array of SubCategoryResultRow
     * @internal
     */
    async querySubCategories(compressedCategoryIds) {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).querySubCategories(this.getRpcProps(), compressedCategoryIds);
    }
    /**
     * queries the BisCore.SubCategory table for entries that are children of used spatial categories and 3D elements.
     * @returns array of SubCategoryResultRow
     * @internal
     */
    async queryAllUsedSpatialSubCategories() {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryAllUsedSpatialSubCategories(this.getRpcProps());
    }
    /** Query for a set of element ids that satisfy the supplied query params
     * @param params The query parameters. The `limit` and `offset` members should be used to page results.
     * @throws [IModelError]($common) If the generated statement is invalid or would return too many rows.
     */
    async queryEntityIds(params) {
        return new Set(this.isOpen ? await core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryEntityIds(this.getRpcProps(), params) : undefined);
    }
    _snapRpc = new core_bentley_1.OneAtATimeAction(async (props) => core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).requestSnap(this.getRpcProps(), IModelApp_1.IModelApp.sessionId, props));
    /** Request a snap from the backend.
     * @note callers must gracefully handle Promise rejected with AbandonedError
     * @internal
     */
    async [Symbols_1._requestSnap](props) {
        return this.isOpen ? this._snapRpc.request(props) : { status: 2 };
    }
    /** @internal
     * @deprecated in 4.8 - will not be removed until after 2026-06-13. Use AccuSnap.doSnapRequest.
     */
    async requestSnap(props) {
        return this[Symbols_1._requestSnap](props);
    }
    _toolTipRpc = new core_bentley_1.OneAtATimeAction(async (id) => core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getToolTipMessage(this.getRpcProps(), id));
    /** Request a tooltip from the backend.
     * @note If another call to this method occurs before preceding call(s) return, all preceding calls will be abandoned - only the most recent will resolve. Therefore callers must gracefully handle Promise rejected with AbandonedError.
     */
    async getToolTipMessage(id) {
        return this.isOpen ? this._toolTipRpc.request(id) : [];
    }
    /** Request element clip containment status from the backend. */
    async getGeometryContainment(requestProps) { return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getGeometryContainment(this.getRpcProps(), requestProps); }
    /** Obtain a summary of the geometry belonging to one or more [GeometricElement]($backend)s suitable for debugging and diagnostics.
     * @param requestProps Specifies the elements to query and options for how to format the output.
     * @returns A string containing the summary, typically consisting of multiple lines.
     * @note Trying to parse the output to programmatically inspect an element's geometry is not recommended.
     * @see [GeometryStreamIterator]($common) to more directly inspect a geometry stream.
     */
    async getGeometrySummary(requestProps) {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getGeometrySummary(this.getRpcProps(), requestProps);
    }
    /** Request a named texture image from the backend.
     * @param textureLoadProps The texture load properties which must contain a name property (a valid 64-bit integer identifier). It optionally can contain the maximum texture size supported by the client.
     * @see [[Id64]]
     * @public
     */
    async queryTextureData(textureLoadProps) {
        if (this.isOpen) {
            const rpcClient = core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token);
            const img = rpcClient.queryTextureData(this.getRpcProps(), textureLoadProps);
            return img;
        }
        return undefined;
    }
    /** Request element mass properties from the backend. */
    async getMassProperties(requestProps) {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getMassProperties(this.getRpcProps(), requestProps);
    }
    /** Request mass properties for multiple elements from the backend.
     * @deprecated in 4.11 - will not be removed until after 2026-06-13. Use [[IModelConnection.getMassProperties]].
     */
    async getMassPropertiesPerCandidate(requestProps) {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getMassPropertiesPerCandidate(this.getRpcProps(), requestProps);
    }
    /** Produce encoded [Polyface]($core-geometry)s from the geometry stream of a [GeometricElement]($backend).
     * A polyface is produced for each geometric entry in the element's geometry stream, excluding geometry like open curves that can't be converted into polyfaces.
     * The polyfaces can be decoded using [readElementMeshes]($common).
     * Symbology, UV parameters, and normal vectors are not included in the result.
     * @param requestProps A description of how to produce the polyfaces and from which element to obtain them.
     * @returns an encoded list of polyfaces that can be decoded by [readElementMeshes]($common).
     * @throws Error if [ElementMeshRequestProps.source]($common) does not refer to a [GeometricElement]($backend).
     * @note This function is intended to support limited analysis of an element's geometry as a mesh. It is not intended for producing graphics.
     * @see [[TileAdmin.requestElementGraphics]] to obtain meshes appropriate for display.
     * @beta
     */
    async generateElementMeshes(requestProps) {
        return core_common_1.IModelReadRpcInterface.getClientForRouting(this.routingContext.token).generateElementMeshes(this.getRpcProps(), requestProps);
    }
    /** Convert a point in this iModel's Spatial coordinates to a [[Cartographic]] using the Geographic location services for this IModelConnection.
     * @param spatial A point in the iModel's spatial coordinates
     * @param result If defined, use this for output
     * @returns A Cartographic location (Horizontal datum depends on iModel's GCS)
     * @throws IModelError if [[isGeoLocated]] is false or point could not be converted.
     * @see [[cartographicFromSpatial]] if you have more than one point to convert, or you don't know whether the iModel has a GCS.
     */
    async spatialToCartographicFromGcs(spatial, result) {
        if (!this.isGeoLocated && this.noGcsDefined)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        const geoConverter = (0, core_bentley_1.expectDefined)(this.geoServices.getConverter());
        const coordResponse = await geoConverter.getGeoCoordinatesFromIModelCoordinates([spatial]);
        if (1 !== coordResponse.geoCoords.length || core_common_1.GeoCoordStatus.NoGCSDefined === coordResponse.geoCoords[0].s)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (core_common_1.GeoCoordStatus.Success !== coordResponse.geoCoords[0].s) {
            const geoServiceStatus = (0, core_common_1.mapToGeoServiceStatus)(coordResponse.geoCoords[0].s);
            throw new core_common_1.IModelError(geoServiceStatus, "Error converting spatial to cartographic");
        }
        const longLatHeight = core_geometry_1.Point3d.fromJSON(coordResponse.geoCoords[0].p); // x is longitude in degrees, y is latitude in degrees, z is height in meters...
        return core_common_1.Cartographic.fromDegrees({ longitude: longLatHeight.x, latitude: longLatHeight.y, height: longLatHeight.z }, result);
    }
    /** Convert a point in this iModel's Spatial coordinates to a [[Cartographic]] using the Geographic location services for this IModelConnection or [[IModel.ecefLocation]].
     * @param spatial A point in the iModel's spatial coordinates
     * @param result If defined, use this for output
     * @returns A Cartographic location (Horizontal datum depends on iModel's GCS)
     * @throws IModelError if [[isGeoLocated]] is false or point could not be converted.
     * @see [[cartographicFromSpatial]] to convert multiple points at once.
     * @see [[spatialToCartographicFromEcef]] to synchronously convert points using the iModel's ECEF transform.
     */
    async spatialToCartographic(spatial, result) {
        return (this.noGcsDefined ? this.spatialToCartographicFromEcef(spatial, result) : this.spatialToCartographicFromGcs(spatial, result));
    }
    /** Convert points in this iModel's spatial coordinate system to [Cartographic]($common) coordinates using either a [[GeoConverter]] or the iModel's [EcefLocation]($common).
     * @param spatial Coordinates to be converted from the iModel's spatial coordinate system
     * @returns The `spatial` coordinates converted to cartographic coordinates, of the same length and order as the `spatial`.
     * @throws IModelError if [[isGeoLocated]] is false or any point could not be converted.
     * @see [[spatialFromCartographic]] to perform the inverse conversion.
     * @see [[spatialToCartographicFromEcef]] to synchronously convert points using the iModel's ECEF transform.
     */
    async cartographicFromSpatial(spatial) {
        return this.cartographicFromSpatialWithGcs(spatial);
    }
    /** Convert points in this iModel's spatial coordinate system to [Cartographic]($common) coordinates using either a [[GeoConverter]] or the iModel's [EcefLocation]($common).
     * @param spatial Coordinates to be converted from the iModel's spatial coordinate system
     * @returns The `spatial` coordinates converted to cartographic coordinates (WGS84 horizontal datum), of the same length and order as the `spatial`.
     * @throws IModelError if [[isGeoLocated]] is false or any point could not be converted.
     * @see [[cartographicFromSpatial]] to perform conversion using iModel's GCS horizontal datum
     * @beta
     */
    async wgs84CartographicFromSpatial(spatial) {
        return this.cartographicFromSpatialWithGcs(spatial, "WGS84");
    }
    /** @internal */
    async cartographicFromSpatialWithGcs(spatial, datumOrGCRS) {
        if (this.noGcsDefined)
            return spatial.map((p) => this.spatialToCartographicFromEcef(p));
        if (!this.isGeoLocated)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (!this.isOpen)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not open");
        if (spatial.length === 0)
            return [];
        const geoConverter = this.geoServices.getConverter(datumOrGCRS);
        (0, core_bentley_1.assert)(undefined !== geoConverter);
        const coordResponse = await geoConverter.getGeoCoordinatesFromIModelCoordinates(spatial);
        if (coordResponse.geoCoords.length !== spatial.length)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        return coordResponse.geoCoords.map((coord) => {
            switch (coord.s) {
                case core_common_1.GeoCoordStatus.NoGCSDefined:
                    throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
                case core_common_1.GeoCoordStatus.Success:
                    const llh = core_geometry_1.Point3d.fromJSON(coord.p);
                    return core_common_1.Cartographic.fromDegrees({ longitude: llh.x, latitude: llh.y, height: llh.z });
                default:
                    throw new core_common_1.IModelError((0, core_common_1.mapToGeoServiceStatus)(coord.s), "Error converting spatial to cartographic");
            }
        });
    }
    /** Convert a [Cartographic]($common) to a point in this iModel's spatial coordinate system using a [[GeoConverter]].
     * @param cartographic A cartographic location
     * @param result If defined, use this for output
     * @returns A point in this iModel's spatial coordinates
     * @throws IModelError if [[isGeoLocated]] is false or cartographic location could not be converted.
     * @see [[spatialFromCartographic]] to convert multiple points at once, or you don't know whether the iModel has a GCS.
     */
    async cartographicToSpatialFromGcs(cartographic, result) {
        if (!this.isGeoLocated && this.noGcsDefined)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        const geoConverter = (0, core_bentley_1.expectDefined)(this.geoServices.getConverter());
        const geoCoord = core_geometry_1.Point3d.create(cartographic.longitudeDegrees, cartographic.latitudeDegrees, cartographic.height); // x is longitude in degrees, y is latitude in degrees, z is height in meters...
        const coordResponse = await geoConverter.getIModelCoordinatesFromGeoCoordinates([geoCoord]);
        if (1 !== coordResponse.iModelCoords.length || core_common_1.GeoCoordStatus.NoGCSDefined === coordResponse.iModelCoords[0].s)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (core_common_1.GeoCoordStatus.Success !== coordResponse.iModelCoords[0].s) {
            const geoServiceStatus = (0, core_common_1.mapToGeoServiceStatus)(coordResponse.iModelCoords[0].s);
            throw new core_common_1.IModelError(geoServiceStatus, "Error converting cartographic to spatial");
        }
        result = result ? result : core_geometry_1.Point3d.createZero();
        result.setFromJSON(coordResponse.iModelCoords[0].p);
        return result;
    }
    /** Convert a [Cartographic]($common) to a point in this iModel's Spatial coordinates using a [[GeoConverter]] or[[IModel.ecefLocation]($common).
     * @param cartographic A cartographic location
     * @param result If defined, use this for output
     * @returns A point in this iModel's spatial coordinates
     * @throws IModelError if [[isGeoLocated]] is false or cartographic location could not be converted.
     * @see [[spatialFromCartographic]] to convert multiple points at once.
     * @see [[cartographicToSpatialFromEcef]] to synchronously convert points using the iModel's ECEF transform.
     */
    async cartographicToSpatial(cartographic, result) {
        return (this.noGcsDefined ? this.cartographicToSpatialFromEcef(cartographic, result) : this.cartographicToSpatialFromGcs(cartographic, result));
    }
    /** Convert [Cartographic]($common) coordinates into points in this iModel's spatial coordinate system using a [[GeoConverter]] or the iModel's [EcefLocation]($common).
     * @param cartographic Coordinates to be converted to the iModel's spatial coordinate system.
     * @returns The `cartographic` coordinates converted to spatial coordinates, of the same length and order as `cartographic`.
     * @throws IModelError if [[isGeoLocated]] is false or any point could not be converted.
     * @see [[cartographicFromSpatial]] to perform the inverse conversion.
     */
    async spatialFromCartographic(cartographic) {
        if (this.noGcsDefined)
            return cartographic.map((p) => this.cartographicToSpatialFromEcef(p));
        const geoCoords = cartographic.map((p) => core_geometry_1.Point3d.create(p.longitudeDegrees, p.latitudeDegrees, p.height));
        return this.toSpatialFromGcs(geoCoords);
    }
    /** Convert geographic coordinates into points in this iModel's spatial coordinate system using a [[GeoConverter]] or the iModel's [EcefLocation]($common).
     * @param geoCoords Coordinates to be converted are in the coordinate system described by the `datumOrGCRS` parameter.  Defaults iModel's spatial coordinate system otherwise.
     * @param datumOrGCRS Datum name or Geographic CRS object definition to use for the conversion.
     * @returns The `geographics` coordinates converted to spatial coordinates, of the same length and order as `geographics`.
     * @throws IModelError if [[isGeoLocated]] is false or any point could not be converted.
     * @beta
     */
    async toSpatialFromGcs(geoCoords, datumOrGCRS) {
        if (!this.isGeoLocated)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (!this.isOpen)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not open");
        if (geoCoords.length === 0)
            return [];
        const geoConverter = this.geoServices.getConverter(datumOrGCRS);
        (0, core_bentley_1.assert)(undefined !== geoConverter);
        const coordResponse = await geoConverter.getIModelCoordinatesFromGeoCoordinates(geoCoords);
        if (coordResponse.iModelCoords.length !== geoCoords.length)
            throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        return coordResponse.iModelCoords.map((coord) => {
            switch (coord.s) {
                case core_common_1.GeoCoordStatus.NoGCSDefined:
                    throw new core_common_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
                case core_common_1.GeoCoordStatus.Success:
                    return core_geometry_1.Point3d.fromJSON(coord.p);
                default:
                    throw new core_common_1.IModelError((0, core_common_1.mapToGeoServiceStatus)(coord.s), "Error converting cartographic to spatial");
            }
        });
    }
    /** @internal */
    getMapEcefToDb(bimElevationBias) {
        if (!this.ecefLocation)
            return core_geometry_1.Transform.createIdentity();
        const mapEcefToDb = this.ecefLocation.getTransform().inverse();
        if (!mapEcefToDb) {
            (0, core_bentley_1.assert)(false);
            return core_geometry_1.Transform.createIdentity();
        }
        mapEcefToDb.origin.z += bimElevationBias;
        return mapEcefToDb;
    }
    _geodeticToSeaLevel;
    _projectCenterAltitude;
    /** Event called immediately after map elevation request is completed. This occurs only in the case where background map terrain is displayed
     * with either geoid or ground offset. These require a query to BingElevation and therefore synching the view may be required
     * when the request is completed.
     * @internal
     */
    onMapElevationLoaded = new core_bentley_1.BeEvent();
    /** The offset between sea level and the geodetic ellipsoid. This will return undefined only if the request for the offset to Bing Elevation
     * is required, and in this case the [[onMapElevationLoaded]] event is raised when the request is completed.
     * @internal
     */
    get geodeticToSeaLevel() {
        if (undefined === this._geodeticToSeaLevel) {
            const elevationProvider = new internal_1.BingElevationProvider();
            this._geodeticToSeaLevel = elevationProvider.getGeodeticToSeaLevelOffset(this.projectExtents.center, this);
            this._geodeticToSeaLevel.then((geodeticToSeaLevel) => {
                this._geodeticToSeaLevel = geodeticToSeaLevel;
                this.onMapElevationLoaded.raiseEvent(this);
            }).catch((_error) => this._geodeticToSeaLevel = 0.0);
        }
        return ("number" === typeof this._geodeticToSeaLevel) ? this._geodeticToSeaLevel : undefined;
    }
    /** The altitude (geodetic) at the project center. This will return undefined only if the request for the offset to Bing Elevation
     * is required, and in this case the [[onMapElevationLoaded]] event is raised when the request is completed.
     * @internal
     */
    get projectCenterAltitude() {
        if (undefined === this._projectCenterAltitude) {
            const elevationProvider = new internal_1.BingElevationProvider();
            this._projectCenterAltitude = elevationProvider.getHeightValue(this.projectExtents.center, this);
            this._projectCenterAltitude.then((projectCenterAltitude) => {
                this._projectCenterAltitude = projectCenterAltitude;
                this.onMapElevationLoaded.raiseEvent(this);
            }).catch((_error) => this._projectCenterAltitude = 0.0);
        }
        return ("number" === typeof this._projectCenterAltitude) ? this._projectCenterAltitude : undefined;
    }
    /**
     * Gets the context that allows accessing the metadata (see `@itwin/ecschema-metadata` package) of this iModel.
     * The context is created lazily when this property is accessed for the first time, with an `ECSchemaRpcLocater` registered as a fallback locater, enabling users to register their own locater that'd take more priority.
     * This means to correctly access schema context, client-side applications must register `ECSchemaRpcInterface` following instructions for [RPC configuration]($docs/learning/rpcinterface/#client-side-configuration).
     * Server-side applications would also [configure RPC]($docs/learning/rpcinterface/#server-side-configuration) as needed.
     *
     * For runtime read-only access - class/property iteration, IS-A checks, navigating relationships, KOQ lookups -
     * prefer [[getSchemaView]]. `schemaContext` remains the right choice when you need custom-attribute deserialization
     * or the full ecschema-metadata object graph.
     *
     * @note While a `BlankConnection` returns a valid `schemaContext`, it has an invalid locater registered by default, and will throw an error when trying to call it's methods.
     * @beta
     */
    get schemaContext() {
        if (this._schemaContext === undefined) {
            const context = new ecschema_metadata_1.SchemaContext();
            // While incremental schema loading is the prefered way to load schemas on the frontend, there might be cases where clients
            // would want to use their own locaters, so if incremenal schema loading is disabled, the locater is not registered.
            if (IModelApp_1.IModelApp.isIncrementalSchemaLoadingEnabled) {
                context.addLocater(new ecschema_rpcinterface_common_1.RpcIncrementalSchemaLocater(this._getRpcProps()));
            }
            context.addFallbackLocater(new ecschema_rpcinterface_common_1.ECSchemaRpcLocater(this._getRpcProps()));
            this._schemaContext = context;
        }
        return this._schemaContext;
    }
    /** Get the schema view for this iModel. The view is built lazily on
     * first call by fetching compact binary schema data via `PRAGMA schema_view` through
     * the existing queryRows RPC (ConcurrentQuery). Subsequent calls return the cached view.
     * Multiple concurrent callers share a single in-flight fetch.
     *
     * The returned `SchemaView` is a lightweight, read-only, synchronous API for
     * navigating schema metadata - classes, properties, relationships, enumerations, etc.
     * It is the recommended default for runtime read-only metadata access and is significantly
     * faster and lower-memory than [[schemaContext]]. Use [[schemaContext]] for custom-attribute
     * deserialization or anywhere you need the full ecschema-metadata object graph.
     * @beta
     */
    async getSchemaView() {
        if (this._schemasPromise) {
            const ctx = await this._schemasPromise;
            if (!ctx.isOutdated)
                return ctx;
        }
        // Capture the in-flight promise locally so the rejection handler only clears
        // `_schemasPromise` if it still points at this build. A concurrent invalidation +
        // re-fetch could otherwise replace the field before our fetch fails, and a naive
        // `_schemasPromise = undefined` would clobber that newer reference.
        const inflight = this._fetchSchemas();
        this._schemasPromise = inflight;
        inflight.catch(() => {
            if (this._schemasPromise === inflight)
                this._schemasPromise = undefined;
        });
        return inflight;
    }
    /**
     * Checks whether the iModel's schemas have changed since the current cached [[SchemaView]] was
     * built, and discards the cache only if they have.
     *
     * Frontend code paths that may affect schemas - such as [[BriefcaseConnection.pullChanges]], or
     * application-specific IPC calls that import or upgrade schemas - cannot reliably determine
     * whether the operation actually modified any schemas. The IPC response for a pull, for example,
     * returns only the new changeset id, not the list of applied changesets with their types.
     * Unconditionally discarding the cached [[SchemaView]] after every such operation would cause
     * unnecessary reloads in the common case where schemas are unchanged. This method avoids that
     * cost by fetching a lightweight schema checksum via `PRAGMA checksum(ecdb_schema)` and
     * comparing it against the token stored in the cached view. Only when the token differs is the
     * cache discarded.
     *
     * Subclasses that expose operations which may modify schemas should await this method after the
     * operation completes to ensure [[getSchemaView]] returns a fresh view if needed.
     * @internal
     */
    async invalidateSchemaViewIfChanged() {
        if (!this._schemasPromise)
            return;
        const existingPromise = this._schemasPromise;
        let existing;
        try {
            existing = await existingPromise;
        }
        catch {
            // The cached promise itself failed; drop it so the next getSchemaView() retries.
            if (this._schemasPromise === existingPromise)
                this._schemasPromise = undefined;
            return;
        }
        if (!existing.schemaToken)
            return;
        try {
            const reader = this.createQueryReader("PRAGMA checksum(ecdb_schema)");
            const result = await reader.next();
            if (result.done)
                throw new Error("PRAGMA checksum(ecdb_schema) returned no rows");
            const liveToken = result.value.sha3_256;
            if (liveToken !== existing.schemaToken) {
                if (this._schemasPromise === existingPromise)
                    this._schemasPromise = undefined;
                existing.markOutdated();
            }
        }
        catch {
            // The checksum check is called right after operations that may have changed schemas
            // (e.g., pullChanges). If we cannot verify the cached view is still current, drop it
            // rather than risk returning stale metadata indefinitely. The next getSchemaView() call
            // will reload. We also mark the existing view outdated so any retained references can
            // observe the invalidation.
            if (this._schemasPromise === existingPromise) {
                this._schemasPromise = undefined;
                existing.markOutdated();
            }
        }
    }
    async _fetchSchemas() {
        // PRAGMA returns exactly one row with format, formatVersion, data (binary), schemaToken.
        // Important: only call reader.next() once - do NOT use `for await` on PRAGMA results.
        // ConcurrentQuery wraps regular ECSQL in LIMIT/OFFSET for pagination but skips this for
        // PRAGMAs. If the serialized result exceeds the memory threshold, the response is marked
        // "Partial", and a `for await` loop would re-issue the same PRAGMA forever since PRAGMAs
        // don't support OFFSET-based pagination.
        const reader = this.createQueryReader(`PRAGMA schema_view(${ecschema_metadata_1.schemaViewFormatVersion})`);
        const result = await reader.next();
        if (result.done)
            throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "PRAGMA schema_view returned no rows");
        const data = result.value.data;
        const token = result.value.schemaToken;
        if (data === undefined || data === null)
            throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "PRAGMA schema_view returned null data column");
        return ecschema_metadata_1.SchemaView.fromBinary(data, token ?? "");
    }
}
exports.IModelConnection = IModelConnection;
/** A connection that exists without an iModel. Useful for connecting to Reality Data services.
 * @note This class exists because our display system requires an IModelConnection type even if only reality data is drawn.
 * @public
 */
class BlankConnection extends IModelConnection {
    isBlankConnection() { return true; }
    /** The Guid that identifies the iTwin for this BlankConnection.
     * @note This can also be set via the [[create]] method using [[BlankConnectionProps.iTwinId]].
     */
    get iTwinId() { return this._iTwinId; }
    set iTwinId(iTwinId) { this._iTwinId = iTwinId; }
    /** A BlankConnection does not have an associated iModel, so its `iModelId` is alway `undefined`. */
    get iModelId() { return undefined; } // GuidString | undefined for the superclass, but always undefined for BlankConnection
    /** A BlankConnection is always considered closed because it does not have a specific backend nor associated iModel.
     * @returns `true` is always returned since RPC operations and iModel queries are not valid.
     * @note Even though true is always returned, it is still valid to call [[close]] to dispose frontend resources.
     */
    get isClosed() { return true; }
    /** Create a new [Blank IModelConnection]($docs/learning/frontend/BlankConnection).
     * @param props The properties to use for the new BlankConnection.
     */
    static create(props) {
        const connection = new BlankConnection({
            name: props.name,
            rootSubject: { name: props.name },
            projectExtents: props.extents,
            globalOrigin: props.globalOrigin,
            ecefLocation: props.location instanceof core_common_1.Cartographic ? core_common_1.EcefLocation.createFromCartographicOrigin(props.location) : props.location,
            key: "",
            iTwinId: props.iTwinId,
        });
        IModelConnection.onOpen.raiseEvent(connection);
        return connection;
    }
    /** There are no connections to the backend to close in the case of a BlankConnection.
     * However, there are frontend resources (like the tile cache) that can be disposed.
     * @note A BlankConnection should not be used after calling `close`.
     */
    async close() {
        this.beforeClose();
    }
    /** @internal */
    closeSync() {
        this.beforeClose();
    }
}
exports.BlankConnection = BlankConnection;
/** A connection to a [SnapshotDb]($backend) hosted on a backend.
 * @public
 */
class SnapshotConnection extends IModelConnection {
    /** Type guard for instanceof [[SnapshotConnection]] */
    isSnapshotConnection() { return true; }
    /** The Guid that identifies this iModel. */
    get iModelId() { return (0, core_bentley_1.expectDefined)(super.iModelId); } // GuidString | undefined for the superclass, but required for SnapshotConnection
    /** Returns `true` if [[close]] has already been called. */
    get isClosed() { return this._isClosed ? true : false; }
    _isClosed;
    /** Returns `true` if this is a connection to a remote snapshot iModel resolved by the backend.
     * @see [[openRemote]]
     */
    get isRemote() { return this._isRemote ? true : false; }
    _isRemote;
    /** Open an IModelConnection to a read-only snapshot iModel from a file name.
     * @note This method is intended for desktop or mobile applications and is not available for web applications.
     */
    static async openFile(filePath) {
        if (!IpcApp_1.IpcApp.isValid)
            throw new Error("IPC required to open a snapshot");
        core_bentley_1.Logger.logTrace(loggerCategory, "SnapshotConnection.openFile", () => ({ filePath }));
        const connectionProps = await IpcApp_1.IpcApp.appFunctionIpc.openSnapshot(filePath);
        const connection = new SnapshotConnection(connectionProps);
        IModelConnection.onOpen.raiseEvent(connection);
        return connection;
    }
    /** Open an IModelConnection to a remote read-only snapshot iModel from a key that will be resolved by the backend.
     * @note This method is intended for web applications.
     * @deprecated in 4.10 - will not be removed until after 2026-06-13. Use [[CheckpointConnection.openRemote]].
     */
    static async openRemote(fileKey) {
        const routingContext = IModelRoutingContext_1.IModelRoutingContext.current || IModelRoutingContext_1.IModelRoutingContext.default;
        core_common_1.RpcManager.setIModel({ iModelId: "undefined", key: fileKey });
        const openResponse = await core_common_1.SnapshotIModelRpcInterface.getClientForRouting(routingContext.token).openRemote(fileKey); // eslint-disable-line @typescript-eslint/no-deprecated
        core_bentley_1.Logger.logTrace(loggerCategory, "SnapshotConnection.openRemote", () => ({ fileKey }));
        const connection = new SnapshotConnection(openResponse);
        connection.routingContext = routingContext;
        connection._isRemote = true;
        IModelConnection.onOpen.raiseEvent(connection);
        return connection;
    }
    /** Close this SnapshotConnection.
     * @note For local snapshot files, `close` closes the connection and the underlying [SnapshotDb]($backend) database file.
     * For remote snapshots, `close` only closes the connection and frees any frontend resources allocated to the connection.
     * @see [[openFile]], [[openRemote]]
     */
    async close() {
        if (this.isClosed)
            return;
        this.beforeClose();
        try {
            if (!this.isRemote) {
                await IpcApp_1.IpcApp.appFunctionIpc.closeIModel(this.key);
            }
        }
        finally {
            this._isClosed = true;
        }
    }
}
exports.SnapshotConnection = SnapshotConnection;
/** @public */
(function (IModelConnection) {
    /** The collection of loaded ModelState objects for an [[IModelConnection]]. */
    class Models {
        _iModel;
        _loadedExtents = new Map();
        _geometryChangedListener;
        _loaded = new Map();
        /** @internal */
        get loaded() { return this._loaded; }
        /** An iterator over all currently-loaded models. */
        [Symbol.iterator]() {
            return this._loaded.values()[Symbol.iterator]();
        }
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
            IModelConnection.onOpen.addListener(() => {
                if (this._iModel.isBriefcaseConnection()) {
                    this._geometryChangedListener = (changes) => {
                        changes.forEach((change) => {
                            this._loadedExtents.delete(change.id);
                        });
                    };
                    this._iModel.txns.onModelGeometryChanged.addListener(this._geometryChangedListener);
                }
            });
            IModelConnection.onClose.addListener(() => {
                if (this._iModel.isBriefcaseConnection() && this._geometryChangedListener) {
                    this._iModel.txns.onModelGeometryChanged.removeListener(this._geometryChangedListener);
                    this._geometryChangedListener = undefined;
                }
            });
        }
        /** The Id of the [RepositoryModel]($backend). */
        get repositoryModelId() { return "0x1"; }
        /** @internal */
        async getDictionaryModel() {
            const res = await this._iModel.models.queryProps({ from: "bis.DictionaryModel", wantPrivate: true });
            if (res.length !== 1 || res[0].id === undefined)
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadModel, "bis.DictionaryModel");
            return res[0].id;
        }
        /** Get a batch of [[ModelProps]] given a list of Model ids. */
        async getProps(modelIds) {
            const iModel = this._iModel;
            return iModel.isOpen ? core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getModelProps(iModel.getRpcProps(), [...core_bentley_1.Id64.toIdSet(modelIds)]) : [];
        }
        /** Find a ModelState in the set of loaded Models by ModelId. */
        getLoaded(id) {
            return this._loaded.get(id);
        }
        /** Given a set of modelIds, return the subset of corresponding models that are not currently loaded.
         * @param modelIds The set of model Ids
         * @returns The subset of the supplied Ids corresponding to models that are not currently loaded, or undefined if all of the specified models are loaded.
         */
        filterLoaded(modelIds) {
            let unloaded;
            for (const id of core_bentley_1.Id64.iterable(modelIds)) {
                if (undefined === this.getLoaded(id)) {
                    if (undefined === unloaded)
                        unloaded = new Set();
                    unloaded.add(id);
                }
            }
            return unloaded;
        }
        /** load a set of Models by Ids. After the returned Promise resolves, you may get the ModelState objects by calling getLoadedModel. */
        async load(modelIds) {
            const notLoaded = this.filterLoaded(modelIds);
            if (undefined === notLoaded)
                return; // all requested models are already loaded
            try {
                const propArray = await this.getProps(notLoaded);
                await this.updateLoadedWithModelProps(propArray);
            }
            catch {
                // ignore error, we had nothing to do.
            }
        }
        /** Given an array of modelProps, find the class for each model and construct it. save it in the iModelConnection's loaded set. */
        async updateLoadedWithModelProps(modelProps) {
            try {
                for (const props of modelProps) {
                    const ctor = await this._iModel.findClassFor(props.classFullName, ModelState_1.ModelState);
                    if (undefined !== ctor && undefined !== props.id && undefined === this.getLoaded(props.id)) { // do not overwrite if someone else loads it while we await
                        const modelState = new ctor(props, this._iModel); // create a new instance of the appropriate ModelState subclass
                        this._loaded.set(modelState.id, modelState); // save it in loaded set
                    }
                }
            }
            catch {
                // ignore error, we had nothing to do.
            }
        }
        /** Remove a model from the set of loaded models. Used internally by BriefcaseConnection in response to txn events.
         * @internal
         */
        unload(modelId) {
            this._loaded.delete(modelId);
        }
        /** Query for a set of model ranges by ModelIds.
         * @param modelIds the Id or Ids of the [GeometricModel]($backend)s for which to query the ranges.
         * @returns An array containing the range of each model of each unique model Id, omitting the range for any Id which did no identify a GeometricModel.
         * @note The contents of the returned array do not follow a deterministic order.
         * @throws [IModelError]($common) if exactly one model Id is specified and that Id does not identify a GeometricModel.
         * @see [[queryExtents]] for a similar function that does not throw and produces a deterministically-ordered result.
         */
        async queryModelRanges(modelIds) {
            const results = await this.queryExtents([...core_bentley_1.Id64.toIdSet(modelIds)]);
            if (results.length === 1 && results[0].status !== core_bentley_1.IModelStatus.Success) {
                throw new core_common_1.IModelError(results[0].status, "error querying model range");
            }
            return results.filter((x) => x.status === core_bentley_1.IModelStatus.Success).map((x) => x.extents);
        }
        /** For each [GeometricModel]($backend) specified by Id, attempts to obtain the union of the volumes of all geometric elements within that model.
         * @param modelIds The Id or Ids of the geometric models for which to obtain the extents.
         * @returns An array of results, one per supplied Id, in the order in which the Ids were supplied. If the extents could not be obtained, the
         * corresponding results entry's `extents` will be a "null" range (@see [Range3d.isNull]($geometry) and its `status` will indicate
         * why the extents could not be obtained (e.g., because the Id did not identify a [GeometricModel]($backend)).
         */
        async queryExtents(modelIds) {
            if (!this._iModel.isOpen)
                return [];
            if (typeof modelIds === "string")
                modelIds = [modelIds];
            const resolvedExtents = new Map();
            const uncachedModelIds = [];
            // Add the cached model ids and the invalid ids
            for (const modelId of modelIds) {
                const cachedExtents = this._loadedExtents.get(modelId);
                if (cachedExtents !== undefined) {
                    resolvedExtents.set(modelId, cachedExtents);
                }
                else if (!core_bentley_1.Id64.isValidId64(modelId)) {
                    resolvedExtents.set(modelId, { id: modelId, extents: core_geometry_1.Range3d.createNull(), status: core_bentley_1.IModelStatus.InvalidId });
                }
                else {
                    uncachedModelIds.push(modelId);
                }
            }
            // Run the ECSql to get uncached model extents
            if (uncachedModelIds.length > 0) {
                const modelList = uncachedModelIds.join(",");
                const useSingleModelQuery = uncachedModelIds.length === 1;
                const modelExtentsQuery = `
          SELECT
            Model.Id AS ECInstanceId,
            iModel_bbox_union(
              iModel_placement_aabb(
                iModel_placement(
                  iModel_point(Origin.X, Origin.Y, 0),
                  iModel_angles(Rotation, 0, 0),
                  iModel_bbox(
                    BBoxLow.X, BBoxLow.Y, -1,
                    BBoxHigh.X, BBoxHigh.Y, 1
                  )
                )
              )
            ) AS bbox
          FROM bis.GeometricElement2d
          WHERE Model.Id ${useSingleModelQuery ? `= ${uncachedModelIds[0]}` : `IN (${modelList})`}
            AND Origin.X IS NOT NULL
          GROUP BY Model.Id

          UNION

          SELECT
            ge.Model.Id AS ECInstanceId,
            iModel_bbox(
              min(i.MinX), min(i.MinY), min(i.MinZ),
              max(i.MaxX), max(i.MaxY), max(i.MaxZ)
            ) AS bbox
          FROM bis.SpatialIndex AS i
          INNER JOIN bis.GeometricElement3d AS ge
            ON ge.ECInstanceId = i.ECInstanceId
          INNER JOIN bis.GeometricModel3d AS gm
            ON ge.Model.Id = gm.ECInstanceId
          WHERE ge.Model.Id ${useSingleModelQuery ? `= ${uncachedModelIds[0]}` : `IN (${modelList})`}
            AND (gm.$->IsNotSpatiallyLocated? IS NULL OR gm.$->IsNotSpatiallyLocated? IS FALSE)
          GROUP BY ge.Model.Id

          UNION

          SELECT
            ge.Model.Id AS ECInstanceId,
            iModel_bbox_union(
              iModel_placement_aabb(
                iModel_placement(
                  iModel_point(ge.Origin.X, ge.Origin.Y, ge.Origin.Z),
                  iModel_angles(ge.Yaw, ge.Pitch, ge.Roll),
                  iModel_bbox(
                    ge.BBoxLow.X, ge.BBoxLow.Y, ge.BBoxLow.Z,
                    ge.BBoxHigh.X, ge.BBoxHigh.Y, ge.BBoxHigh.Z
                  )
                )
              )
            ) AS bbox
          FROM bis.GeometricElement3d ge
          INNER JOIN bis.GeometricModel3d gm
            ON ge.Model.Id = gm.ECInstanceId
          WHERE ge.Model.Id ${useSingleModelQuery ? `= ${uncachedModelIds[0]}` : `IN (${modelList})`}
            AND gm.$->IsNotSpatiallyLocated? IS TRUE
            AND ge.Origin.X IS NOT NULL
          GROUP BY ge.Model.Id
          `;
                const extentsQueryReader = this._iModel.createQueryReader(modelExtentsQuery, undefined, {
                    rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames,
                });
                for await (const row of extentsQueryReader) {
                    const byteArray = new Uint8Array(Object.values(row.bbox));
                    const extents = core_geometry_1.Range3d.fromArrayBuffer(byteArray.buffer);
                    const extent = { id: row.ECInstanceId, extents, status: core_bentley_1.IModelStatus.Success };
                    resolvedExtents.set(extent.id, extent);
                    this._loadedExtents.set(extent.id, extent);
                }
            }
            // Check if there still are any unresolved model IDs
            const unresolvedModelIds = uncachedModelIds.filter((id) => !resolvedExtents.has(id));
            if (unresolvedModelIds.length > 0) {
                const modelList = unresolvedModelIds.join(",");
                const modelExistenceQuery = `
          SELECT
            m.ECInstanceId,
            CASE WHEN g.ECInstanceId IS NOT NULL THEN 1 ELSE 0 END AS isGeometricModel
          FROM bis.Model m
          LEFT JOIN bis.GeometricModel g
            ON m.ECInstanceId = g.ECInstanceId
          WHERE m.ECInstanceId ${unresolvedModelIds.length === 1 ? `= ${unresolvedModelIds[0]}` : `IN (${modelList})`}
          `;
                const modelExistenceQueryReader = this._iModel.createQueryReader(modelExistenceQuery, undefined, {
                    rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames,
                });
                for await (const row of modelExistenceQueryReader) {
                    const extent = {
                        id: row.ECInstanceId,
                        extents: core_geometry_1.Range3d.createNull(),
                        status: row.isGeometricModel ? core_bentley_1.IModelStatus.Success : core_bentley_1.IModelStatus.WrongModel,
                    };
                    resolvedExtents.set(extent.id, extent);
                    this._loadedExtents.set(extent.id, extent);
                }
            }
            // Return the results while maintaining the same order
            return modelIds.map((modelId) => {
                const extent = resolvedExtents.get(modelId);
                if (extent === undefined) {
                    const notFound = { id: modelId, extents: core_geometry_1.Range3d.createNull(), status: core_bentley_1.IModelStatus.NotFound };
                    this._loadedExtents.set(notFound.id, notFound);
                    return notFound;
                }
                if (extent.status === core_bentley_1.IModelStatus.InvalidId) {
                    return { ...extent, id: "0" };
                }
                return extent;
            });
        }
        /** Query for a set of ModelProps of the specified ModelQueryParams.
         * @param queryParams The query parameters. The `limit` and `offset` members should be used to page results.
         * @throws [IModelError]($common) If the generated statement is invalid or would return too many props.
         */
        async queryProps(queryParams) {
            const iModel = this._iModel;
            if (!iModel.isOpen)
                return [];
            const params = { ...queryParams }; // make a copy
            params.from = queryParams.from || ModelState_1.ModelState.classFullName; // use "BisCore:Model" as default class name
            params.where = queryParams.where || "";
            if (!queryParams.wantPrivate) {
                if (params.where.length > 0)
                    params.where += " AND ";
                params.where += "IsPrivate=FALSE ";
            }
            if (!queryParams.wantTemplate) {
                if (params.where.length > 0)
                    params.where += " AND ";
                params.where += "IsTemplate=FALSE ";
            }
            return core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryModelProps(iModel.getRpcProps(), params);
        }
        /** Asynchronously stream ModelProps using the specified ModelQueryParams. */
        async *query(queryParams) {
            // NOTE: this implementation has the desired API signature, but its implementation must be improved to actually page results
            const modelPropsArray = await this.queryProps(queryParams);
            for (const modelProps of modelPropsArray) {
                yield modelProps;
            }
        }
    }
    IModelConnection.Models = Models;
    /** The collection of Elements for an [[IModelConnection]]. */
    class Elements {
        _iModel;
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
        }
        /** The Id of the [root subject element]($docs/bis/guide/references/glossary.md#subject-root) for this iModel. */
        get rootSubjectId() { return "0x1"; }
        /** Get a set of element ids that satisfy a query */
        async queryIds(params) { return this._iModel.queryEntityIds(params); }
        /** Get an array of [[ElementProps]] given one or more element ids.
         * @note This method returns **all** of the properties of the element (excluding GeometryStream), which may be a very large amount of data - consider using
         * [[IModelConnection.query]] to select only those properties of interest to limit the amount of data returned.
         */
        async getProps(arg) {
            const iModel = this._iModel;
            return iModel.isOpen ? core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getElementProps(this._iModel.getRpcProps(), [...core_bentley_1.Id64.toIdSet(arg)]) : [];
        }
        /** Obtain the properties of a single element, optionally specifying specific properties to include or exclude.
         * For example, [[getProps]] and [[queryProps]] omit the [GeometryStreamProps]($common) property of [GeometricElementProps]($common) and [GeometryPartProps]($common)
         * because it can be quite large and is generally not useful to frontend code. The following code requests that the geometry stream be included:
         * ```ts
         *  const props = await iModel.elements.loadProps(elementId, { wantGeometry: true });
         * ```
         * @param identifier Identifies the element by its Id, federation Guid, or [Code]($common).
         * @param options Optionally includes or excludes specific properties.
         * @returns The properties of the requested element; or `undefined` if no element exists with the specified identifier or the iModel is not open.
         * @throws [IModelError]($common) if the element exists but could not be loaded.
         */
        async loadProps(identifier, options) {
            const imodel = this._iModel;
            return imodel.isOpen ? core_common_1.IModelReadRpcInterface.getClientForRouting(imodel.routingContext.token).loadElementProps(imodel.getRpcProps(), identifier, options) : undefined;
        }
        /** Get an array  of [[ElementProps]] that satisfy a query
         * @param params The query parameters. The `limit` and `offset` members should be used to page results.
         * @throws [IModelError]($common) If the generated statement is invalid or would return too many props.
         */
        async queryProps(params) {
            const iModel = this._iModel;
            return iModel.isOpen ? core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryElementProps(iModel.getRpcProps(), params) : [];
        }
        /** Obtain the [Placement]($common)s of a set of [GeometricElement]($backend)s.
         * @param elementIds The Ids of the elements whose placements are to be queried.
         * @param options Options customizing how the placements are queried.
         * @returns an array of placements, each having an additional `elementId` property identifying the element from which the placement was obtained.
         * @note Any Id that does not identify a geometric element with a valid bounding box and origin is omitted from the returned array.
         */
        async getPlacements(elementIds, options) {
            let ids;
            if (typeof elementIds === "string")
                ids = [elementIds];
            else if (!Array.isArray(elementIds))
                ids = Array.from(elementIds);
            else
                ids = elementIds;
            if (ids.length === 0)
                return [];
            const select3d = `
        SELECT
          ECInstanceId,
          Origin.x as x, Origin.y as y, Origin.z as z,
          BBoxLow.x as lx, BBoxLow.y as ly, BBoxLow.z as lz,
          BBoxHigh.x as hx, BBoxHigh.y as hy, BBoxHigh.z as hz,
          Yaw, Pitch, Roll,
          NULL as Rotation
        FROM bis.GeometricElement3d
        WHERE Origin IS NOT NULL AND BBoxLow IS NOT NULL AND BBoxHigh IS NOT NULL`;
            // Note: For the UNION ALL statement, the column aliases in select2d are ignored - so they
            // must match those in select3d.
            const select2d = `
        SELECT
          ECInstanceId,
          Origin.x as x, Origin.y as y, NULL as z,
          BBoxLow.x as lx, BBoxLow.y as ly, NULL as lz,
          BBoxHigh.x as hx, BBoxHigh.y as hy, NULL as hz,
          NULL as yaw, NULL as pitch, NULL as roll,
          Rotation
        FROM bis.GeometricElement2d
        WHERE Origin IS NOT NULL AND BBoxLow IS NOT NULL AND BBoxHigh IS NOT NULL`;
            const idCriterion = `ECInstanceId IN (${ids.join(",")})`;
            let ecsql;
            switch (options?.type) {
                case "3d":
                    ecsql = `${select3d} AND ${idCriterion}`;
                    break;
                case "2d":
                    ecsql = `${select2d} AND ${idCriterion}`;
                    break;
                default:
                    ecsql = `
            SELECT * FROM (
              ${select3d}
              UNION ALL
              ${select2d}
            ) WHERE ${idCriterion}`;
                    break;
            }
            const placements = new Array();
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            for await (const queryRow of this._iModel.createQueryReader(ecsql, undefined, { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames })) {
                const row = queryRow.toRow();
                const origin = [row.x, row.y, row.z];
                const bbox = {
                    low: { x: row.lx, y: row.ly, z: row.lz },
                    high: { x: row.hx, y: row.hy, z: row.hz },
                };
                let placement;
                if (undefined === row.lz)
                    placement = core_common_1.Placement2d.fromJSON({ bbox, origin, angle: row.rotation });
                else
                    placement = core_common_1.Placement3d.fromJSON({ bbox, origin, angles: { yaw: row.yaw, pitch: row.pitch, roll: row.roll } });
                const placementWithId = placement;
                placementWithId.elementId = row.id;
                placements.push(placementWithId);
            }
            return placements;
        }
    }
    IModelConnection.Elements = Elements;
    /** The collection of [[CodeSpec]] entities for an [[IModelConnection]]. */
    class CodeSpecs {
        _iModel;
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
        }
        _isCodeSpecProperties(x) {
            if (!x || !x.scopeSpec || !Object.values(core_common_1.CodeScopeSpec.Type).includes(x.scopeSpec.type))
                return false;
            if (typeof x.scopeSpec.fGuidRequired !== "boolean" && typeof x.scopeSpec.fGuidRequired !== "undefined")
                return false;
            if (typeof x.scopeSpec.relationship !== "string" && typeof x.scopeSpec.relationship !== "undefined")
                return false;
            if (typeof x.spec?.isManagedWithDgnDb !== "boolean" && typeof x.spec?.isManagedWithDgnDb !== "undefined")
                return false;
            if (typeof x.version !== "string" && typeof x.version !== "undefined")
                return false;
            return true;
        }
        async _loadCodeSpec(identifier) {
            const isNameDefined = identifier.name !== undefined;
            const query = `
        SELECT ECInstanceId AS Id, Name, JsonProperties
        FROM BisCore.CodeSpec
        WHERE ${isNameDefined ? `Name=:name` : `Id=:id`}`;
            const params = new core_common_1.QueryBinder();
            if (isNameDefined) {
                params.bindString("name", identifier.name);
            }
            else {
                params.bindId("id", identifier.id);
            }
            const queryReader = this._iModel.createQueryReader(query, params, {
                rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames,
            });
            const queryResult = await queryReader.next();
            if (queryResult.done)
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, "CodeSpec not found");
            const codeSpecResult = queryResult.value;
            if (typeof codeSpecResult.Id !== "string" ||
                typeof codeSpecResult.Name !== "string" ||
                typeof codeSpecResult.JsonProperties !== "string")
                throw new Error("Invalid CodeSpec was returned");
            const jsonProperties = JSON.parse(codeSpecResult.JsonProperties);
            if (!this._isCodeSpecProperties(jsonProperties))
                throw new Error("Invalid CodeSpecProperties returned in the CodeSpec");
            const codeSpec = core_common_1.CodeSpec.createFromJson(this._iModel, core_bentley_1.Id64.fromString(codeSpecResult.Id), codeSpecResult.Name, jsonProperties);
            return codeSpec;
        }
        /** Look up a CodeSpec by Id.
         * @param codeSpecId The Id of the CodeSpec to load
         * @returns The CodeSpec with the specified Id
         * @throws [[IModelError]] if the Id is invalid or if no CodeSpec with that Id could be found.
         */
        async getById(codeSpecId) {
            if (codeSpecId === "")
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, "CodeSpec not found");
            if (!core_bentley_1.Id64.isValid(codeSpecId))
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Invalid codeSpecId", () => ({ codeSpecId }));
            return this._loadCodeSpec({ id: codeSpecId });
        }
        /** Look up a CodeSpec by name.
         * @param name The name of the CodeSpec to load
         * @returns The CodeSpec with the specified name
         * @throws [[IModelError]] if no CodeSpec with the specified name could be found.
         */
        async getByName(name) {
            if (name === "")
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, "CodeSpec not found");
            return this._loadCodeSpec({ name });
        }
    }
    IModelConnection.CodeSpecs = CodeSpecs;
    /** The collection of views for an [[IModelConnection]]. */
    class Views {
        _iModel;
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
        }
        _writeViewStoreProxy;
        _readViewStoreProxy;
        get viewStoreWriter() {
            return this._writeViewStoreProxy ??= new Proxy(this, {
                get(views, methodName) {
                    const iModel = views._iModel;
                    return async (...args) => core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).callViewStore(iModel.getRpcProps(), core_common_1.ViewStoreRpc.version, true, methodName, ...args);
                },
            });
        }
        get viewsStoreReader() {
            return this._readViewStoreProxy ??= new Proxy(this, {
                get(views, methodName) {
                    const iModel = views._iModel;
                    return async (...args) => core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).callViewStore(iModel.getRpcProps(), core_common_1.ViewStoreRpc.version, false, methodName, ...args);
                },
            });
        }
        /** Query for an array of ViewDefinitionProps
         * @param queryParams Query parameters specifying the views to return. The `limit` and `offset` members should be used to page results.
         * @throws [IModelError]($common) If the generated statement is invalid or would return too many props.
         */
        async queryProps(queryParams) {
            const iModel = this._iModel;
            if (iModel.isClosed)
                return [];
            const params = { ...queryParams }; // make a copy
            params.from = queryParams.from || ViewState_1.ViewState.classFullName; // use "BisCore:ViewDefinition" as default class name
            params.where = queryParams.where || "";
            if (queryParams.wantPrivate === undefined || !queryParams.wantPrivate) {
                if (params.where.length > 0)
                    params.where += " AND ";
                params.where += "IsPrivate=FALSE ";
            }
            const viewProps = await core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryElementProps(iModel.getRpcProps(), params);
            (0, core_bentley_1.assert)((viewProps.length === 0) || ("categorySelectorId" in viewProps[0]), "invalid view definition"); // spot check that the first returned element is-a ViewDefinitionProps
            return viewProps;
        }
        /** Get an array of the ViewSpecs for all views in this IModel that satisfy a ViewQueryParams.
         *
         * This is typically used to create a list for UI.
         *
         * For example:
         * ```ts
         * [[include:IModelConnection.Views.getSpatialViewList]]
         * ```
         * @param queryParams The parameters for the views to find. The `limit` and `offset` members should be used to page results.
         * @throws [IModelError]($common) If the generated statement is invalid or would return too many props.
         */
        async getViewList(queryParams) {
            const views = [];
            const viewProps = await this.queryProps(queryParams);
            viewProps.forEach((viewProp) => {
                views.push({ id: viewProp.id, name: viewProp.code.value ?? "", class: viewProp.classFullName });
            });
            return views;
        }
        /** Query the Id of the default [ViewDefinition]($backend), if any, stored in this iModel's property table.
         * The default view is typically chosen by the application (such as a connector) that created the iModel.
         * There is no guarantee that this view will be suitable for the purposes of any other applications.
         * Most applications should ignore the default view and instead create a [[ViewState]] that fits their own requirements using APIs like [[ViewCreator3d]].
         * @returns the Id of the default view as defined in the iModel's property table, or an invalid ID if no default view is defined.
         * @deprecated in 4.2 - will not be removed until after 2026-06-13. Create a ViewState to your own specifications.
         */
        async queryDefaultViewId() {
            const iModel = this._iModel;
            return iModel.isOpen ? core_common_1.IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getDefaultViewId(iModel.getRpcProps()) : core_bentley_1.Id64.invalid;
        }
        /** Load a [[ViewState]] object from the specified [[ViewDefinition]] id. */
        async load(viewDefinitionId) {
            const options = {
                displayStyle: {
                    omitScheduleScriptElementIds: !IModelApp_1.IModelApp.tileAdmin.enableFrontendScheduleScripts,
                    compressExcludedElementIds: true,
                },
            };
            const viewProps = await core_common_1.IModelReadRpcInterface.getClientForRouting(this._iModel.routingContext.token).getViewStateData(this._iModel.getRpcProps(), viewDefinitionId, options);
            const viewState = await this.convertViewStatePropsToViewState(viewProps);
            return viewState;
        }
        /** Return the [[ViewState]] object associated with the [[ViewStateProps]] passed in. */
        async convertViewStatePropsToViewState(viewProps) {
            const className = viewProps.viewDefinitionProps.classFullName;
            const ctor = await this._iModel.findClassFor(className, undefined);
            if (undefined === ctor)
                throw new core_common_1.IModelError(core_bentley_1.IModelStatus.WrongClass, "Invalid ViewState class", () => viewProps);
            const viewState = (0, core_bentley_1.expectDefined)(ctor.createFromProps(viewProps, this._iModel));
            await viewState.load(); // loads models for ModelSelector
            return viewState;
        }
    }
    IModelConnection.Views = Views;
    /** Provides access to information about the [Category]($backend)'s stored in an [[IModelConnection]].
     * This information is cached internally so that repeated requests need not query the backend.
     * @see [[IModelConnection.categories]] for the categories associated with a specific iModel.
     */
    class Categories {
        /** @internal */
        cache;
        /** @internal */
        constructor(iModel) {
            this.cache = new SubCategoriesCache_1.SubCategoriesCache(iModel);
        }
        /** Obtain information about one or more [Category]($backend)'s. */
        async getCategoryInfo(categoryIds) {
            return this.cache.getCategoryInfo(categoryIds);
        }
        /** Obtain information about one or more [SubCategory]($backend)'s belonging to the specified [Category]($backend). */
        async getSubCategoryInfo(args) {
            return this.cache.getSubCategoryInfo(args.category, args.subCategories);
        }
    }
    IModelConnection.Categories = Categories;
})(IModelConnection || (exports.IModelConnection = IModelConnection = {}));
//# sourceMappingURL=IModelConnection.js.map