"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getWidestPossibleExpectedBlockRange = getWidestPossibleExpectedBlockRange;
exports.isChainPaused = isChainPaused;
exports.isChainDisabledAtBlock = isChainDisabledAtBlock;
exports.computeDesiredTransferAmountToSpoke = computeDesiredTransferAmountToSpoke;
exports.getNetSendAmountForL1Token = getNetSendAmountForL1Token;
exports.getRunningBalanceForL1Token = getRunningBalanceForL1Token;
exports.updateRunningBalance = updateRunningBalance;
exports.addLastRunningBalance = addLastRunningBalance;
exports.updateRunningBalanceForDeposit = updateRunningBalanceForDeposit;
exports.constructPoolRebalanceLeaves = constructPoolRebalanceLeaves;
const tslib_1 = require("tslib");
const SpokePoolClient_1 = require("../../SpokePoolClient");
const utils_1 = require("../../../utils");
const svm_1 = require("../../../arch/svm");
const assert_1 = tslib_1.__importDefault(require("assert"));
async function getWidestPossibleExpectedBlockRange(chainIds, spokeClients, endBlockBuffers, clients, latestMainnetBlock, enabledChains, optimistic = false) {
    const resolveEndBlock = (chainId, idx) => Math.max(spokeClients[chainId].latestHeightSearched - endBlockBuffers[idx], 0);
    const resolveSVMEndBlock = (chainId, idx) => {
        const spokePoolClient = spokeClients[chainId];
        (0, assert_1.default)((0, SpokePoolClient_1.isSVMSpokePoolClient)(spokePoolClient));
        const maxSlot = resolveEndBlock(chainId, idx);
        return (0, svm_1.getLatestFinalizedSlotWithBlock)(spokePoolClient.svmEventsClient.getRpc(), spokePoolClient.logger, BigInt(maxSlot));
    };
    const latestPossibleBundleEndBlockNumbers = await Promise.all(chainIds.map((chainId, idx) => {
        if (!enabledChains.includes(chainId) || !(0, utils_1.isDefined)(spokeClients[chainId])) {
            return -1;
        }
        if ((0, utils_1.chainIsEvm)(chainId)) {
            return Promise.resolve(resolveEndBlock(chainId, idx));
        }
        if ((0, utils_1.chainIsSvm)(chainId)) {
            return resolveSVMEndBlock(chainId, idx);
        }
        (0, assert_1.default)(false, `Unsupported chainId: ${chainId}`);
    }));
    return chainIds.map((chainId, index) => {
        const lastEndBlockForChain = clients.hubPoolClient.getLatestBundleEndBlockForChain(chainIds, latestMainnetBlock, chainId);
        if (!enabledChains.includes(chainId)) {
            return [lastEndBlockForChain, lastEndBlockForChain];
        }
        if (lastEndBlockForChain >= latestPossibleBundleEndBlockNumbers[index]) {
            return [lastEndBlockForChain, lastEndBlockForChain];
        }
        return [
            optimistic
                ? clients.hubPoolClient.getOptimisticBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId)
                : clients.hubPoolClient.getNextBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId),
            latestPossibleBundleEndBlockNumbers[index],
        ];
    });
}
function isChainPaused(blockRangeForChain) {
    return blockRangeForChain[0] === blockRangeForChain[1];
}
function isChainDisabledAtBlock(chainId, mainnetBlock, configStoreClient) {
    return configStoreClient.getDisabledChainsForBlock(mainnetBlock).includes(chainId);
}
function computeDesiredTransferAmountToSpoke(runningBalance, spokePoolTargetBalance) {
    if (runningBalance.gte(0)) {
        return runningBalance;
    }
    if (runningBalance.abs().lt(spokePoolTargetBalance.threshold)) {
        return utils_1.bnZero;
    }
    const transferSize = runningBalance.abs().sub(spokePoolTargetBalance.target);
    if (transferSize.lt(0)) {
        return utils_1.bnZero;
    }
    return transferSize.mul(-1);
}
function getNetSendAmountForL1Token(spokePoolTargetBalance, runningBalance) {
    return computeDesiredTransferAmountToSpoke(runningBalance, spokePoolTargetBalance);
}
function getRunningBalanceForL1Token(spokePoolTargetBalance, runningBalance) {
    const desiredTransferAmount = computeDesiredTransferAmountToSpoke(runningBalance, spokePoolTargetBalance);
    return runningBalance.sub(desiredTransferAmount);
}
function updateRunningBalance(runningBalances, l2ChainId, l1Token, updateAmount) {
    if (!runningBalances[l2ChainId]) {
        runningBalances[l2ChainId] = {};
    }
    const runningBalance = runningBalances[l2ChainId][l1Token];
    if (runningBalance) {
        runningBalances[l2ChainId][l1Token] = runningBalance.add(updateAmount);
    }
    else {
        runningBalances[l2ChainId][l1Token] = updateAmount;
    }
}
function addLastRunningBalance(latestMainnetBlock, runningBalances, hubPoolClient) {
    Object.keys(runningBalances).forEach((repaymentChainId) => {
        Object.keys(runningBalances[Number(repaymentChainId)]).forEach((l1TokenAddress) => {
            const { runningBalance } = hubPoolClient.getRunningBalanceBeforeBlockForChain(latestMainnetBlock, Number(repaymentChainId), utils_1.EvmAddress.from(l1TokenAddress));
            if (!runningBalance.eq(utils_1.bnZero)) {
                updateRunningBalance(runningBalances, Number(repaymentChainId), l1TokenAddress, runningBalance);
            }
        });
    });
}
function updateRunningBalanceForDeposit(runningBalances, hubPoolClient, deposit, updateAmount, mainnetBundleEndBlock) {
    const l1TokenCounterpart = hubPoolClient.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, mainnetBundleEndBlock);
    (0, assert_1.default)((0, utils_1.isDefined)(l1TokenCounterpart), "updateRunningBalanceForDeposit: l1TokenCounterpart is undefined");
    updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart.toEvmAddress(), updateAmount);
}
function constructPoolRebalanceLeaves(latestMainnetBlock, runningBalances, realizedLpFees, chainsWithRefundsOnly, configStoreClient, maxL1TokenCount) {
    (0, assert_1.default)(chainsWithRefundsOnly.every((chainId) => runningBalances[chainId] === undefined) &&
        chainsWithRefundsOnly.every((chainId) => realizedLpFees[chainId] === undefined), "Refund-only chains should not have running balances or realized LP fees.");
    const leaves = [];
    Object.keys(runningBalances)
        .map((chainId) => Number(chainId))
        .concat(chainsWithRefundsOnly)
        .sort((chainIdA, chainIdB) => chainIdA - chainIdB)
        .map((chainId) => {
        if (chainsWithRefundsOnly.includes(chainId)) {
            leaves.push({
                chainId,
                bundleLpFees: [],
                netSendAmounts: [],
                runningBalances: [],
                groupIndex: 0,
                leafId: leaves.length,
                l1Tokens: [],
            });
            return;
        }
        const sortedL1Tokens = Object.keys(runningBalances[chainId]).sort((addressA, addressB) => {
            return (0, utils_1.compareAddresses)(addressA, addressB);
        });
        let groupIndexForChainId = 0;
        const maxL1TokensPerLeaf = maxL1TokenCount || configStoreClient.getMaxRefundCountForRelayerRefundLeafForBlock(latestMainnetBlock);
        for (let i = 0; i < sortedL1Tokens.length; i += maxL1TokensPerLeaf) {
            const l1TokensToIncludeInThisLeaf = sortedL1Tokens.slice(i, i + maxL1TokensPerLeaf);
            const spokeTargetBalances = l1TokensToIncludeInThisLeaf.map((l1Token) => configStoreClient.getSpokeTargetBalancesForBlock(l1Token, chainId, latestMainnetBlock));
            const leafBundleLpFees = l1TokensToIncludeInThisLeaf.map((l1Token) => realizedLpFees[chainId]?.[l1Token] ?? utils_1.bnZero);
            const leafNetSendAmounts = l1TokensToIncludeInThisLeaf.map((l1Token, index) => runningBalances[chainId] && runningBalances[chainId][l1Token]
                ? getNetSendAmountForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token])
                : utils_1.bnZero);
            const leafRunningBalances = l1TokensToIncludeInThisLeaf.map((l1Token, index) => runningBalances[chainId]?.[l1Token]
                ? getRunningBalanceForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token])
                : utils_1.bnZero);
            leaves.push({
                chainId: chainId,
                bundleLpFees: leafBundleLpFees,
                netSendAmounts: leafNetSendAmounts,
                runningBalances: leafRunningBalances,
                groupIndex: groupIndexForChainId++,
                leafId: leaves.length,
                l1Tokens: l1TokensToIncludeInThisLeaf.map((l1TokenAddr) => utils_1.EvmAddress.from(l1TokenAddr)),
            });
        }
    });
    return leaves;
}
//# sourceMappingURL=PoolRebalanceUtils.js.map