import { __assign, __awaiter, __extends, __generator, __spreadArray } from "tslib";
import { queue } from "async";
import winston from "winston";
import { SolanaClusterRpcFactory } from "./baseRpcFactories";
import { SolanaDefaultRpcFactory } from "./defaultRpcFactory";
import { getOriginFromURL } from "../../utils";
// This factory is a very small addition to the SolanaDefaultRpcFactory that ensures that no more than maxConcurrency
// requests are ever in flight. It uses the async/queue library to manage this.
var RateLimitedSolanaRpcFactory = /** @class */ (function (_super) {
    __extends(RateLimitedSolanaRpcFactory, _super);
    // Takes the same arguments as the SolanaDefaultRpcFactory, but it has an additional parameters to control
    // concurrency and logging at the beginning of the list.
    function RateLimitedSolanaRpcFactory(maxConcurrency, pctRpcCallsLogged, logger) {
        if (logger === void 0) { logger = winston.createLogger({
            transports: [new winston.transports.Console()],
        }); }
        var defaultConstructorParams = [];
        for (var _i = 3; _i < arguments.length; _i++) {
            defaultConstructorParams[_i - 3] = arguments[_i];
        }
        var _this = _super.apply(this, defaultConstructorParams) || this;
        _this.pctRpcCallsLogged = pctRpcCallsLogged;
        _this.logger = logger;
        _this.defaultTransport = new (SolanaDefaultRpcFactory.bind.apply(SolanaDefaultRpcFactory, __spreadArray([void 0], defaultConstructorParams, false)))().createTransport();
        // This sets up the queue. Each task is executed by forwarding the RPC request to the underlying base transport.
        // This queue sends out requests concurrently, but stops once the concurrency limit is reached. The maxConcurrency
        // is configured here.
        _this.queue = queue(function (_a, callback) {
            var rpcArgs = _a.rpcArgs, resolve = _a.resolve, reject = _a.reject;
            return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0: return [4 /*yield*/, this.wrapSendWithLog.apply(this, rpcArgs).then(resolve)
                                .catch(reject)];
                        case 1:
                            _b.sent();
                            // we need this for the queue to know that the task is done
                            // @see: https://caolan.github.io/async/v3/global.html
                            callback();
                            return [2 /*return*/];
                    }
                });
            });
        }, maxConcurrency);
        return _this;
    }
    RateLimitedSolanaRpcFactory.prototype.wrapSendWithLog = function () {
        var rpcArgs = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            rpcArgs[_i] = arguments[_i];
        }
        return __awaiter(this, void 0, void 0, function () {
            var payload, loggerArgs, startTime, result, elapsedTimeS, error_1, elapsedTimeS;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!(this.pctRpcCallsLogged <= 0 || Math.random() > this.pctRpcCallsLogged / 100)) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.defaultTransport.apply(this, rpcArgs)];
                    case 1: 
                    // Non sample path: no logging or timing, just issue the request.
                    return [2 /*return*/, _a.sent()];
                    case 2:
                        payload = rpcArgs[0].payload;
                        loggerArgs = {
                            at: "SolanaProviderUtils",
                            message: "Solana provider response sample",
                            provider: getOriginFromURL(this.clusterUrl),
                            method: payload.method,
                            params: payload.params,
                            chainId: this.chainId,
                            datadog: true,
                        };
                        startTime = performance.now();
                        _a.label = 3;
                    case 3:
                        _a.trys.push([3, 5, , 6]);
                        return [4 /*yield*/, this.defaultTransport.apply(this, rpcArgs)];
                    case 4:
                        result = _a.sent();
                        elapsedTimeS = (performance.now() - startTime) / 1000;
                        this.logger.debug(__assign(__assign({}, loggerArgs), { success: true, timeElapsed: elapsedTimeS }));
                        return [2 /*return*/, result];
                    case 5:
                        error_1 = _a.sent();
                        elapsedTimeS = (performance.now() - startTime) / 1000;
                        this.logger.debug(__assign(__assign({}, loggerArgs), { success: false, timeElapsed: elapsedTimeS }));
                        throw error_1;
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    RateLimitedSolanaRpcFactory.prototype.createTransport = function () {
        var _this = this;
        return function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            // This simply creates a promise and adds the arguments and resolve and reject handlers to the task.
            return new Promise(function (resolve, reject) {
                var task = {
                    rpcArgs: args,
                    resolve: resolve,
                    reject: reject,
                };
                // We didn't previously wait for this push so we can emulate
                // the same behavior with the `void` keyword.
                void _this.queue.push(task);
            });
        };
    };
    return RateLimitedSolanaRpcFactory;
}(SolanaClusterRpcFactory));
export { RateLimitedSolanaRpcFactory };
//# sourceMappingURL=rateLimitedRpcFactory.js.map