/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RpcInterface
 */
import { assert, BeDuration, BentleyStatus, CompressedId64Set, Id64, IModelStatus, Logger, } from "@itwin/core-bentley";
import { Code, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, NoContentError, RpcInterface, RpcManager, RpcPendingResponse, SyncMode, ViewStoreRpc, } from "@itwin/core-common";
import { BackendLoggerCategory } from "../BackendLoggerCategory";
import { SpatialCategory } from "../Category";
import { ConcurrentQuery } from "../ConcurrentQuery";
import { CustomViewState3dCreator } from "../CustomViewState3dCreator";
import { generateGeometrySummaries } from "../GeometrySummary";
import { PromiseMemoizer } from "../PromiseMemoizer";
import { RpcTrace } from "../rpc/tracing";
import { ViewStateHydrator } from "../ViewStateHydrator";
import { RpcBriefcaseUtility } from "./RpcBriefcaseUtility";
import { _nativeDb } from "../internal/Symbols";
class ViewStateRequestMemoizer extends PromiseMemoizer {
    _timeoutMs;
    static _instance;
    static async perform(props) {
        if (!this._instance)
            this._instance = new ViewStateRequestMemoizer();
        return this._instance.perform(props);
    }
    constructor() {
        const memoize = async (props) => {
            const db = await RpcBriefcaseUtility.findOpenIModel(props.accessToken, props.tokenProps);
            const viewCreator = new CustomViewState3dCreator(db);
            return viewCreator.getCustomViewState3dData(props.options);
        };
        const stringify = (props) => {
            const token = props.tokenProps;
            const modelIds = props.options.modelIds;
            return `${token.key}-${token.iTwinId}-${token.iModelId}-${token.changeset?.id}:${modelIds}`;
        };
        super(memoize, stringify);
        this._timeoutMs = 20 * 1000;
    }
    async perform(props) {
        const memo = this.memoize(props);
        // Rejections must be caught so that the memoization entry is deleted.
        await BeDuration.race(this._timeoutMs, memo.promise).catch(() => undefined);
        if (memo.isPending)
            throw new RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error
        this.deleteMemoized(props);
        if (memo.isFulfilled) {
            assert(undefined !== memo.result);
            return memo.result;
        }
        assert(memo.isRejected);
        throw memo.error;
    }
}
function currentActivity() {
    return RpcTrace.expectCurrentActivity;
}
async function getIModelForRpc(tokenProps) {
    return RpcBriefcaseUtility.findOpenIModel(RpcTrace.expectCurrentActivity.accessToken, tokenProps);
}
/** The backend implementation of IModelReadRpcInterface.
 * @internal
 */
export class IModelReadRpcImpl extends RpcInterface {
    static register() { RpcManager.registerImpl(IModelReadRpcInterface, IModelReadRpcImpl); }
    async getConnectionProps(tokenProps) {
        return RpcBriefcaseUtility.openWithTimeout(currentActivity(), tokenProps, SyncMode.FixedVersion);
    }
    async getCustomViewState3dData(tokenProps, options) {
        const accessToken = currentActivity().accessToken;
        return ViewStateRequestMemoizer.perform({ accessToken, tokenProps, options });
    }
    async hydrateViewState(tokenProps, options) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const viewHydrater = new ViewStateHydrator(iModelDb);
        return viewHydrater.getHydrateResponseProps(options);
    }
    async queryAllUsedSpatialSubCategories(tokenProps) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.queryAllUsedSpatialSubCategories();
    }
    async querySubCategories(tokenProps, compressedCategoryIds) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const decompressedIds = CompressedId64Set.decompressArray(compressedCategoryIds);
        return iModelDb.querySubCategories(decompressedIds);
    }
    async queryRows(tokenProps, request) {
        const iModelDb = await getIModelForRpc(tokenProps);
        if (iModelDb.isReadonly && request.usePrimaryConn === true) {
            Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request);
            request.usePrimaryConn = false;
        }
        return ConcurrentQuery.executeQueryRequest(iModelDb[_nativeDb], request);
    }
    async queryBlob(tokenProps, request) {
        const iModelDb = await getIModelForRpc(tokenProps);
        if (iModelDb.isReadonly && request.usePrimaryConn === true) {
            Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request);
            request.usePrimaryConn = false;
        }
        return ConcurrentQuery.executeBlobRequest(iModelDb[_nativeDb], request);
    }
    async queryModelRanges(tokenProps, modelIds) {
        const results = await this.queryModelExtents(tokenProps, modelIds);
        if (results.length === 1 && results[0].status !== IModelStatus.Success)
            throw new IModelError(results[0].status, "error querying model range");
        return results.filter((x) => x.status === IModelStatus.Success).map((x) => x.extents);
    }
    async queryModelExtents(tokenProps, modelIds) {
        const iModel = await getIModelForRpc(tokenProps);
        return iModel.models.queryExtents(modelIds);
    }
    async getModelProps(tokenProps, modelIdsList) {
        const modelIds = new Set(modelIdsList);
        const iModelDb = await getIModelForRpc(tokenProps);
        const modelJsonArray = [];
        for (const id of modelIds) {
            try {
                const modelProps = iModelDb.models.getModelProps(id);
                modelJsonArray.push(modelProps);
            }
            catch (error) {
                if (modelIds.size === 1)
                    throw error; // if they're asking for more than one model, don't throw on error.
            }
        }
        return modelJsonArray;
    }
    async queryModelProps(tokenProps, params) {
        const ids = await this.queryEntityIds(tokenProps, params);
        return this.getModelProps(tokenProps, [...ids]);
    }
    async getElementProps(tokenProps, elementIdsList) {
        const elementIds = new Set(elementIdsList);
        const iModelDb = await getIModelForRpc(tokenProps);
        const elementProps = [];
        for (const id of elementIds) {
            try {
                elementProps.push(iModelDb.elements.getElementProps({ id }));
            }
            catch (error) {
                if (elementIds.size === 1)
                    throw error; // if they're asking for more than one element, don't throw on error.
            }
        }
        return elementProps;
    }
    async loadElementProps(tokenProps, identifier, options) {
        const props = options ? { ...options } : {};
        if (typeof identifier === "string") {
            if (Id64.isId64(identifier))
                props.id = identifier;
            else
                props.federationGuid = identifier;
        }
        else {
            props.code = Code.fromJSON(identifier);
        }
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.elements.tryGetElementProps(props);
    }
    async getGeometrySummary(tokenProps, request) {
        const iModel = await getIModelForRpc(tokenProps);
        return generateGeometrySummaries(request, iModel);
    }
    async queryElementProps(tokenProps, params) {
        const ids = await this.queryEntityIds(tokenProps, params);
        const res = this.getElementProps(tokenProps, [...ids]);
        return res;
    }
    async queryEntityIds(tokenProps, params) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const res = iModelDb.queryEntityIds(params);
        return [...res];
    }
    async getClassHierarchy(tokenProps, classFullName) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const classArray = [];
        while (true) {
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            const classMetaData = iModelDb.getMetaData(classFullName);
            classArray.push(classFullName);
            if (!classMetaData.baseClasses || classMetaData.baseClasses.length === 0)
                break;
            classFullName = classMetaData.baseClasses[0];
        }
        return classArray;
    }
    async getAllCodeSpecs(tokenProps) {
        const codeSpecs = [];
        const iModelDb = await getIModelForRpc(tokenProps);
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        iModelDb.withPreparedStatement("SELECT ECInstanceId AS id, name, jsonProperties FROM BisCore.CodeSpec", (statement) => {
            for (const row of statement)
                codeSpecs.push({ id: row.id, name: row.name, jsonProperties: JSON.parse(row.jsonProperties) });
        });
        return codeSpecs;
    }
    async getViewStateData(tokenProps, viewDefinitionId, options) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.views.getViewStateProps(viewDefinitionId, options);
    }
    async readFontJson(tokenProps) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb[_nativeDb].readFontMap();
    }
    async requestSnap(tokenProps, sessionId, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.requestSnap(sessionId, props);
    }
    async cancelSnap(tokenProps, sessionId) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.cancelSnap(sessionId);
    }
    async getGeometryContainment(tokenProps, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.getGeometryContainment(props);
    }
    async getMassProperties(tokenProps, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.getMassProperties(props);
    }
    async getMassPropertiesPerCandidate(tokenProps, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const getSingleCandidateMassProperties = async (candidate) => {
            try {
                const massPropResults = [];
                for (const op of props.operations) {
                    const massProperties = await iModelDb.getMassProperties({ operation: op, candidates: [candidate] });
                    massPropResults.push(massProperties);
                }
                let singleCandidateResult = { status: BentleyStatus.ERROR, candidate }; // eslint-disable-line @typescript-eslint/no-deprecated
                if (massPropResults.some((r) => r.status !== BentleyStatus.ERROR)) {
                    singleCandidateResult.status = BentleyStatus.SUCCESS;
                    for (const r of massPropResults.filter((mpr) => mpr.status !== BentleyStatus.ERROR)) {
                        singleCandidateResult = { ...singleCandidateResult, ...r };
                    }
                }
                return singleCandidateResult;
            }
            catch {
                return { status: BentleyStatus.ERROR, candidate };
            }
        };
        const promises = []; // eslint-disable-line @typescript-eslint/no-deprecated
        for (const candidate of CompressedId64Set.iterable(props.candidates)) {
            promises.push(getSingleCandidateMassProperties(candidate));
        }
        return Promise.all(promises);
    }
    async getToolTipMessage(tokenProps, id) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const el = iModelDb.elements.getElement(id);
        return (el === undefined) ? [] : el.getToolTipMessage();
    }
    /** Send a view thumbnail to the frontend. This is a binary transfer with the metadata in a 16-byte prefix header.
     * @deprecated in 3.6.0 - might be removed in next major version. Use queryViewThumbnail instead
     */
    async getViewThumbnail(tokenProps, viewId) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const thumbnail = iModelDb.views.getThumbnail(viewId);
        if (undefined === thumbnail || 0 === thumbnail.image.length)
            throw new NoContentError();
        const val = new Uint8Array(thumbnail.image.length + 16); // allocate a new buffer 16 bytes larger than the image size
        new Uint32Array(val.buffer, 0, 4).set([thumbnail.image.length, thumbnail.format === "jpeg" ? ImageSourceFormat.Jpeg : ImageSourceFormat.Png, thumbnail.width, thumbnail.height]); // Put the metadata in the first 16 bytes.
        val.set(thumbnail.image, 16); // put the image data at offset 16 after metadata
        return val;
    }
    async getDefaultViewId(tokenProps) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const spec = { namespace: "dgn_View", name: "DefaultView" };
        const blob = iModelDb.queryFilePropertyBlob(spec);
        if (undefined === blob || 8 !== blob.length)
            return Id64.invalid;
        const view = new Uint32Array(blob.buffer);
        return Id64.fromUint32Pair(view[0], view[1]);
    }
    async getSpatialCategoryId(tokenProps, categoryName) {
        const iModelDb = await getIModelForRpc(tokenProps);
        const dictionary = iModelDb.models.getModel(IModel.dictionaryId);
        return SpatialCategory.queryCategoryIdByName(iModelDb, dictionary.id, categoryName);
    }
    async getIModelCoordinatesFromGeoCoordinates(tokenProps, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.getIModelCoordinatesFromGeoCoordinates(props);
    }
    async getGeoCoordinatesFromIModelCoordinates(tokenProps, props) {
        const iModelDb = await getIModelForRpc(tokenProps);
        return iModelDb.getGeoCoordinatesFromIModelCoordinates(props);
    }
    async queryTextureData(tokenProps, textureLoadProps) {
        const db = await getIModelForRpc(tokenProps);
        return db.queryTextureData(textureLoadProps);
    }
    async generateElementMeshes(tokenProps, props) {
        const db = await getIModelForRpc(tokenProps);
        return db[_nativeDb].generateElementMeshes(props);
    }
    /** @internal */
    async callViewStore(tokenProps, version, forWrite, methodName, ...args) {
        if (!RpcInterface.isVersionCompatible(ViewStoreRpc.version, version))
            throw new Error("ViewStoreRpc version mismatch");
        const db = await getIModelForRpc(tokenProps);
        const viewStore = await db.views.accessViewStore({ accessLevel: forWrite ? "write" : "read" });
        const access = viewStore[forWrite ? "writeLocker" : "reader"];
        const func = access[methodName];
        if (typeof func !== "function")
            throw new IModelError(IModelStatus.FunctionNotFound, `Illegal ViewStore RPC call "${methodName}"`);
        return func.call(access, ...args);
    }
}
//# sourceMappingURL=IModelReadRpcImpl.js.map