import { __awaiter, __extends, __generator, __spreadArray } from "tslib";
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR } from "@solana/kit";
import { getThrowSolanaErrorResponseTransformer } from "@solana/rpc-transformers";
import { SolanaClusterRpcFactory } from "./baseRpcFactories";
import { RateLimitedSolanaRpcFactory } from "./rateLimitedRpcFactory";
import { isSolanaError } from "../../arch/svm";
import { delay } from "../../utils";
import { getOriginFromURL } from "../../utils/NetworkUtils";
// This factory adds retry logic on top of the RateLimitedSolanaRpcFactory.
// It follows the same composition pattern as other factories in this module.
var RetrySolanaRpcFactory = /** @class */ (function (_super) {
    __extends(RetrySolanaRpcFactory, _super);
    function RetrySolanaRpcFactory(retries, retryDelaySeconds) {
        var rateLimitedConstructorParams = [];
        for (var _i = 2; _i < arguments.length; _i++) {
            rateLimitedConstructorParams[_i - 2] = arguments[_i];
        }
        var _this = this;
        // SolanaClusterRpcFactory shares the last two constructor parameters with RateLimitedSolanaRpcFactory.
        var superParams = rateLimitedConstructorParams.slice(-2);
        _this = _super.apply(this, superParams) || this;
        _this.retries = retries;
        _this.retryDelaySeconds = retryDelaySeconds;
        // Validate retry configuration
        if (_this.retries < 0 || !Number.isInteger(_this.retries)) {
            throw new Error("retries cannot be < 0 and must be an integer. Currently set to ".concat(_this.retries));
        }
        if (_this.retryDelaySeconds < 0) {
            throw new Error("retryDelaySeconds cannot be < 0. Currently set to ".concat(_this.retryDelaySeconds));
        }
        // Create the rate limited transport.
        var rateLimitedRpcFactory = new (RateLimitedSolanaRpcFactory.bind.apply(RateLimitedSolanaRpcFactory, __spreadArray([void 0], rateLimitedConstructorParams, false)))();
        _this.rateLimitedTransport = rateLimitedRpcFactory.createTransport();
        _this.logger = rateLimitedRpcFactory.logger;
        return _this;
    }
    RetrySolanaRpcFactory.prototype.createTransport = function () {
        var _this = this;
        return function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            return _this._tryCall(function () { return _this.rateLimitedTransport.apply(_this, args); }, args);
        };
    };
    /**
     * Retry wrapper for transport calls with Solana-specific error handling.
     * @param transportCall Function that makes the transport call
     * @param args Original transport arguments for logging
     * @returns Promise that resolves to the transport response
     */
    RetrySolanaRpcFactory.prototype._tryCall = function (transportCall, args) {
        return __awaiter(this, void 0, void 0, function () {
            var _a, method, params, retryAttempt, response, error_1, retryDelaySeconds, exponentialBackoff, jitter, delayS;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _a = args[0].payload, method = _a.method, params = _a.params;
                        retryAttempt = 0;
                        _b.label = 1;
                    case 1:
                        if (!true) return [3 /*break*/, 7];
                        _b.label = 2;
                    case 2:
                        _b.trys.push([2, 4, , 6]);
                        return [4 /*yield*/, transportCall()];
                    case 3:
                        response = _b.sent();
                        // Make sure SolanaErrors get thrown
                        getThrowSolanaErrorResponseTransformer()(response, { methodName: method, params: params });
                        return [2 /*return*/, response];
                    case 4:
                        error_1 = _b.sent();
                        if (retryAttempt++ >= this.retries || this.shouldFailImmediate(method, error_1)) {
                            throw error_1;
                        }
                        retryDelaySeconds = this.retryDelaySeconds;
                        exponentialBackoff = retryDelaySeconds * Math.pow(2, retryAttempt - 1);
                        jitter = 1 + 2 * Math.random();
                        delayS = this.isRateLimitResponse(error_1) ? exponentialBackoff + jitter : retryDelaySeconds;
                        // Log retry attempt if logger is available
                        this.logger.debug({
                            at: "RetryRpcFactory",
                            message: "Retrying Solana RPC call",
                            provider: getOriginFromURL(this.clusterUrl),
                            method: method,
                            retryAttempt: retryAttempt,
                            retryDelaySeconds: delayS,
                            error: error_1 === null || error_1 === void 0 ? void 0 : error_1.toString(),
                        });
                        return [4 /*yield*/, delay(delayS)];
                    case 5:
                        _b.sent();
                        return [3 /*break*/, 6];
                    case 6: return [3 /*break*/, 1];
                    case 7: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Determine whether a Solana RPC error indicates an unrecoverable error that should not be retried.
     * @param method RPC method name
     * @param error Error object from the RPC call
     * @returns True if the request should be aborted immediately, otherwise false
     */
    RetrySolanaRpcFactory.prototype.shouldFailImmediate = function (method, error) {
        if (!isSolanaError(error)) {
            return false;
        }
        // const { __code: code } = error.context;
        switch (method) {
            default:
                return false;
        }
    };
    /**
     * Identify whether an error thrown was the result of an RPC provider 429 response.
     * @param error Error object from the RPC query.
     * @returns True if the response was a 429 rate-limit response.
     */
    RetrySolanaRpcFactory.prototype.isRateLimitResponse = function (error) {
        if (!isSolanaError(error)) {
            return false;
        }
        var code = error.context.__code;
        return code === SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR && error.context.statusCode === 429;
    };
    return RetrySolanaRpcFactory;
}(SolanaClusterRpcFactory));
export { RetrySolanaRpcFactory };
//# sourceMappingURL=retryRpcFactory.js.map