import { __assign, __awaiter, __extends, __generator, __spreadArray } from "tslib";
import { SVMEventNames, unwrapEventData, getFillDeadline, getTimestampForSlot, getStatePda, SvmCpiEventsClient, findDeposit, relayFillStatus, fillStatusArray, } from "../../arch/svm";
import { InvalidFill, getNetworkName, isDefined, sortEventsAscendingInPlace, SvmAddress, } from "../../utils";
import { isUpdateFailureReason } from "../BaseAbstractClient";
import { knownEventNames, SpokePoolClient } from "./SpokePoolClient";
import { SVM_SPOKE_POOL_CLIENT_TYPE } from "./types";
/**
 * SvmSpokePoolClient is a client for the SVM SpokePool program. It extends the base SpokePoolClient
 * and implements the abstract methods required for interacting with an SVM Spoke Pool.
 */
var SVMSpokePoolClient = /** @class */ (function (_super) {
    __extends(SVMSpokePoolClient, _super);
    /**
     * Note: Strongly prefer to use the async create() method to instantiate.
     */
    function SVMSpokePoolClient(logger, hubPoolClient, chainId, deploymentSlot, // Using slot instead of block number for SVM
    eventSearchConfig, svmEventsClient, programId, statePda) {
        var _this = 
        // Convert deploymentSlot to number for base class, might need refinement
        _super.call(this, logger, hubPoolClient, chainId, Number(deploymentSlot), eventSearchConfig) || this;
        _this.svmEventsClient = svmEventsClient;
        _this.programId = programId;
        _this.statePda = statePda;
        _this.type = SVM_SPOKE_POOL_CLIENT_TYPE;
        _this.spokePoolAddress = SvmAddress.from(programId.toString());
        return _this;
    }
    /**
     * Factory method to asynchronously create an instance of SvmSpokePoolClient.
     */
    SVMSpokePoolClient.create = function (logger, hubPoolClient, chainId, deploymentSlot, eventSearchConfig, // Provide default
    rpc) {
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        return __awaiter(this, void 0, void 0, function () {
            var svmEventsClient, programId, statePda;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, SvmCpiEventsClient.create(rpc)];
                    case 1:
                        svmEventsClient = _a.sent();
                        programId = svmEventsClient.getProgramAddress();
                        return [4 /*yield*/, getStatePda(programId)];
                    case 2:
                        statePda = _a.sent();
                        return [2 /*return*/, new SVMSpokePoolClient(logger, hubPoolClient, chainId, deploymentSlot, eventSearchConfig, svmEventsClient, programId, statePda)];
                }
            });
        });
    };
    /**
     * Factory method to asynchronously create an instance of SvmSpokePoolClient with an existing event client.
     */
    SVMSpokePoolClient.createWithExistingEventClient = function (logger, hubPoolClient, chainId, deploymentSlot, eventSearchConfig, // Provide default
    eventClient) {
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        return __awaiter(this, void 0, void 0, function () {
            var programId, statePda;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        programId = eventClient.getProgramAddress();
                        return [4 /*yield*/, getStatePda(programId)];
                    case 1:
                        statePda = _a.sent();
                        return [2 /*return*/, new SVMSpokePoolClient(logger, hubPoolClient, chainId, deploymentSlot, eventSearchConfig, eventClient, programId, statePda)];
                }
            });
        });
    };
    SVMSpokePoolClient.prototype._queryableEventNames = function () {
        // We want to take the internal event names relevant to
        // the SVM SpokePoolClient and filter them against the
        // knownEventNames list that we reference in practice
        var internalEventNames = Object.values(SVMEventNames);
        return internalEventNames.filter(function (e) { return knownEventNames.includes(e); });
    };
    /**
     * Performs an update to refresh the state of this client by querying SVM events.
     */
    SVMSpokePoolClient.prototype._update = function (eventsToQuery) {
        return __awaiter(this, void 0, void 0, function () {
            var searchConfig, reason, eventSearchConfigs, spokePoolAddress, timerStart, _a, currentTime, eventsQueried, errMsg;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, this.updateSearchConfig(this.svmEventsClient.getRpc())];
                    case 1:
                        searchConfig = _b.sent();
                        if (isUpdateFailureReason(searchConfig)) {
                            reason = searchConfig;
                            return [2 /*return*/, { success: false, reason: reason }];
                        }
                        eventSearchConfigs = eventsToQuery.map(function (eventName) {
                            if (!_this._queryableEventNames().includes(eventName)) {
                                throw new Error("SpokePoolClient: Cannot query unrecognised SpokePool event name: ".concat(eventName));
                            }
                            var _searchConfig = __assign({}, searchConfig); // shallow copy
                            return _searchConfig;
                        });
                        spokePoolAddress = this.svmEventsClient.getProgramAddress();
                        this.log("debug", "Updating SpokePool client for chain ".concat(this.chainId), {
                            eventsToQuery: eventsToQuery,
                            searchConfig: searchConfig,
                            spokePool: spokePoolAddress,
                        });
                        timerStart = Date.now();
                        return [4 /*yield*/, Promise.all(__spreadArray([
                                this.getTimeAt(searchConfig.to)
                            ], eventsToQuery.map(function (eventName, idx) { return __awaiter(_this, void 0, void 0, function () {
                                var config, events;
                                return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0:
                                            config = eventSearchConfigs[idx];
                                            return [4 /*yield*/, this.svmEventsClient.queryEvents(eventName, BigInt(config.from), BigInt(config.to), {
                                                    limit: config.maxLookBack,
                                                })];
                                        case 1:
                                            events = _a.sent();
                                            return [2 /*return*/, events.map(function (event) { return (__assign({ txnRef: event.signature, blockNumber: Number(event.slot), txnIndex: 0, logIndex: 0 }, unwrapEventData(event.data))); })];
                                    }
                                });
                            }); }), true))];
                    case 2:
                        _a = _b.sent(), currentTime = _a[0], eventsQueried = _a.slice(1);
                        this.log("debug", "Time to query new events from RPC for ".concat(this.chainId, ": ").concat(Date.now() - timerStart, " ms"));
                        if (currentTime < this.currentTime) {
                            errMsg = "currentTime: ".concat(currentTime, " < ").concat(this.currentTime);
                            throw new Error("SvmSpokePoolClient::update: ".concat(errMsg));
                        }
                        // Sort all events to ensure they are stored in a consistent order.
                        eventsQueried.forEach(function (events) { return sortEventsAscendingInPlace(events); });
                        return [2 /*return*/, {
                                success: true,
                                currentTime: Number(currentTime),
                                searchEndBlock: searchConfig.to,
                                events: eventsQueried,
                            }];
                }
            });
        });
    };
    /**
     * Retrieves the fill deadline buffer fetched from the State PDA.
     * @note This function assumes that fill deadline buffer is a constant value in svm environments.
     */
    SVMSpokePoolClient.prototype.getMaxFillDeadlineInRange = function (_startSlot, _endSlot) {
        return getFillDeadline(this.svmEventsClient.getRpc(), this.statePda);
    };
    /**
     * Retrieves the timestamp for a given SVM slot number.
     */
    SVMSpokePoolClient.prototype.getTimestampForBlock = function (slot) {
        return __awaiter(this, void 0, void 0, function () {
            var _slot, timestamp;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _slot = BigInt(slot);
                        _a.label = 1;
                    case 1: return [4 /*yield*/, getTimestampForSlot(this.svmEventsClient.getRpc(), _slot)];
                    case 2:
                        timestamp = _a.sent();
                        if (isDefined(timestamp)) {
                            return [2 /*return*/, timestamp];
                        }
                        _a.label = 3;
                    case 3:
                        if (--_slot > 0) return [3 /*break*/, 1];
                        _a.label = 4;
                    case 4: throw new Error("Unable to resolve time at or before ".concat(getNetworkName(this.chainId), " slot ").concat(slot));
                }
            });
        });
    };
    /**
     * Retrieves the timestamp for a given SVM slot number.
     * @note This function uses the same underlying function as getTimestampForBlock.
     *       It is kept for consistency with the EVM SpokePoolClient.
     */
    SVMSpokePoolClient.prototype.getTimeAt = function (slot) {
        return this.getTimestampForBlock(slot);
    };
    /**
     * Finds a deposit based on its deposit ID on the SVM chain.
     */
    SVMSpokePoolClient.prototype.findDeposit = function (depositId) {
        return __awaiter(this, void 0, void 0, function () {
            var deposit, _a;
            var _b, _c;
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0: return [4 /*yield*/, findDeposit(this.svmEventsClient, depositId)];
                    case 1:
                        deposit = _d.sent();
                        if (!deposit) {
                            return [2 /*return*/, {
                                    found: false,
                                    code: InvalidFill.DepositIdNotFound,
                                    reason: "Deposit with ID ".concat(depositId, " not found"),
                                }];
                        }
                        _b = {
                            found: true
                        };
                        _a = [__assign({}, deposit)];
                        _c = {};
                        return [4 /*yield*/, this.getBlockNumber(Number(deposit.quoteTimestamp))];
                    case 2: 
                    // Because we have additional context about this deposit, we can enrich it
                    // with additional information.
                    return [2 /*return*/, (_b.deposit = __assign.apply(void 0, _a.concat([(_c.quoteBlockNumber = _d.sent(), _c.originChainId = this.chainId, _c.fromLiteChain = this.isOriginLiteChain(deposit), _c.toLiteChain = this.isDestinationLiteChain(deposit), _c)])),
                            _b)];
                }
            });
        });
    };
    /**
     * Retrieves the fill status for a given relay data from the SVM chain.
     */
    SVMSpokePoolClient.prototype.relayFillStatus = function (relayData, atHeight) {
        return relayFillStatus(this.programId, relayData, this.chainId, this.svmEventsClient, atHeight);
    };
    /**
     * Retrieves the fill status for an array of given relay data.
     * @param relayData The array relay data to retrieve the fill status for.
     * @param atHeight The slot at which to query the fill status.
     * @returns The fill status for each of the given relay data.
     */
    SVMSpokePoolClient.prototype.fillStatusArray = function (relayData, atHeight, destinationChainId) {
        // @note: deploymentBlock actually refers to the deployment slot. Also, blockTag should be a slot number.
        destinationChainId !== null && destinationChainId !== void 0 ? destinationChainId : (destinationChainId = this.chainId);
        return fillStatusArray(this.programId, relayData, destinationChainId, this.svmEventsClient, atHeight, this.logger);
    };
    return SVMSpokePoolClient;
}(SpokePoolClient));
export { SVMSpokePoolClient };
//# sourceMappingURL=SVMSpokePoolClient.js.map