"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkPortfolioToolDefinition = void 0;
exports.checkPortfolio = checkPortfolio;
const viem_1 = require("viem");
const config_1 = require("../config");
const tokens_1 = require("../utils/tokens");
const portfolioCache = {};
const CACHE_TTL = 5000; // 5 seconds TTL
async function checkPortfolio(chainName, address) {
    try {
        const client = (0, config_1.getPublicClient)(chainName);
        let targetAddress = address;
        if (!targetAddress) {
            const { getAddress } = await Promise.resolve().then(() => __importStar(require('../config')));
            targetAddress = (await getAddress());
        }
        if (!targetAddress) {
            throw new Error('Address is required but could not be resolved from private key.');
        }
        const cacheKey = `${chainName}:${targetAddress.toLowerCase()}`;
        const now = Date.now();
        if (portfolioCache[cacheKey] && now - portfolioCache[cacheKey].timestamp < CACHE_TTL) {
            return portfolioCache[cacheKey].data + `\n\n*(Cached from ${(now - portfolioCache[cacheKey].timestamp) / 1000}s ago)*`;
        }
        const tokensToScan = [
            { symbol: 'Native', address: '0x0000000000000000000000000000000000000000', isNative: true }
        ];
        const chainTokens = tokens_1.TOKEN_MAP[chainName];
        if (chainTokens) {
            for (const [sym, addr] of Object.entries(chainTokens)) {
                if (addr !== "0x0000000000000000000000000000000000000000") {
                    tokensToScan.push({ symbol: sym, address: addr, isNative: false });
                }
            }
        }
        // Merge User-Defined Whitelist
        const { getUserTokens } = await Promise.resolve().then(() => __importStar(require('../../utils/userWhitelistManager')));
        const userCustomTokens = getUserTokens(targetAddress, chainName);
        for (const tokenAddr of userCustomTokens) {
            if (!tokensToScan.find(t => t.address.toLowerCase() === tokenAddr.toLowerCase())) {
                tokensToScan.push({ symbol: 'Token', address: tokenAddr, isNative: false });
            }
        }
        // Merge Dynamic Trending Whitelist (CoinGecko lists)
        const { getDynamicTokensForChain } = await Promise.resolve().then(() => __importStar(require('../../utils/dynamicTokenUpdater')));
        const dynamicTokens = await getDynamicTokensForChain(chainName);
        for (const dToken of dynamicTokens) {
            if (!tokensToScan.find(t => t.address.toLowerCase() === dToken.address.toLowerCase())) {
                tokensToScan.push({ symbol: dToken.symbol, address: dToken.address, isNative: false });
            }
        }
        let report = `📊 **Portfolio for ${targetAddress} on ${chainName.toUpperCase()}**\n\n`;
        let totalUsdValue = 0;
        // We will do True On-Chain Multicall with Chunking (max 30 tokens / 60 calls per batch)
        const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
        const MULTICALL3_ABI = [{
                inputs: [{ name: 'addr', type: 'address' }],
                name: 'getEthBalance',
                outputs: [{ name: 'balance', type: 'uint256' }],
                stateMutability: 'view',
                type: 'function',
            }];
        const contracts = [];
        for (const t of tokensToScan) {
            if (t.isNative) {
                contracts.push({ address: MULTICALL3_ADDRESS, abi: MULTICALL3_ABI, functionName: 'getEthBalance', args: [targetAddress] });
            }
            else {
                contracts.push({ address: t.address, abi: tokens_1.ERC20_ABI, functionName: 'balanceOf', args: [targetAddress] });
                contracts.push({ address: t.address, abi: tokens_1.ERC20_ABI, functionName: 'decimals' });
            }
        }
        const CHUNK_SIZE = 60; // 30 tokens (2 calls per token)
        const multicallResults = [];
        try {
            // Create a timeout promise for 5 seconds (more tolerant for large portfolios)
            const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('RPC request timed out')), 5000));
            const executionPromise = (async () => {
                for (let i = 0; i < contracts.length; i += CHUNK_SIZE) {
                    const chunk = contracts.slice(i, i + CHUNK_SIZE);
                    const res = await client.multicall({ contracts: chunk, allowFailure: true });
                    multicallResults.push(...res);
                }
            })();
            await Promise.race([executionPromise, timeoutPromise]);
        }
        catch (e) {
            return `⚠️ **${chainName.toUpperCase()} Network is experiencing high latency.**\nThe public RPC failed to respond. Please try again later.`;
        }
        // Map results back to tokens
        let resultIndex = 0;
        const balances = tokensToScan.map((t) => {
            let balanceNum = 0;
            if (t.isNative) {
                const balResult = multicallResults[resultIndex++];
                if (balResult?.status === 'success' && balResult.result) {
                    balanceNum = parseFloat((0, viem_1.formatEther)(balResult.result));
                }
            }
            else {
                const balResult = multicallResults[resultIndex++];
                const decResult = multicallResults[resultIndex++];
                if (balResult?.status === 'success' && balResult.result !== undefined && decResult?.status === 'success' && decResult.result !== undefined) {
                    balanceNum = parseFloat((0, viem_1.formatUnits)(balResult.result, Number(decResult.result)));
                }
            }
            return { ...t, balanceNum };
        });
        const nonZeroBalances = balances.filter(b => b.balanceNum > 0);
        if (nonZeroBalances.length === 0) {
            return report + `No funds found for standard tokens on this chain. Net Worth: $0.00`;
        }
        // Now fetch prices from Dexscreener
        // Prepare addresses to fetch
        const addressesToFetch = nonZeroBalances.map(b => b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address).filter(Boolean);
        const priceMap = {};
        if (addressesToFetch.length > 0) {
            const url = `https://api.dexscreener.com/latest/dex/tokens/${addressesToFetch.join(',')}`;
            try {
                const res = await fetch(url);
                const data = await res.json();
                if (data.pairs) {
                    data.pairs.forEach((p) => {
                        if (!priceMap[p.baseToken.address.toLowerCase()]) {
                            priceMap[p.baseToken.address.toLowerCase()] = parseFloat(p.priceUsd);
                        }
                    });
                }
            }
            catch (e) { }
        }
        for (const b of nonZeroBalances) {
            const lookupAddr = (b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address)?.toLowerCase() || "";
            const price = priceMap[lookupAddr] || 0;
            const usdValue = b.balanceNum * price;
            totalUsdValue += usdValue;
            const formattedUsd = usdValue > 0 && usdValue < 0.01 ? usdValue.toFixed(4) : usdValue.toFixed(2);
            const pnlIndicator = usdValue > 0 ? '🟢' : '⚪';
            report += `${pnlIndicator} **$${b.symbol}** | ${b.balanceNum.toFixed(4)} ${b.symbol} ($${formattedUsd})\n`;
            report += `📈 **PnL:** +0.00% ($0.00) _(Simulation)_\n`;
            report += `🤖 *Analysis:* Asset tracking active. Awaiting historical entry price data.\n\n`;
        }
        report += `\n💰 **Estimated Net Worth: $${totalUsdValue.toFixed(2)}**`;
        portfolioCache[cacheKey] = { data: report, timestamp: Date.now() };
        return report;
    }
    catch (error) {
        return `Failed to check portfolio: ${error.message}`;
    }
}
exports.checkPortfolioToolDefinition = {
    type: "function",
    function: {
        name: "check_portfolio",
        description: "Scans the user's wallet for common tokens on a specific chain and calculates their total USD Net Worth (PNL proxy) using live prices.",
        parameters: {
            type: "object",
            properties: {
                chainName: {
                    type: "string",
                    enum: config_1.SUPPORTED_CHAIN_NAMES,
                    description: "The blockchain network",
                },
                address: {
                    type: "string",
                    description: "Optional wallet address. If omitted, uses the AI agent's own wallet.",
                }
            },
            required: ["chainName"],
        },
    },
};
