import { __assign, __awaiter, __extends, __generator, __spreadArray } from "tslib";
import { fillStatusArray, findDepositBlock, getMaxFillDeadlineInRange as getMaxFillDeadline, getTimeAt as _getTimeAt, relayFillStatus, getTimestampForBlock as _getTimestampForBlock, } from "../../arch/evm";
import { BigNumber, getNetworkName, InvalidFill, toBN, EvmAddress, toAddressType, } from "../../utils";
import { logToSortableEvent, paginatedEventQuery, sortEventsAscendingInPlace, spreadEventWithBlockNumber, } from "../../utils/EventUtils";
import { isUpdateFailureReason } from "../BaseAbstractClient";
import { knownEventNames, SpokePoolClient } from "./SpokePoolClient";
import { EVM_SPOKE_POOL_CLIENT_TYPE } from "./types";
/**
 * An EVM-specific SpokePoolClient.
 */
var EVMSpokePoolClient = /** @class */ (function (_super) {
    __extends(EVMSpokePoolClient, _super);
    function EVMSpokePoolClient(logger, spokePool, hubPoolClient, chainId, deploymentBlock, eventSearchConfig) {
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        var _this = _super.call(this, logger, hubPoolClient, chainId, deploymentBlock, eventSearchConfig) || this;
        _this.spokePool = spokePool;
        _this.type = EVM_SPOKE_POOL_CLIENT_TYPE;
        _this.spokePoolAddress = EvmAddress.from(spokePool.address);
        return _this;
    }
    EVMSpokePoolClient.prototype.relayFillStatus = function (relayData, atHeight) {
        return relayFillStatus(this.spokePool, relayData, atHeight, this.chainId);
    };
    EVMSpokePoolClient.prototype.fillStatusArray = function (relayData, atHeight) {
        return fillStatusArray(this.spokePool, relayData, atHeight);
    };
    EVMSpokePoolClient.prototype.getMaxFillDeadlineInRange = function (startBlock, endBlock) {
        return getMaxFillDeadline(this.spokePool, startBlock, endBlock);
    };
    EVMSpokePoolClient.prototype._availableEventsOnSpoke = function (eventNames) {
        var _this = this;
        if (eventNames === void 0) { eventNames = knownEventNames; }
        return Object.fromEntries(this.spokePool.interface.fragments
            .filter(function (_a) {
            var name = _a.name, type = _a.type;
            return type === "event" && eventNames.includes(name);
        })
            .map(function (_a) {
            var name = _a.name;
            return [name, _this.spokePool.filters[name]()];
        }));
    };
    EVMSpokePoolClient.prototype._queryableEventNames = function () {
        return Object.keys(this._availableEventsOnSpoke(knownEventNames));
    };
    EVMSpokePoolClient.prototype._update = function (eventsToQuery) {
        return __awaiter(this, void 0, void 0, function () {
            var searchConfig, reason, eventSearchConfigs, spokePool, timerStart, multicallFunctions, _a, multicallOutput, events, currentTime, errMsg, eventsWithBlockNumber;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, this.updateSearchConfig(this.spokePool.provider)];
                    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
                            // By default, an event's query range is controlled by the `eventSearchConfig` passed in during instantiation.
                            // However, certain events have special overriding requirements to their search ranges:
                            // - EnabledDepositRoute: The full history is always required, so override the requested fromBlock.
                            if (eventName === "EnabledDepositRoute" && !_this.isUpdated) {
                                _searchConfig.from = _this.deploymentBlock;
                            }
                            return {
                                filter: _this._availableEventsOnSpoke()[eventName],
                                searchConfig: _searchConfig,
                            };
                        });
                        spokePool = this.spokePool;
                        this.log("debug", "Updating SpokePool client for chain ".concat(this.chainId), {
                            eventsToQuery: eventsToQuery,
                            searchConfig: searchConfig,
                            spokePool: spokePool.address,
                        });
                        timerStart = Date.now();
                        multicallFunctions = ["getCurrentTime"];
                        return [4 /*yield*/, Promise.all(__spreadArray([
                                spokePool.callStatic.multicall(multicallFunctions.map(function (f) { return spokePool.interface.encodeFunctionData(f); }), { blockTag: searchConfig.to })
                            ], eventSearchConfigs.map(function (config) { return paginatedEventQuery(_this.spokePool, config.filter, config.searchConfig); }), true))];
                    case 2:
                        _a = _b.sent(), multicallOutput = _a[0], events = _a.slice(1);
                        this.log("debug", "Time to query new events from RPC for ".concat(this.chainId, ": ").concat(Date.now() - timerStart, " ms"));
                        currentTime = multicallFunctions.map(function (fn, idx) { return spokePool.interface.decodeFunctionResult(fn, multicallOutput[idx])[0]; })[0];
                        if (!BigNumber.isBigNumber(currentTime) || currentTime.lt(this.currentTime)) {
                            errMsg = BigNumber.isBigNumber(currentTime)
                                ? "currentTime: ".concat(currentTime, " < ").concat(toBN(this.currentTime))
                                : "currentTime is not a BigNumber: ".concat(JSON.stringify(currentTime));
                            throw new Error("EVMSpokePoolClient::update: ".concat(errMsg));
                        }
                        // Sort all events to ensure they are stored in a consistent order.
                        events.forEach(function (events) { return sortEventsAscendingInPlace(events.map(logToSortableEvent)); });
                        eventsWithBlockNumber = events.map(function (eventList) {
                            return eventList.map(function (event) { return spreadEventWithBlockNumber(event); });
                        });
                        return [2 /*return*/, {
                                success: true,
                                currentTime: currentTime.toNumber(),
                                searchEndBlock: searchConfig.to,
                                events: eventsWithBlockNumber,
                            }];
                }
            });
        });
    };
    EVMSpokePoolClient.prototype.getTimeAt = function (blockNumber) {
        return _getTimeAt(this.spokePool, blockNumber);
    };
    EVMSpokePoolClient.prototype.findDeposit = function (depositId) {
        return __awaiter(this, void 0, void 0, function () {
            var deposit, upperBound, from, chain, reason, to, tStart, maxLookBack, query, tStop, event, _a;
            var _b;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        deposit = this.getDeposit(depositId);
                        if (deposit) {
                            return [2 /*return*/, { found: true, deposit: deposit }];
                        }
                        upperBound = this.latestHeightSearched || undefined;
                        return [4 /*yield*/, findDepositBlock(this.spokePool, depositId, this.deploymentBlock, upperBound)];
                    case 1:
                        from = _c.sent();
                        chain = getNetworkName(this.chainId);
                        if (!from) {
                            reason = "Unable to find ".concat(chain, " depositId ").concat(depositId) +
                                " within blocks [".concat(this.deploymentBlock, ", ").concat(upperBound !== null && upperBound !== void 0 ? upperBound : "latest", "].");
                            return [2 /*return*/, { found: false, code: InvalidFill.DepositIdNotFound, reason: reason }];
                        }
                        to = from;
                        tStart = Date.now();
                        maxLookBack = this.eventSearchConfig.maxLookBack;
                        return [4 /*yield*/, Promise.all([
                                paginatedEventQuery(this.spokePool, this.spokePool.filters.V3FundsDeposited(null, null, null, null, null, depositId), { from: from, to: to, maxLookBack: maxLookBack }),
                                paginatedEventQuery(this.spokePool, this.spokePool.filters.FundsDeposited(null, null, null, null, null, depositId), { from: from, to: to, maxLookBack: maxLookBack }),
                            ])];
                    case 2:
                        query = (_c.sent()).flat();
                        tStop = Date.now();
                        event = query.find(function (_a) {
                            var args = _a.args;
                            return args["depositId"].eq(depositId);
                        });
                        if (event === undefined) {
                            return [2 /*return*/, {
                                    found: false,
                                    code: InvalidFill.DepositIdNotFound,
                                    reason: "".concat(chain, " depositId ").concat(depositId, " not found at block ").concat(from, "."),
                                }];
                        }
                        _a = [__assign({}, spreadEventWithBlockNumber(event))];
                        _b = { inputToken: toAddressType(event.args.inputToken, event.args.originChainId), outputToken: toAddressType(event.args.outputToken, event.args.destinationChainId), depositor: toAddressType(event.args.depositor, this.chainId), recipient: toAddressType(event.args.recipient, event.args.destinationChainId), exclusiveRelayer: toAddressType(event.args.exclusiveRelayer, event.args.destinationChainId), originChainId: this.chainId };
                        return [4 /*yield*/, this.getBlockNumber(Number(event.args["quoteTimestamp"]))];
                    case 3:
                        deposit = __assign.apply(void 0, _a.concat([(_b.quoteBlockNumber = _c.sent(), _b.fromLiteChain = true, _b.toLiteChain = true, _b)]));
                        if (deposit.outputToken.isZeroAddress()) {
                            deposit.outputToken = this.getDestinationTokenForDeposit(deposit);
                        }
                        deposit.fromLiteChain = this.isOriginLiteChain(deposit);
                        deposit.toLiteChain = this.isDestinationLiteChain(deposit);
                        this.logger.debug({
                            at: "SpokePoolClient#findDeposit",
                            message: "Located deposit outside of SpokePoolClient's search range",
                            deposit: deposit,
                            elapsedMs: tStop - tStart,
                        });
                        return [2 /*return*/, { found: true, deposit: deposit }];
                }
            });
        });
    };
    EVMSpokePoolClient.prototype.getTimestampForBlock = function (blockNumber) {
        return _getTimestampForBlock(this.spokePool.provider, blockNumber);
    };
    return EVMSpokePoolClient;
}(SpokePoolClient));
export { EVMSpokePoolClient };
//# sourceMappingURL=EVMSpokePoolClient.js.map