"use strict";
/** Transfer-time USD for Chain 138 checkpoint leaves (off-chain metadata only). */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CHAIN138_KNOWN_TOKEN_USD = exports.CHAIN138_STABLE_USD_PEG = exports.CHAIN138_NATIVE_PRICING_ADDRESS = void 0;
exports.usdStringToE8 = usdStringToE8;
exports.formatAmountUsd = formatAmountUsd;
exports.blockTimestampToIso = blockTimestampToIso;
exports.fetchHistoricalPriceUsd = fetchHistoricalPriceUsd;
exports.priceTransferLine = priceTransferLine;
exports.sumUsdStrings = sumUsdStrings;
exports.enrichLeafUsdFields = enrichLeafUsdFields;
exports.CHAIN138_NATIVE_PRICING_ADDRESS = '0xc02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
/** Canonical Chain 138 stables — $1 peg when price-at API is unavailable. */
exports.CHAIN138_STABLE_USD_PEG = {
    '0x93e66202a11b1772e55407b32b44e5cd8eda7f22': 1, // cUSDT
    '0xf22258f57794cc8e06237084b353ab30ffa640b': 1, // cUSDC
};
/** Known Chain 138 assets (override via env CHECKPOINT_USD_PEG_<SYMBOL>). */
exports.CHAIN138_KNOWN_TOKEN_USD = {
    ...exports.CHAIN138_STABLE_USD_PEG,
    '0xb7721dd53a8c629d9f1ba31a5819afe250002b03': 15, // LINK (set CHECKPOINT_USD_PEG_LINK)
    '0xe94260c555ac1d9d3cc9e1632883452ebdf0082e': 90000, // cBTC
    '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2': 2490, // WETH pricing proxy
};
const SYMBOL_USD_ESTIMATE = {
    CUSDT: 1,
    CUSDC: 1,
    USDT: 1,
    USDC: 1,
    CJPYC: 0.006285683794888213,
    CGBPC: 1.3550353712543854,
    CEURC: 1.178,
    CAUDC: 0.7136366390016357,
    CCADC: 0.7255928549430243,
    CCHFC: 1.2776572668112798,
    LINK: 15,
    CBTC: 90000,
    WETH: 2490,
    ETH: 2490,
};
function knownTokenUsd(address, symbol) {
    const addr = address.toLowerCase();
    const envKey = symbol ? `CHECKPOINT_USD_PEG_${symbol.toUpperCase()}` : '';
    if (symbol && envKey && process.env[envKey]) {
        const n = Number(process.env[envKey]);
        if (Number.isFinite(n) && n > 0)
            return n;
    }
    const fromAddr = exports.CHAIN138_KNOWN_TOKEN_USD[addr];
    if (fromAddr != null && fromAddr > 0)
        return fromAddr;
    if (symbol) {
        const fromSym = SYMBOL_USD_ESTIMATE[symbol.toUpperCase()];
        if (fromSym != null && fromSym > 0)
            return fromSym;
    }
    return undefined;
}
/** Convert decimal USD string (e.g. batch JSON) to 8-decimal fixed point for on-chain events. */
function usdStringToE8(usd) {
    if (usd == null || usd === '')
        return 0n;
    const n = Number(usd);
    if (!Number.isFinite(n) || n < 0)
        return 0n;
    return BigInt(Math.round(n * 1e8));
}
function decimalToScaledInteger(value, scale) {
    if (!Number.isFinite(value))
        return null;
    const normalized = value.toFixed(scale);
    const negative = normalized.startsWith('-');
    const unsigned = negative ? normalized.slice(1) : normalized;
    const [whole, fraction = ''] = unsigned.split('.');
    try {
        const scaled = BigInt(whole + fraction.padEnd(scale, '0'));
        return { scaled: negative ? -scaled : scaled, scale: 10n ** BigInt(scale) };
    }
    catch {
        return null;
    }
}
/** USD string with `outputScale` fractional digits (default 6). */
function formatAmountUsd(rawAmount, decimals, priceUsd, priceScale = 8, outputScale = 6) {
    if (!rawAmount || !Number.isFinite(priceUsd) || decimals < 0)
        return undefined;
    try {
        const amount = BigInt(rawAmount);
        const parsedPrice = decimalToScaledInteger(priceUsd, priceScale);
        if (!parsedPrice)
            return undefined;
        const numerator = amount * parsedPrice.scaled * (10n ** BigInt(outputScale));
        const denominator = (10n ** BigInt(decimals)) * parsedPrice.scale;
        const rounded = (numerator + denominator / 2n) / denominator;
        const divisor = 10n ** BigInt(outputScale);
        const whole = rounded / divisor;
        const fraction = (rounded % divisor).toString().padStart(outputScale, '0').replace(/0+$/, '');
        return fraction ? `${whole.toString()}.${fraction}` : whole.toString();
    }
    catch {
        return undefined;
    }
}
function blockTimestampToIso(blockTimestamp) {
    const sec = blockTimestamp > 1e12 ? Math.floor(blockTimestamp / 1000) : blockTimestamp;
    return new Date(sec * 1000).toISOString();
}
const priceCache = new Map();
async function fetchHistoricalPriceUsd(cfg, tokenAddress, blockTimestamp) {
    if (cfg.enabled === false)
        return null;
    const chainId = cfg.chainId ?? 138;
    const iso = blockTimestampToIso(blockTimestamp);
    const key = `${chainId}:${tokenAddress.toLowerCase()}:${iso}`;
    if (priceCache.has(key))
        return priceCache.get(key) ?? null;
    const base = cfg.apiBaseUrl.replace(/\/$/, '');
    const url = `${base}/tokens/${tokenAddress}/price-at?chainId=${chainId}&timestamp=${encodeURIComponent(iso)}`;
    try {
        let res = null;
        for (let attempt = 0; attempt < 4; attempt++) {
            res = await fetch(url);
            if (res.ok || res.status < 500)
                break;
            await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
        }
        if (!res) {
            priceCache.set(key, null);
            return null;
        }
        if (!res.ok) {
            const pegFail = knownTokenUsd(tokenAddress);
            if (pegFail != null) {
                const snap = {
                    chainId,
                    tokenAddress: tokenAddress.toLowerCase(),
                    requestedTimestamp: iso,
                    effectiveTimestamp: iso,
                    priceUsd: pegFail,
                    source: 'canonical_token_peg',
                };
                priceCache.set(key, snap);
                return snap;
            }
            priceCache.set(key, null);
            return null;
        }
        const body = (await res.json());
        if (body?.error || body?.priceUsd == null || !Number.isFinite(body.priceUsd)) {
            const peg = knownTokenUsd(tokenAddress);
            if (peg != null) {
                const snap = {
                    chainId,
                    tokenAddress: tokenAddress.toLowerCase(),
                    requestedTimestamp: iso,
                    effectiveTimestamp: iso,
                    priceUsd: peg,
                    source: 'canonical_token_peg',
                };
                priceCache.set(key, snap);
                return snap;
            }
            priceCache.set(key, null);
            return null;
        }
        priceCache.set(key, body);
        return body;
    }
    catch {
        const peg = knownTokenUsd(tokenAddress);
        if (peg != null) {
            const snap = {
                chainId,
                tokenAddress: tokenAddress.toLowerCase(),
                requestedTimestamp: iso,
                effectiveTimestamp: iso,
                priceUsd: peg,
                source: 'canonical_token_peg',
            };
            priceCache.set(key, snap);
            return snap;
        }
        priceCache.set(key, null);
        return null;
    }
}
async function priceTransferLine(cfg, line, blockTimestamp) {
    const amount = BigInt(line.amountRaw || '0');
    if (amount === 0n) {
        return { ...line, valueUsd: '0.000000', priceUsd: line.priceUsd, priceSource: line.priceSource ?? 'zero_amount' };
    }
    const pricingAddress = line.kind === 'native' ? exports.CHAIN138_NATIVE_PRICING_ADDRESS : (line.token || '').trim();
    if (!pricingAddress) {
        return { ...line, valueUsd: undefined, priceSource: 'unavailable' };
    }
    let snap = await fetchHistoricalPriceUsd(cfg, pricingAddress, blockTimestamp);
    if (!snap) {
        const peg = knownTokenUsd(pricingAddress, line.tokenSymbol);
        if (peg != null) {
            snap = {
                chainId: cfg.chainId ?? 138,
                tokenAddress: pricingAddress.toLowerCase(),
                requestedTimestamp: blockTimestampToIso(blockTimestamp),
                effectiveTimestamp: blockTimestampToIso(blockTimestamp),
                priceUsd: peg,
                source: 'canonical_token_peg',
            };
        }
    }
    if (!snap) {
        return { ...line, valueUsd: undefined, priceSource: 'unavailable' };
    }
    const decimals = line.kind === 'native' ? 18 : (line.tokenDecimals ?? 18);
    const valueUsd = formatAmountUsd(line.amountRaw, decimals, snap.priceUsd);
    return {
        ...line,
        priceUsd: snap.priceUsd,
        priceSource: snap.source,
        priceEffectiveTimestamp: snap.effectiveTimestamp,
        valueUsd,
    };
}
/** Sum USD strings (6 decimal places) without floating-point drift. */
function sumUsdStrings(values) {
    const scale = 6n;
    const factor = 10n ** scale;
    let sumMicro = 0n;
    let any = false;
    for (const v of values) {
        if (v == null || v === '')
            continue;
        const neg = v.startsWith('-');
        const parts = (neg ? v.slice(1) : v).split('.');
        const whole = BigInt(parts[0] || '0');
        const frac = (parts[1] || '').padEnd(Number(scale), '0').slice(0, Number(scale));
        const micro = whole * factor + BigInt(frac || '0');
        sumMicro += neg ? -micro : micro;
        any = true;
    }
    if (!any)
        return undefined;
    const negOut = sumMicro < 0n;
    const abs = negOut ? -sumMicro : sumMicro;
    const whole = abs / factor;
    const frac = (abs % factor).toString().padStart(Number(scale), '0').replace(/0+$/, '');
    const body = frac ? `${whole}.${frac}` : whole.toString();
    return negOut ? `-${body}` : body;
}
async function enrichLeafUsdFields(cfg, leaf, allErc20) {
    const out = { ...leaf };
    for (const k of [
        'valueUsd',
        'nativeValueUsd',
        'tokenValueUsd',
        'nativePriceUsd',
        'tokenPriceUsd',
        'priceSource',
        'priceEffectiveTimestamp',
        'totalTransfersUsd',
        'transfers',
        'usdEnrichedAt',
    ]) {
        delete out[k];
    }
    const blockTimestamp = Number(out.blockTimestamp ?? 0);
    const nativeWei = BigInt(String(out.nativeValueWei ?? out.value ?? '0'));
    const lines = [];
    if (nativeWei > 0n) {
        lines.push({
            kind: 'native',
            tokenSymbol: 'ETH',
            tokenDecimals: 18,
            from: String(out.from ?? ''),
            to: String(out.to ?? ''),
            amountRaw: nativeWei.toString(),
        });
    }
    for (const t of allErc20) {
        lines.push({
            kind: 'erc20',
            token: t.token,
            tokenSymbol: t.tokenSymbol,
            tokenDecimals: t.tokenDecimals,
            from: t.from,
            to: t.to,
            amountRaw: t.value.toString(),
            logIndex: t.logIndex,
        });
    }
    const priced = [];
    for (const line of lines) {
        priced.push(await priceTransferLine(cfg, line, blockTimestamp));
        if (cfg.requestDelayMs && cfg.requestDelayMs > 0) {
            await new Promise((r) => setTimeout(r, cfg.requestDelayMs));
        }
    }
    out.transfers = priced;
    const nativeLine = priced.find((l) => l.kind === 'native');
    const tokenLines = priced.filter((l) => l.kind === 'erc20');
    const primaryToken = tokenLines.reduce((best, cur) => {
        if (!best)
            return cur;
        try {
            return BigInt(cur.amountRaw) > BigInt(best.amountRaw) ? cur : best;
        }
        catch {
            return best;
        }
    }, undefined);
    if (nativeLine?.valueUsd != null)
        out.nativeValueUsd = nativeLine.valueUsd;
    if (nativeLine?.priceUsd != null)
        out.nativePriceUsd = nativeLine.priceUsd;
    if (primaryToken) {
        if (primaryToken.valueUsd != null)
            out.tokenValueUsd = primaryToken.valueUsd;
        if (primaryToken.priceUsd != null)
            out.tokenPriceUsd = primaryToken.priceUsd;
        if (primaryToken.priceSource)
            out.priceSource = primaryToken.priceSource;
        if (primaryToken.priceEffectiveTimestamp) {
            out.priceEffectiveTimestamp = primaryToken.priceEffectiveTimestamp;
        }
    }
    const effectiveToken = BigInt(String(out.tokenValue ?? '0'));
    if (effectiveToken > 0n && primaryToken?.valueUsd != null) {
        out.valueUsd = primaryToken.valueUsd;
    }
    else if (nativeLine?.valueUsd != null) {
        out.valueUsd = nativeLine.valueUsd;
    }
    else if (priced.length === 0) {
        out.valueUsd = '0.000000';
        out.priceSource = 'no_transfer_value';
    }
    else {
        out.valueUsd = sumUsdStrings(priced.map((l) => l.valueUsd));
        out.priceSource = priced.some((l) => l.priceSource && l.priceSource !== 'unavailable')
            ? 'multi_transfer_sum'
            : 'unavailable';
    }
    out.totalTransfersUsd = sumUsdStrings(priced.map((l) => l.valueUsd));
    out.usdEnrichedAt = new Date().toISOString();
    return out;
}
