"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.CheckpointConnection = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const FrontendLoggerCategory_1 = require("./common/FrontendLoggerCategory");
const IModelApp_1 = require("./IModelApp");
const IModelConnection_1 = require("./IModelConnection");
const IModelRoutingContext_1 = require("./IModelRoutingContext");
const IpcApp_1 = require("./IpcApp");
const loggerCategory = FrontendLoggerCategory_1.FrontendLoggerCategory.IModelConnection;
/**
 * An IModelConnection to a Checkpoint of an iModel.
 * @see [CheckpointConnection]($docs/learning/frontend/IModelConnection)
 * @public
 */
class CheckpointConnection extends IModelConnection_1.IModelConnection {
    _fromIpc;
    /** The Guid that identifies the iTwin that owns this iModel. */
    get iTwinId() { return super.iTwinId ?? core_bentley_1.Guid.empty; }
    /** The Guid that identifies this iModel. */
    get iModelId() { return super.iModelId ?? core_bentley_1.Guid.empty; }
    /** Returns `true` if [[close]] has already been called. */
    get isClosed() { return this._isClosed ? true : false; }
    _isClosed;
    constructor(props, fromIpc) {
        super(props);
        this._fromIpc = fromIpc;
    }
    /** Type guard for instanceof [[CheckpointConnection]] */
    isCheckpointConnection() { return true; }
    /**
     * Open a readonly IModelConnection to a Checkpoint of an iModel.
     */
    static async openRemote(iTwinId, iModelId, version = core_common_1.IModelVersion.latest()) {
        if (undefined === IModelApp_1.IModelApp.hubAccess)
            throw new Error("Missing an implementation of IModelApp.hubAccess");
        const accessToken = await IModelApp_1.IModelApp.getAccessToken();
        const changeset = await IModelApp_1.IModelApp.hubAccess.getChangesetFromVersion({ accessToken, iModelId, version });
        let connection;
        const iModelProps = { iTwinId, iModelId, changeset };
        if (IpcApp_1.IpcApp.isValid) {
            connection = new this(await IpcApp_1.IpcApp.appFunctionIpc.openCheckpoint(iModelProps), true);
        }
        else {
            const routingContext = IModelRoutingContext_1.IModelRoutingContext.current || IModelRoutingContext_1.IModelRoutingContext.default;
            connection = new this(await this.callOpen(iModelProps, routingContext), false);
            core_common_1.RpcManager.setIModel(connection);
            connection.routingContext = routingContext;
            core_common_1.RpcRequest.notFoundHandlers.addListener(connection._reopenConnectionHandler);
        }
        IModelConnection_1.IModelConnection.onOpen.raiseEvent(connection);
        return connection;
    }
    static async callOpen(iModelToken, routingContext) {
        // Try opening the iModel repeatedly accommodating any pending responses from the backend.
        // Waits for an increasing amount of time (but within a range) before checking on the pending request again.
        const connectionRetryIntervalRange = { min: 100, max: 5000 }; // in milliseconds
        let connectionRetryInterval = Math.min(connectionRetryIntervalRange.min, IModelConnection_1.IModelConnection.connectionTimeout);
        const openForReadOperation = core_common_1.RpcOperation.lookup(core_common_1.IModelReadRpcInterface, "getConnectionProps");
        if (!openForReadOperation)
            throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "IModelReadRpcInterface.getConnectionProps() is not available");
        openForReadOperation.policy.retryInterval = () => connectionRetryInterval;
        core_bentley_1.Logger.logTrace(loggerCategory, `IModelConnection.open`, iModelToken);
        const startTime = Date.now();
        const removeListener = core_common_1.RpcRequest.events.addListener((type, request) => {
            if (type !== core_common_1.RpcRequestEvent.PendingUpdateReceived) // eslint-disable-line @typescript-eslint/no-deprecated
                return;
            if (!(openForReadOperation && request.operation === openForReadOperation))
                return;
            core_bentley_1.Logger.logTrace(loggerCategory, "Received pending open notification in IModelConnection.open", iModelToken);
            const connectionTimeElapsed = Date.now() - startTime;
            if (connectionTimeElapsed > IModelConnection_1.IModelConnection.connectionTimeout) {
                core_bentley_1.Logger.logError(loggerCategory, `Timed out opening connection in IModelConnection.open (took longer than ${IModelConnection_1.IModelConnection.connectionTimeout} milliseconds)`, iModelToken);
                throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Opening a connection was timed out"); // NEEDS_WORK: More specific error status
            }
            connectionRetryInterval = Math.min(connectionRetryIntervalRange.max, connectionRetryInterval * 2, IModelConnection_1.IModelConnection.connectionTimeout - connectionTimeElapsed);
            if (request.retryInterval !== connectionRetryInterval) {
                request.retryInterval = connectionRetryInterval;
                core_bentley_1.Logger.logTrace(loggerCategory, `Adjusted open connection retry interval to ${request.retryInterval} milliseconds in IModelConnection.open`, iModelToken);
            }
        });
        const openPromise = core_common_1.IModelReadRpcInterface.getClientForRouting(routingContext.token).getConnectionProps(iModelToken);
        let openResponse;
        try {
            openResponse = await openPromise;
        }
        finally {
            core_bentley_1.Logger.logTrace(loggerCategory, "Completed open request in IModelConnection.open", iModelToken);
            removeListener();
        }
        return openResponse;
    }
    _reopenConnectionHandler = async (request, response, resubmit, reject) => {
        if (!response.hasOwnProperty("isIModelNotFoundResponse"))
            reject();
        const iModelRpcProps = request.parameters[0];
        if (this._fileKey !== iModelRpcProps.key)
            reject(); // The handler is called for a different connection than this
        core_bentley_1.Logger.logTrace(loggerCategory, "Attempting to reopen connection", () => iModelRpcProps);
        try {
            const openResponse = await CheckpointConnection.callOpen(iModelRpcProps, this.routingContext);
            // The new/reopened connection may have a new rpcKey and/or changesetId, but the other IModelRpcTokenProps should be the same
            this._fileKey = openResponse.key;
            this.changeset = (0, core_bentley_1.expectDefined)(openResponse.changeset);
        }
        catch (error) {
            reject(core_bentley_1.BentleyError.getErrorMessage(error));
        }
        finally {
        }
        core_bentley_1.Logger.logTrace(loggerCategory, "Resubmitting original request after reopening connection", iModelRpcProps);
        request.parameters[0] = this.getRpcProps(); // Modify the token of the original request before resubmitting it.
        resubmit();
    };
    /** Close this CheckpointConnection */
    async close() {
        if (this.isClosed)
            return;
        this.beforeClose();
        if (this._fromIpc)
            await IpcApp_1.IpcApp.appFunctionIpc.closeIModel(this._fileKey);
        else
            core_common_1.RpcRequest.notFoundHandlers.removeListener(this._reopenConnectionHandler);
        this._isClosed = true;
    }
}
exports.CheckpointConnection = CheckpointConnection;
//# sourceMappingURL=CheckpointConnection.js.map