/*---------------------------------------------------------------------------------------------
* 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
 */
var _a;
import { BentleyError, BentleyStatus, IModelStatus, Logger, RpcInterfaceStatus, StatusCategory, Tracing } from "@itwin/core-bentley";
import { CommonLoggerCategory } from "../../CommonLoggerCategory";
import { IModelError } from "../../IModelError";
import { RpcInterface } from "../../RpcInterface";
import { RpcConfiguration } from "./RpcConfiguration";
import { RpcProtocolEvent, RpcRequestStatus } from "./RpcConstants";
import { RpcControlChannel, RpcNotFoundResponse, RpcPendingResponse } from "./RpcControl";
import { RpcMarshaling } from "./RpcMarshaling";
import { RpcOperation } from "./RpcOperation";
import { RpcProtocol, RpcProtocolVersion } from "./RpcProtocol";
import { CURRENT_INVOCATION, RpcRegistry } from "./RpcRegistry";
/** An RPC operation invocation in response to a request.
 * @internal
 */
export class RpcInvocation {
    /** The status for this request. */
    get status() {
        return this._threw ? RpcRequestStatus.Rejected :
            this._pending ? RpcRequestStatus.Pending :
                this._notFound ? RpcRequestStatus.NotFound :
                    this._noContent ? RpcRequestStatus.NoContent :
                        RpcRequestStatus.Resolved;
    }
    /** The elapsed time for this invocation. */
    get elapsed() {
        return this._timeOut - this._timeIn;
    }
    /**
     * The invocation for the current RPC operation.
     * @note The return value of this function is only reliable in an RPC impl class member function where program control was received from the RpcInvocation constructor function.
     */
    static current(rpcImpl) {
        return rpcImpl[CURRENT_INVOCATION];
    }
    /** Constructs an invocation. */
    constructor(protocol, request) {
        this._threw = false;
        this._pending = false;
        this._notFound = false;
        this._noContent = false;
        this._timeIn = 0;
        this._timeOut = 0;
        /** The operation of the request. */
        this.operation = undefined;
        this._timeIn = new Date().getTime();
        this.protocol = protocol;
        this.request = request;
        try {
            try {
                this.operation = RpcOperation.lookup(this.request.operation.interfaceDefinition, this.request.operation.operationName);
                const backend = this.operation.interfaceVersion;
                const frontend = this.request.operation.interfaceVersion;
                if (!RpcInterface.isVersionCompatible(backend, frontend)) {
                    throw new IModelError(RpcInterfaceStatus.IncompatibleVersion, `Backend version ${backend} does not match frontend version ${frontend} for RPC interface ${this.operation.operationName}.`);
                }
            }
            catch (error) {
                if (this.handleUnknownOperation(error)) {
                    this.operation = RpcOperation.lookup(this.request.operation.interfaceDefinition, this.request.operation.operationName);
                }
                else {
                    throw error;
                }
            }
            this.result = this.resolve();
        }
        catch (error) {
            this.result = this.reject(error);
        }
        this.fulfillment = this.result.then(async (value) => this._threw ? this.fulfillRejected(value) : this.fulfillResolved(value), async (reason) => this.fulfillRejected(reason));
    }
    handleUnknownOperation(error) {
        RpcControlChannel.ensureInitialized();
        return this.protocol.configuration.controlChannel.handleUnknownOperation(this, error);
    }
    static sanitizeForLog(activity) {
        /* eslint-disable @typescript-eslint/naming-convention */
        return activity ? {
            ActivityId: activity.activityId, SessionId: activity.sessionId, ApplicationId: activity.applicationId, ApplicationVersion: activity.applicationVersion, rpcMethod: activity.rpcMethod,
        } : undefined;
        /* eslint-enable @typescript-eslint/naming-convention */
    }
    async resolve() {
        const request = this.request;
        const activity = {
            activityId: request.id,
            applicationId: request.applicationId,
            applicationVersion: request.applicationVersion,
            sessionId: request.sessionId,
            user: request.user,
            accessToken: request.authorization,
            rpcMethod: request.operation.operationName,
        };
        try {
            this.protocol.events.raiseEvent(RpcProtocolEvent.RequestReceived, this);
            const parameters = request.parametersOverride || RpcMarshaling.deserialize(this.protocol, request.parameters);
            this.applyPolicies(parameters);
            const impl = RpcRegistry.instance.getImplForInterface(this.operation.interfaceDefinition);
            impl[CURRENT_INVOCATION] = this;
            const op = this.lookupOperationFunction(impl);
            return await _a.runActivity(activity, async () => op.call(impl, ...parameters)
                .catch(async (error) => {
                // this catch block is intentionally placed inside `runActivity` to attach the right logging metadata and use the correct openTelemetry span.
                if (!(error instanceof RpcPendingResponse)) {
                    Logger.logError(CommonLoggerCategory.RpcInterfaceBackend, "Error in RPC operation", { error: BentleyError.getErrorProps(error) });
                    Tracing.recordException(error);
                }
                throw error;
            }));
        }
        catch (error) {
            return this.reject(error);
        }
    }
    applyPolicies(parameters) {
        if (!parameters || !Array.isArray(parameters))
            return;
        for (let i = 0; i !== parameters.length; ++i) {
            const parameter = parameters[i];
            const isToken = typeof (parameter) === "object" && parameter !== null && parameter.hasOwnProperty("iModelId") && parameter.hasOwnProperty("iTwinId");
            if (isToken && this.protocol.checkToken && !this.operation.policy.allowTokenMismatch) {
                const inflated = this.protocol.inflateToken(parameter, this.request);
                parameters[i] = inflated;
                if (!_a.compareTokens(parameter, inflated)) {
                    if (RpcConfiguration.throwOnTokenMismatch) {
                        throw new IModelError(BentleyStatus.ERROR, "IModelRpcProps mismatch detected for this request.");
                    }
                    else {
                        Logger.logWarning(CommonLoggerCategory.RpcInterfaceBackend, "IModelRpcProps mismatch detected for this request.");
                    }
                }
            }
        }
    }
    static compareTokens(a, b) {
        return a.key === b.key &&
            a.iTwinId === b.iTwinId &&
            a.iModelId === b.iModelId &&
            (undefined === a.changeset || (a.changeset.id === b.changeset?.id));
    }
    async reject(error) {
        this._threw = true;
        return error;
    }
    async fulfillResolved(value) {
        this._timeOut = new Date().getTime();
        this.protocol.events.raiseEvent(RpcProtocolEvent.BackendResponseCreated, this);
        const result = RpcMarshaling.serialize(this.protocol, value);
        return this.fulfill(result, value);
    }
    async fulfillRejected(reason) {
        this._timeOut = new Date().getTime();
        if (!RpcConfiguration.developmentMode)
            reason.stack = undefined;
        const result = RpcMarshaling.serialize(this.protocol, reason);
        if (reason instanceof RpcPendingResponse) {
            this._pending = true;
            this._threw = false;
            result.objects = reason.message;
            this.protocol.events.raiseEvent(RpcProtocolEvent.BackendReportedPending, this);
        }
        else if (this.supportsNoContent() && reason?.errorNumber === IModelStatus.NoContent) {
            this._noContent = true;
            this._threw = false;
            this.protocol.events.raiseEvent(RpcProtocolEvent.BackendReportedNoContent, this);
        }
        else if (reason instanceof RpcNotFoundResponse) {
            this._notFound = true;
            this._threw = false;
            this.protocol.events.raiseEvent(RpcProtocolEvent.BackendReportedNotFound, this);
        }
        else {
            this._threw = true;
            this.protocol.events.raiseEvent(RpcProtocolEvent.BackendErrorOccurred, this);
        }
        return this.fulfill(result, reason);
    }
    supportsNoContent() {
        if (!this.request.protocolVersion) {
            return false;
        }
        return RpcProtocol.protocolVersion >= RpcProtocolVersion.IntroducedNoContent && this.request.protocolVersion >= RpcProtocolVersion.IntroducedNoContent;
    }
    supportsStatusCategory() {
        if (!this.request.protocolVersion) {
            return false;
        }
        if (!this.protocol.supportsStatusCategory) {
            return false;
        }
        return RpcProtocol.protocolVersion >= RpcProtocolVersion.IntroducedStatusCategory && this.request.protocolVersion >= RpcProtocolVersion.IntroducedStatusCategory;
    }
    fulfill(result, rawResult) {
        const fulfillment = {
            result,
            rawResult,
            status: this.protocol.getCode(this.status),
            id: this.request.id,
            interfaceName: (typeof (this.operation) === "undefined") ? "" : this.operation.interfaceDefinition.interfaceName,
            allowCompression: this.operation ? this.operation.policy.allowResponseCompression : true,
        };
        this.transformResponseStatus(fulfillment, rawResult);
        try {
            const impl = RpcRegistry.instance.getImplForInterface(this.operation.interfaceDefinition);
            if (impl[CURRENT_INVOCATION] === this) {
                impl[CURRENT_INVOCATION] = undefined;
            }
        }
        catch { }
        return fulfillment;
    }
    lookupOperationFunction(implementation) {
        const func = implementation[this.operation.operationName];
        if (!func || typeof (func) !== "function")
            throw new IModelError(BentleyStatus.ERROR, `RPC interface class "${implementation.constructor.name}" does not implement operation "${this.operation.operationName}".`);
        return func;
    }
    transformResponseStatus(fulfillment, rawResult) {
        if (!this.supportsStatusCategory()) {
            return;
        }
        let managedStatus;
        if (this._pending) {
            managedStatus = "pending";
        }
        else if (this._notFound) {
            managedStatus = "notFound";
        }
        else if (this._noContent) {
            managedStatus = "noContent";
        }
        if (managedStatus) {
            const responseValue = fulfillment.result.objects;
            const status = { iTwinRpcCoreResponse: true, managedStatus, responseValue };
            fulfillment.result.objects = JSON.stringify(status);
            status.responseValue = rawResult; // for ipc case
            fulfillment.rawResult = status;
        }
        if (rawResult instanceof Error) {
            fulfillment.status = StatusCategory.for(rawResult).code;
        }
    }
}
_a = RpcInvocation;
RpcInvocation.runActivity = async (_activity, fn) => fn();
//# sourceMappingURL=RpcInvocation.js.map