/*---------------------------------------------------------------------------------------------
* 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
 */
import { assert, BeEvent, GeoServiceStatus, Id64, Logger, OneAtATimeAction, OpenMode, TransientIdSequence, } from "@itwin/core-bentley";
import { Cartographic, CodeSpec, DbResult, EcefLocation, ECSqlReader, FontMap, GeoCoordStatus, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, IModelStatus, mapToGeoServiceStatus, NoContentError, Placement2d, Placement3d, QueryOptionsBuilder, QueryRowFormat, RpcManager, SnapshotIModelRpcInterface, ViewStoreRpc, } from "@itwin/core-common";
import { Point3d, Range3d, Transform } from "@itwin/core-geometry";
import { FrontendLoggerCategory } from "./common/FrontendLoggerCategory";
import { GeoServices } from "./GeoServices";
import { IModelApp } from "./IModelApp";
import { IModelRoutingContext } from "./IModelRoutingContext";
import { ModelState } from "./ModelState";
import { HiliteSet, SelectionSet } from "./SelectionSet";
import { SubCategoriesCache } from "./SubCategoriesCache";
import { BingElevationProvider } from "./tile/internal";
import { Tiles } from "./Tiles";
import { ViewState } from "./ViewState";
import { _requestSnap } from "./common/internal/Symbols";
const loggerCategory = FrontendLoggerCategory.IModelConnection;
/** A connection to a [IModelDb]($backend) hosted on the backend.
 * @public
 * @extensions
 */
export class IModelConnection extends IModel {
    /** A cache of information about SubCategories chiefly used for rendering.
     * @internal
     */
    get subcategories() { return this.categories.cache; }
    /** @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; }
    /** 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 === 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; }
    /** Load the FontMap for this IModelConnection.
     * @returns Returns a Promise<FontMap> that is fulfilled when the FontMap member of this IModelConnection is valid.
     */
    async loadFontMap() {
        if (undefined === this.fontMap) {
            this.fontMap = new FontMap();
            if (this.isOpen) {
                const fontProps = await IModelReadRpcInterface.getClientForRouting(this.routingContext.token).readFontJson(this.getRpcProps());
                this.fontMap.addFonts(fontProps.fonts);
            }
        }
        return this.fontMap;
    }
    /** 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.lookupEntityClass(className);
        if (undefined !== ctor)
            return ctor;
        // it's not registered, we need to query its class hierarchy.
        ctor = defaultClass; // in case we cant find a registered class that handles this class
        // wait until we get the full list of base classes from backend
        if (this.isOpen) {
            const baseClasses = await 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.lookupEntityClass(className);
            if (undefined !== ctor)
                return ctor;
            // walk through the list until we find a registered base class
            baseClasses.some((baseClass) => {
                const test = IModelApp.lookupEntityClass(baseClass);
                if (test === undefined)
                    return false; // nope, not registered
                ctor = test; // found it, save it
                IModelApp.registerEntityState(className, ctor); // and register the fact that our starting class is handled by this subclass.
                return true; // stop
            });
        }
        return ctor; // either the baseClass handler or defaultClass if we didn't find a registered baseClass
    }
    /** @internal */
    constructor(iModelProps) {
        super(iModelProps);
        /** Generator for unique Ids of transient graphics for this IModelConnection. */
        this.transientIds = new TransientIdSequence();
        /** @internal Whether GCS has been disabled for this iModelConnection. */
        this._gcsDisabled = false;
        this._extentsExpansion = Range3d.createNull();
        /** The RPC routing for this connection. */
        this.routingContext = IModelRoutingContext.default;
        /** 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.
         */
        this.onClose = new BeEvent();
        this._snapRpc = new OneAtATimeAction(async (props) => IModelReadRpcInterface.getClientForRouting(this.routingContext.token).requestSnap(this.getRpcProps(), IModelApp.sessionId, props));
        this._toolTipRpc = new OneAtATimeAction(async (id) => IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getToolTipMessage(this.getRpcProps(), id));
        /** 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
         */
        this.onMapElevationLoaded = new BeEvent();
        super.initialize(iModelProps.name, 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(this);
        this.hilited = new HiliteSet(this);
        this.tiles = new Tiles(this);
        this.geoServices = GeoServices.createForIModel(this);
        /* eslint-disable-next-line deprecation/deprecation */
        this.displayedExtents = Range3d.fromJSON(this.projectExtents);
        this.onProjectExtentsChanged.addListener(() => {
            // Compute new displayed extents as the union of the ranges we previously expanded by with the new project extents.
            /* eslint-disable-next-line deprecation/deprecation */
            this.expandDisplayedExtents(this._extentsExpansion);
        });
        this.hilited.onModelSubCategoryModeChanged.addListener(() => {
            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.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 IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryRows(this.getRpcProps(), request);
            },
        };
        return new 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 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 IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryAllUsedSpatialSubCategories(this.getRpcProps());
    }
    /** Execute a query and stream its results
     * The result of the query is async iterator over the rows. The iterator will get next page automatically once rows in current page has been read.
     * [ECSQL row]($docs/learning/ECSQLRowFormat).
     *
     * See also:
     * - [ECSQL Overview]($docs/learning/frontend/ExecutingECSQL)
     * - [Code Examples]($docs/learning/frontend/ECSQLCodeExamples)
     *
     * @param ecsql The ECSQL statement to execute
     * @param params The values to bind to the parameters (if the ECSQL has any).
     * @param options Allow to specify certain flags which control how query is executed.
     * @returns Returns the query result as an *AsyncIterableIterator<any>*  which lazy load result as needed. The row format is determined by *rowFormat* parameter.
     * See [ECSQL row format]($docs/learning/ECSQLRowFormat) for details about the format of the returned rows.
     * @throws [IModelError]($common) If there was any error while submitting, preparing or stepping into query
     * @deprecated in 3.7. Use [[createQueryReader]] instead; it accepts the same parameters.
     */
    async *query(ecsql, params, options) {
        const builder = new QueryOptionsBuilder(options);
        const reader = this.createQueryReader(ecsql, params, builder.getOptions());
        while (await reader.step())
            yield reader.formatCurrentRow();
    }
    /** Compute number of rows that would be returned by the ECSQL.
     *
     * See also:
     * - [ECSQL Overview]($docs/learning/frontend/ExecutingECSQL)
     * - [Code Examples]($docs/learning/frontend/ECSQLCodeExamples)
     *
     * @param ecsql The ECSQL statement to execute
     * @param params The values to bind to the parameters (if the ECSQL has any).
     * See "[iTwin.js Types used in ECSQL Parameter Bindings]($docs/learning/ECSQLParameterTypes)" for details.
     * @returns Return row count.
     * @throws [IModelError]($common) If the statement is invalid
     * @deprecated in 3.7. Count the number of results using `count(*)` where the original query is a subquery instead. E.g., `SELECT count(*) FROM (<query-whose-rows-to-count>)`.
     */
    async queryRowCount(ecsql, params) {
        for await (const row of this.createQueryReader(`select count(*) from (${ecsql})`, params)) {
            return row[0];
        }
        throw new IModelError(DbResult.BE_SQLITE_ERROR, "Failed to get row count");
    }
    /** Cancel any previous query with same token and run execute the current specified query.
     * The result of the query is async iterator over the rows. The iterator will get next page automatically once rows in current page has been read.
     * [ECSQL row]($docs/learning/ECSQLRowFormat).
     *
     * See also:
     * - [ECSQL Overview]($docs/learning/frontend/ExecutingECSQL)
     * - [Code Examples]($docs/learning/frontend/ECSQLCodeExamples)
     *
     * @param ecsql The ECSQL statement to execute
     * @param token None empty restart token. The previous query with same token would be cancelled. This would cause
     * exception which user code must handle.
     * @param params The values to bind to the parameters (if the ECSQL has any).
     * @param options Allow to specify certain flags which control how query is executed.
     * @returns Returns the query result as an *AsyncIterableIterator<any>*  which lazy load result as needed. The row format is determined by *rowFormat* parameter.
     * See [ECSQL row format]($docs/learning/ECSQLRowFormat) for details about the format of the returned rows.
     * @throws [IModelError]($common) If there was any error while submitting, preparing or stepping into query
     * @deprecated in 3.7. Use [[createQueryReader]] instead. Pass in the restart token as part of the `config` argument; e.g., `{ restartToken: myToken }` or `new QueryOptionsBuilder().setRestartToken(myToken).getOptions()`.
     */
    async *restartQuery(token, ecsql, params, options) {
        for await (const row of this.createQueryReader(ecsql, params, new QueryOptionsBuilder(options).setRestartToken(token).getOptions())) {
            yield row;
        }
    }
    /** 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 IModelReadRpcInterface.getClientForRouting(this.routingContext.token).queryEntityIds(this.getRpcProps(), params) : undefined);
    }
    /** Request a snap from the backend.
     * @note callers must gracefully handle Promise rejected with AbandonedError
     * @internal
     */
    async [_requestSnap](props) {
        return this.isOpen ? this._snapRpc.request(props) : { status: 2 };
    }
    /** @internal
     * @deprecated in 4.8. Use AccuSnap.doSnapRequest.
     */
    async requestSnap(props) {
        return this[_requestSnap](props);
    }
    /** 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 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 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 = IModelReadRpcInterface.getClientForRouting(this.routingContext.token);
            const img = rpcClient.queryTextureData(this.getRpcProps(), textureLoadProps);
            return img;
        }
        return undefined;
    }
    /** Request element mass properties from the backend.
     * @note For better performance use [[getMassPropertiesPerCandidate]] when called from a loop with identical operations and a single candidate per iteration.
     */
    async getMassProperties(requestProps) {
        return IModelReadRpcInterface.getClientForRouting(this.routingContext.token).getMassProperties(this.getRpcProps(), requestProps);
    }
    /** Request mass properties for multiple elements from the backend. */
    async getMassPropertiesPerCandidate(requestProps) {
        return 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 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 IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        const geoConverter = this.geoServices.getConverter();
        const coordResponse = await geoConverter.getGeoCoordinatesFromIModelCoordinates([spatial]);
        if (1 !== coordResponse.geoCoords.length || GeoCoordStatus.NoGCSDefined === coordResponse.geoCoords[0].s)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (GeoCoordStatus.Success !== coordResponse.geoCoords[0].s) {
            const geoServiceStatus = mapToGeoServiceStatus(coordResponse.geoCoords[0].s);
            throw new IModelError(geoServiceStatus, "Error converting spatial to cartographic");
        }
        const longLatHeight = Point3d.fromJSON(coordResponse.geoCoords[0].p); // x is longitude in degrees, y is latitude in degrees, z is height in meters...
        return 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 IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (!this.isOpen)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not open");
        if (spatial.length === 0)
            return [];
        const geoConverter = this.geoServices.getConverter(datumOrGCRS);
        assert(undefined !== geoConverter);
        const coordResponse = await geoConverter.getGeoCoordinatesFromIModelCoordinates(spatial);
        if (coordResponse.geoCoords.length !== spatial.length)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        return coordResponse.geoCoords.map((coord) => {
            switch (coord.s) {
                case GeoCoordStatus.NoGCSDefined:
                    throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
                case GeoCoordStatus.Success:
                    const llh = Point3d.fromJSON(coord.p);
                    return Cartographic.fromDegrees({ longitude: llh.x, latitude: llh.y, height: llh.z });
                default:
                    throw new IModelError(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 IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        const geoConverter = this.geoServices.getConverter();
        const geoCoord = 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 || GeoCoordStatus.NoGCSDefined === coordResponse.iModelCoords[0].s)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (GeoCoordStatus.Success !== coordResponse.iModelCoords[0].s) {
            const geoServiceStatus = mapToGeoServiceStatus(coordResponse.iModelCoords[0].s);
            throw new IModelError(geoServiceStatus, "Error converting cartographic to spatial");
        }
        result = result ? result : 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) => 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 IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        if (!this.isOpen)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not open");
        if (geoCoords.length === 0)
            return [];
        const geoConverter = this.geoServices.getConverter(datumOrGCRS);
        assert(undefined !== geoConverter);
        const coordResponse = await geoConverter.getIModelCoordinatesFromGeoCoordinates(geoCoords);
        if (coordResponse.iModelCoords.length !== geoCoords.length)
            throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
        return coordResponse.iModelCoords.map((coord) => {
            switch (coord.s) {
                case GeoCoordStatus.NoGCSDefined:
                    throw new IModelError(GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated");
                case GeoCoordStatus.Success:
                    return Point3d.fromJSON(coord.p);
                default:
                    throw new IModelError(mapToGeoServiceStatus(coord.s), "Error converting cartographic to spatial");
            }
        });
    }
    /** Expand this iModel's [[displayedExtents]] to include the specified range.
     * This is done automatically when reality models are added to a spatial view. In some cases a [[TiledGraphicsProvider]] may wish to expand
     * the extents explicitly to include its geometry.
     * @deprecated in 3.6. See [[displayedExtents]].
     */
    expandDisplayedExtents(range) {
        this._extentsExpansion.extendRange(range);
        /* eslint-disable-next-line deprecation/deprecation */
        this.displayedExtents.setFrom(this.projectExtents);
        /* eslint-disable-next-line deprecation/deprecation */
        this.displayedExtents.extendRange(this._extentsExpansion);
    }
    /** @internal */
    getMapEcefToDb(bimElevationBias) {
        if (!this.ecefLocation)
            return Transform.createIdentity();
        const mapEcefToDb = this.ecefLocation.getTransform().inverse();
        if (!mapEcefToDb) {
            assert(false);
            return Transform.createIdentity();
        }
        mapEcefToDb.origin.z += bimElevationBias;
        return mapEcefToDb;
    }
    /** 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 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 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;
    }
}
/** The maximum time (in milliseconds) to wait before timing out the request to open a connection to a new iModel */
IModelConnection.connectionTimeout = 10 * 60 * 1000;
/** 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.
 */
IModelConnection.onClose = new BeEvent();
/** Event called immediately after *any* IModelConnection is opened. */
IModelConnection.onOpen = new BeEvent();
/** 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
 */
export 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) {
        return new this({
            name: props.name,
            rootSubject: { name: props.name },
            projectExtents: props.extents,
            globalOrigin: props.globalOrigin,
            ecefLocation: props.location instanceof Cartographic ? EcefLocation.createFromCartographicOrigin(props.location) : props.location,
            key: "",
            iTwinId: props.iTwinId,
        });
    }
    /** 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();
    }
}
/** A connection to a [SnapshotDb]($backend) hosted on a backend.
 * @public
 */
export class SnapshotConnection extends IModelConnection {
    /** Type guard for instanceof [[SnapshotConnection]] */
    isSnapshotConnection() { return true; }
    /** The Guid that identifies this iModel. */
    get iModelId() { return 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; }
    /** 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; }
    /** Open an IModelConnection to a read-only snapshot iModel from a file name.
     * @note This method is intended for desktop or mobile applications and should not be used for web applications.
     */
    static async openFile(filePath) {
        const routingContext = IModelRoutingContext.current || IModelRoutingContext.default;
        RpcManager.setIModel({ iModelId: "undefined", key: filePath });
        const openResponse = await SnapshotIModelRpcInterface.getClientForRouting(routingContext.token).openFile(filePath);
        Logger.logTrace(loggerCategory, "SnapshotConnection.openFile", () => ({ filePath }));
        const connection = new SnapshotConnection(openResponse);
        connection.routingContext = routingContext;
        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.
     */
    static async openRemote(fileKey) {
        const routingContext = IModelRoutingContext.current || IModelRoutingContext.default;
        RpcManager.setIModel({ iModelId: "undefined", key: fileKey });
        const openResponse = await SnapshotIModelRpcInterface.getClientForRouting(routingContext.token).openRemote(fileKey);
        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 SnapshotIModelRpcInterface.getClientForRouting(this.routingContext.token).close(this.getRpcProps());
            }
        }
        finally {
            this._isClosed = true;
        }
    }
}
/** @public */
(function (IModelConnection) {
    /** The collection of loaded ModelState objects for an [[IModelConnection]]. */
    class Models {
        /** @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;
            this._loaded = new Map();
        }
        /** 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 IModelError(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 ? IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getModelProps(iModel.getRpcProps(), [...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 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 (err) {
                // 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);
                    if (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 (err) {
                // 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 iModel = this._iModel;
            return iModel.isOpen ? IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryModelRanges(iModel.getRpcProps(), [...Id64.toIdSet(modelIds)]) : [];
        }
        /** 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) {
            const iModel = this._iModel;
            if (!iModel.isOpen)
                return [];
            if (typeof modelIds === "string")
                modelIds = [modelIds];
            return IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryModelExtents(iModel.getRpcProps(), modelIds);
        }
        /** 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.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 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 {
        /** @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 ? IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getElementProps(this._iModel.getRpcProps(), [...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 ? 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 ? 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();
            for await (const queryRow of this._iModel.createQueryReader(ecsql, undefined, { rowFormat: 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 = Placement2d.fromJSON({ bbox, origin, angle: row.rotation });
                else
                    placement = 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 {
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
        }
        /** Loads all CodeSpec from the remote IModelDb. */
        async _loadAllCodeSpecs() {
            if (this._loaded)
                return;
            this._loaded = [];
            const codeSpecArray = await IModelReadRpcInterface.getClientForRouting(this._iModel.routingContext.token).getAllCodeSpecs(this._iModel.getRpcProps());
            for (const codeSpec of codeSpecArray) {
                this._loaded.push(CodeSpec.createFromJson(this._iModel, Id64.fromString(codeSpec.id), codeSpec.name, codeSpec.jsonProperties));
            }
        }
        /** 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 (!Id64.isValid(codeSpecId))
                throw new IModelError(IModelStatus.InvalidId, "Invalid codeSpecId", () => ({ codeSpecId }));
            await this._loadAllCodeSpecs(); // ensure all codeSpecs have been downloaded
            const found = this._loaded.find((codeSpec) => codeSpec.id === codeSpecId);
            if (!found)
                throw new IModelError(IModelStatus.NotFound, "CodeSpec not found");
            return found;
        }
        /** 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) {
            await this._loadAllCodeSpecs(); // ensure all codeSpecs have been downloaded
            const found = this._loaded.find((codeSpec) => codeSpec.name === name);
            if (!found)
                throw new IModelError(IModelStatus.NotFound, "CodeSpec not found");
            return found;
        }
    }
    IModelConnection.CodeSpecs = CodeSpecs;
    /** The collection of views for an [[IModelConnection]]. */
    class Views {
        /** @internal */
        constructor(_iModel) {
            this._iModel = _iModel;
        }
        get viewStoreWriter() {
            return this._writeViewStoreProxy ??= new Proxy(this, {
                get(views, methodName) {
                    const iModel = views._iModel;
                    return async (...args) => IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).callViewStore(iModel.getRpcProps(), ViewStoreRpc.version, true, methodName, ...args);
                },
            });
        }
        get viewsStoreReader() {
            return this._readViewStoreProxy ??= new Proxy(this, {
                get(views, methodName) {
                    const iModel = views._iModel;
                    return async (...args) => IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).callViewStore(iModel.getRpcProps(), 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.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 IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).queryElementProps(iModel.getRpcProps(), params);
            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. Create a ViewState to your own specifications.
         */
        async queryDefaultViewId() {
            const iModel = this._iModel;
            return iModel.isOpen ? IModelReadRpcInterface.getClientForRouting(iModel.routingContext.token).getDefaultViewId(iModel.getRpcProps()) : Id64.invalid;
        }
        /** Load a [[ViewState]] object from the specified [[ViewDefinition]] id. */
        async load(viewDefinitionId) {
            const options = {
                displayStyle: {
                    omitScheduleScriptElementIds: !IModelApp.tileAdmin.enableFrontendScheduleScripts,
                    compressExcludedElementIds: true,
                },
            };
            const viewProps = await 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 IModelError(IModelStatus.WrongClass, "Invalid ViewState class", () => viewProps);
            const viewState = ctor.createFromProps(viewProps, this._iModel);
            await viewState.load(); // loads models for ModelSelector
            return viewState;
        }
        /** Get a thumbnail for a view.
         * @param viewId The id of the view of the thumbnail.
         * @returns A Promise of the ThumbnailProps.
         * @throws "No content" error if invalid thumbnail.
         * @deprecated in 3.x use ViewStore apis
         */
        async getThumbnail(_viewId) {
            // eslint-disable-next-line deprecation/deprecation
            const val = await IModelReadRpcInterface.getClientForRouting(this._iModel.routingContext.token).getViewThumbnail(this._iModel.getRpcProps(), _viewId.toString());
            const intValues = new Uint32Array(val.buffer, 0, 4);
            if (intValues[1] !== ImageSourceFormat.Jpeg && intValues[1] !== ImageSourceFormat.Png)
                throw new NoContentError();
            return { format: intValues[1] === ImageSourceFormat.Jpeg ? "jpeg" : "png", width: intValues[2], height: intValues[3], image: new Uint8Array(val.buffer, 16, intValues[0]) };
        }
    }
    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 */
        constructor(iModel) {
            this.cache = new 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 || (IModelConnection = {}));
//# sourceMappingURL=IModelConnection.js.map