"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionRateUseCase = void 0;
const not_found_error_1 = require("../../../domain/errors/not-found-error");
const validation_error_1 = require("../../../domain/errors/validation-error");
const DEFAULT_MINIMUM_SWAP_VALUE_USD = 10;
function formatRawAmount(raw, decimals) {
    try {
        const value = BigInt(raw);
        const isNegative = value < 0n;
        const absValue = isNegative ? -value : value;
        const divisor = 10n ** BigInt(decimals);
        const integerPart = absValue / divisor;
        const fractionalPart = absValue % divisor;
        const fractionalStr = fractionalPart.toString().padStart(decimals, '0').replace(/0+$/, '');
        const sign = isNegative ? '-' : '';
        return fractionalStr ? `${sign}${integerPart}.${fractionalStr}` : `${sign}${integerPart}`;
    }
    catch {
        return null;
    }
}
function calculateFeeRaw(fee, gasPrice, gasLimit) {
    if (fee) {
        return fee;
    }
    if (gasPrice && gasLimit != null) {
        return (BigInt(gasPrice) * BigInt(gasLimit)).toString();
    }
    return null;
}
function isTokenTransferPath(path) {
    return 'fromTokenValue' in path && 'toTokenValue' in path;
}
class TransactionRateUseCase {
    networkRepository;
    tokenRepository;
    coinMarketCapClient;
    constructor(networkRepository, tokenRepository, coinMarketCapClient) {
        this.networkRepository = networkRepository;
        this.tokenRepository = tokenRepository;
        this.coinMarketCapClient = coinMarketCapClient;
    }
    async execute(input) {
        const network = await this.networkRepository.findById(input.networkId);
        if (!network) {
            throw new not_found_error_1.NotFoundError('Network', input.networkId);
        }
        const networkTokens = await this.tokenRepository.findByNetworkId(network.id);
        const nativeToken = networkTokens.find((token) => token.isNative);
        const fee = await this.buildFee(input, nativeToken);
        const swap = input.method.toLowerCase() === 'swap' ? await this.buildSwap(input, networkTokens) : null;
        return {
            networkId: network.id,
            method: input.method,
            fee,
            swap,
        };
    }
    async buildFee(input, nativeToken) {
        const raw = calculateFeeRaw(input.fee, input.gasPrice, input.gasLimit);
        if (!raw) {
            return {
                raw: null,
                formatted: null,
                tokenSymbol: nativeToken?.symbol ?? null,
                tokenDecimals: nativeToken?.decimals ?? null,
                usd: null,
            };
        }
        const formatted = nativeToken ? formatRawAmount(raw, nativeToken.decimals) : null;
        const usd = formatted && nativeToken ? await this.estimateUsd(nativeToken, formatted) : null;
        return {
            raw,
            formatted,
            tokenSymbol: nativeToken?.symbol ?? null,
            tokenDecimals: nativeToken?.decimals ?? null,
            usd,
        };
    }
    async buildSwap(input, networkTokens) {
        const tokenIn = await this.resolveTokenIn(input, networkTokens);
        const tokenOut = await this.resolveTokenOut(input, networkTokens);
        if (!tokenIn || !tokenOut) {
            const details = {};
            if (!tokenIn) {
                details.tokenIn = ['Token not found'];
            }
            if (!tokenOut) {
                details.tokenOut = ['Token not found'];
            }
            throw new validation_error_1.ValidationError('Swap transactions require tokenIn and tokenOut to be specified or derivable from data', details);
        }
        const amountInRaw = this.resolveAmountIn(input);
        const amountOutRaw = this.resolveAmountOut(input);
        const amountIn = await this.buildAmount(amountInRaw, tokenIn);
        const amountOut = await this.buildAmount(amountOutRaw, tokenOut);
        const minimumSwapValueUsd = tokenIn.minimumSwapValue ?? DEFAULT_MINIMUM_SWAP_VALUE_USD;
        let minimumSwapValueFormatted = null;
        if (amountIn.usd != null && amountIn.usd > 0 && amountIn.formatted != null) {
            const amountInFormattedValue = parseFloat(amountIn.formatted);
            minimumSwapValueFormatted = ((minimumSwapValueUsd * amountInFormattedValue) / amountIn.usd).toString();
        }
        const meetsMinimum = amountIn.usd != null ? amountIn.usd >= minimumSwapValueUsd : null;
        return {
            amountIn,
            amountOut,
            minimumSwapValueUsd,
            minimumSwapValueFormatted,
            meetsMinimum,
        };
    }
    async resolveTokenIn(input, networkTokens) {
        const tokenTransferPath = (input.data ?? []).find(isTokenTransferPath);
        if (tokenTransferPath) {
            const fromToken = await this.tokenRepository.findById(tokenTransferPath.from);
            if (fromToken) {
                return fromToken;
            }
        }
        if (input.tokenIn) {
            return this.findTokenBySymbolOrCode(input.tokenIn, networkTokens);
        }
        return null;
    }
    async resolveTokenOut(input, networkTokens) {
        const tokenTransferPath = (input.data ?? []).find(isTokenTransferPath);
        if (tokenTransferPath) {
            const toToken = await this.tokenRepository.findById(tokenTransferPath.to);
            if (toToken) {
                return toToken;
            }
        }
        if (input.tokenOut) {
            return this.findTokenBySymbolOrCode(input.tokenOut, networkTokens);
        }
        return null;
    }
    findTokenBySymbolOrCode(identifier, networkTokens) {
        const upperIdentifier = identifier.toUpperCase();
        const bySymbol = networkTokens.find((token) => token.symbol.toUpperCase() === upperIdentifier);
        if (bySymbol) {
            return bySymbol;
        }
        const byCode = networkTokens.find((token) => token.code.toUpperCase() === upperIdentifier);
        return byCode ?? null;
    }
    resolveAmountIn(input) {
        if (input.amountIn) {
            return input.amountIn;
        }
        const tokenTransferPath = (input.data ?? []).find(isTokenTransferPath);
        return tokenTransferPath ? tokenTransferPath.fromTokenValue : null;
    }
    resolveAmountOut(input) {
        if (input.amountOut) {
            return input.amountOut;
        }
        const tokenTransferPath = (input.data ?? []).find(isTokenTransferPath);
        return tokenTransferPath ? tokenTransferPath.toTokenValue : null;
    }
    async buildAmount(raw, token) {
        if (!raw) {
            return {
                raw: null,
                formatted: null,
                tokenSymbol: token.symbol,
                tokenDecimals: token.decimals,
                usd: null,
            };
        }
        const formatted = formatRawAmount(raw, token.decimals);
        const usd = formatted ? await this.estimateUsd(token, formatted) : null;
        return {
            raw,
            formatted,
            tokenSymbol: token.symbol,
            tokenDecimals: token.decimals,
            usd,
        };
    }
    async estimateUsd(token, formattedAmount) {
        try {
            const coinInfo = await this.coinMarketCapClient.getCoinInfoBySlug(token.code);
            if (!coinInfo) {
                return null;
            }
            const prices = await this.coinMarketCapClient.getPrices([coinInfo.id], 'USD');
            const price = prices.get(coinInfo.id);
            if (price == null) {
                return null;
            }
            return parseFloat(formattedAmount) * price;
        }
        catch {
            return null;
        }
    }
}
exports.TransactionRateUseCase = TransactionRateUseCase;
//# sourceMappingURL=transaction-rate.use-case.js.map