import { __awaiter, __extends, __generator, __spreadArray } from "tslib";
import { getThrowSolanaErrorResponseTransformer } from "@solana/rpc-transformers";
import { is, object, optional, string, tuple } from "superstruct";
import { SolanaClusterRpcFactory } from "./baseRpcFactories";
import { CacheType } from "../utils";
import { jsonReplacerWithBigInts, jsonReviverWithBigInts } from "../../utils";
import { RetrySolanaRpcFactory } from "./retryRpcFactory";
var CachedSolanaRpcFactory = /** @class */ (function (_super) {
    __extends(CachedSolanaRpcFactory, _super);
    function CachedSolanaRpcFactory(providerCacheNamespace, redisClient) {
        var _this = this;
        var retryConstructorParams = [];
        for (var _i = 2; _i < arguments.length; _i++) {
            retryConstructorParams[_i - 2] = arguments[_i];
        }
        // SolanaClusterRpcFactory shares the last two constructor parameters with RetryRpcFactory.
        var superParams = retryConstructorParams.slice(-2);
        _this = _super.apply(this, superParams) || this;
        _this.redisClient = redisClient;
        // Create the rate limited transport and RPC client.
        var retryRpcFactory = new (RetrySolanaRpcFactory.bind.apply(RetrySolanaRpcFactory, __spreadArray([void 0], retryConstructorParams, false)))();
        _this.retryTransport = retryRpcFactory.createTransport();
        _this.retryRpcClient = retryRpcFactory.createRpcClient();
        // Pre-compute as much of the redis key as possible.
        var cachePrefix = "".concat(providerCacheNamespace, ",").concat(new URL(_this.clusterUrl).hostname, ",").concat(_this.chainId);
        _this.getTransactionCachePrefix = "".concat(cachePrefix, ":getTransaction,");
        return _this;
    }
    CachedSolanaRpcFactory.prototype.createTransport = function () {
        var _this = this;
        return function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            return __awaiter(_this, void 0, void 0, function () {
                var _a, method, params, cacheType, redisKey, redisResult;
                var _b;
                return __generator(this, function (_c) {
                    switch (_c.label) {
                        case 0:
                            _a = args[0].payload, method = _a.method, params = _a.params;
                            cacheType = this.redisClient ? this.cacheType(method) : CacheType.NONE;
                            if (cacheType === CacheType.NONE) {
                                return [2 /*return*/, this.retryTransport.apply(this, args)];
                            }
                            redisKey = this.buildRedisKey(method, params);
                            return [4 /*yield*/, ((_b = this.redisClient) === null || _b === void 0 ? void 0 : _b.get(redisKey))];
                        case 1:
                            redisResult = _c.sent();
                            // If cache has the result, parse the json and return it.
                            if (redisResult) {
                                return [2 /*return*/, JSON.parse(redisResult, jsonReviverWithBigInts)];
                            }
                            // Cache does not have the result. Query it directly and cache it if finalized.
                            return [2 /*return*/, this.requestAndCacheFinalized.apply(this, args)];
                    }
                });
            });
        };
    };
    CachedSolanaRpcFactory.prototype.requestAndCacheFinalized = function () {
        var _a, _b;
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return __awaiter(this, void 0, void 0, function () {
            var _c, method, params, getSignatureStatusesResponse, error_1, getTransactionResponse, redisKey;
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        _c = args[0].payload, method = _c.method, params = _c.params;
                        // Only handles getTransaction right now.
                        if (method !== "getTransaction")
                            return [2 /*return*/, this.retryTransport.apply(this, args)];
                        // Do not throw if params are not valid, just skip caching and pass through to the underlying transport.
                        if (!this.isGetTransactionParams(params))
                            return [2 /*return*/, this.retryTransport.apply(this, args)];
                        _d.label = 1;
                    case 1:
                        _d.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.retryRpcClient
                                .getSignatureStatuses([params[0]], {
                                searchTransactionHistory: true,
                            })
                                .send()];
                    case 2:
                        getSignatureStatusesResponse = _d.sent();
                        if (((_a = getSignatureStatusesResponse.value[0]) === null || _a === void 0 ? void 0 : _a.confirmationStatus) !== "finalized") {
                            return [2 /*return*/, this.retryTransport.apply(this, args)];
                        }
                        return [3 /*break*/, 4];
                    case 3:
                        error_1 = _d.sent();
                        return [2 /*return*/, this.retryTransport.apply(this, args)];
                    case 4: return [4 /*yield*/, this.retryTransport.apply(this, args)];
                    case 5:
                        getTransactionResponse = _d.sent();
                        // Do not cache JSON-RPC error responses, let them pass through for the RPC client to handle.
                        try {
                            getThrowSolanaErrorResponseTransformer()(getTransactionResponse, { methodName: method, params: params });
                        }
                        catch (_e) {
                            return [2 /*return*/, getTransactionResponse];
                        }
                        redisKey = this.buildRedisKey(method, params);
                        return [4 /*yield*/, ((_b = this.redisClient) === null || _b === void 0 ? void 0 : _b.set(redisKey, JSON.stringify(getTransactionResponse, jsonReplacerWithBigInts), Number.POSITIVE_INFINITY))];
                    case 6:
                        _d.sent();
                        return [2 /*return*/, getTransactionResponse];
                }
            });
        });
    };
    CachedSolanaRpcFactory.prototype.buildRedisKey = function (method, params) {
        // Only handles getTransaction right now.
        switch (method) {
            case "getTransaction":
                return this.getTransactionCachePrefix + JSON.stringify(params, jsonReplacerWithBigInts);
            default:
                throw new Error("CachedSolanaRpcFactory::buildRedisKey: invalid JSON-RPC method ".concat(method));
        }
    };
    CachedSolanaRpcFactory.prototype.cacheType = function (method) {
        // Today, we only cache getTransaction.
        if (method === "getTransaction") {
            // We only store finalized transactions in the cache, hence TTL is not required.
            return CacheType.NO_TTL;
        }
        else {
            return CacheType.NONE;
        }
    };
    CachedSolanaRpcFactory.prototype.isGetTransactionParams = function (params) {
        return is(params, tuple([
            string(),
            optional(object()), // We use only the tx signature to get its commitment, but pass through the options as is.
        ]));
    };
    return CachedSolanaRpcFactory;
}(SolanaClusterRpcFactory));
export { CachedSolanaRpcFactory };
//# sourceMappingURL=cachedRpcFactory.js.map