"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.adaptHyperLiquidLedgerUpdateToUserHistoryItem = exports.parseAssetName = exports.calculateHip3AssetId = exports.calculatePositionSize = exports.formatHyperLiquidSize = exports.formatHyperLiquidPrice = exports.buildAssetMapping = exports.adaptAccountStateFromSDK = exports.adaptMarketFromSDK = exports.adaptOrderFromSDK = exports.adaptPositionFromSDK = exports.adaptOrderToSDK = void 0;
const utils_1 = require("@metamask/utils");
const hyperLiquidConfig_1 = require("../constants/hyperLiquidConfig.cjs");
const perpsConfig_1 = require("../constants/perpsConfig.cjs");
const significantFigures_1 = require("./significantFigures.cjs");
const readOptionalString = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
const readOptionalOrderId = (value) => {
    if (typeof value === 'string' && value.length > 0) {
        return value;
    }
    if (typeof value === 'number' && Number.isFinite(value)) {
        return value.toString();
    }
    return undefined;
};
const getParentTpslMetadata = (rawOrder) => ({
    takeProfitPrice: readOptionalString(rawOrder.takeProfitPrice),
    stopLossPrice: readOptionalString(rawOrder.stopLossPrice),
    takeProfitOrderId: readOptionalOrderId(rawOrder.takeProfitOrderId),
    stopLossOrderId: readOptionalOrderId(rawOrder.stopLossOrderId),
});
/**
 * HyperLiquid SDK Adapter Utilities
 *
 * These functions transform between MetaMask Perps API types and HyperLiquid SDK types.
 * The SDK uses cryptic property names for efficiency, but our API uses descriptive names
 * to provide a consistent interface across different perps protocols.
 */
function adaptOrderToSDK(order, symbolToAssetId) {
    const assetId = symbolToAssetId.get(order.symbol);
    if (assetId === undefined) {
        const availableDexs = new Set();
        symbolToAssetId.forEach((_, symbol) => {
            if (symbol.includes(':')) {
                const dex = symbol.split(':')[0];
                availableDexs.add(dex);
            }
        });
        const dexHint = availableDexs.size > 0
            ? ` Available HIP-3 DEXs: ${Array.from(availableDexs).join(', ')}`
            : ' No HIP-3 DEXs currently available.';
        throw new Error(`Asset ${order.symbol} not found in asset mapping.${dexHint} Check console logs for "HyperLiquidProvider: Asset mapping built" to see available assets.`);
    }
    return {
        a: assetId,
        b: order.isBuy,
        p: order.price ?? '0',
        s: order.size,
        r: order.reduceOnly ?? false,
        t: order.orderType === 'limit'
            ? {
                limit: { tif: 'Gtc' },
            }
            : {
                limit: { tif: 'FrontendMarket' },
            },
        c: order.clientOrderId && (0, utils_1.isHexString)(order.clientOrderId)
            ? order.clientOrderId
            : undefined,
    };
}
exports.adaptOrderToSDK = adaptOrderToSDK;
function adaptPositionFromSDK(assetPosition) {
    const pos = assetPosition.position;
    return {
        symbol: pos.coin,
        size: pos.szi,
        entryPrice: pos.entryPx,
        positionValue: pos.positionValue,
        unrealizedPnl: pos.unrealizedPnl,
        marginUsed: pos.marginUsed,
        leverage: {
            type: pos.leverage.type,
            value: pos.leverage.value,
            rawUsd: pos.leverage.type === 'isolated' ? pos.leverage.rawUsd : undefined,
        },
        liquidationPrice: pos.liquidationPx,
        maxLeverage: pos.maxLeverage,
        returnOnEquity: pos.returnOnEquity,
        cumulativeFunding: pos.cumFunding,
        takeProfitCount: 0,
        stopLossCount: 0,
    };
}
exports.adaptPositionFromSDK = adaptPositionFromSDK;
function adaptOrderFromSDK(rawOrder, position) {
    // TODO: Remove this widened boundary type when FrontendOrder includes
    // takeProfitPrice/stopLossPrice and takeProfitOrderId/stopLossOrderId.
    const parentTpslMetadata = getParentTpslMetadata(rawOrder);
    // Extract basic fields with appropriate conversions
    const orderId = rawOrder.oid.toString();
    const symbol = rawOrder.coin;
    const side = rawOrder.side === 'B' ? 'buy' : 'sell';
    const detailedOrderType = rawOrder.orderType;
    const { isTrigger } = rawOrder;
    const { reduceOnly } = rawOrder;
    let orderType = 'market';
    if (detailedOrderType.toLowerCase().includes('limit') || rawOrder.limitPx) {
        orderType = 'limit';
    }
    const price = rawOrder.limitPx || rawOrder.triggerPx || '0';
    let size = rawOrder.sz;
    let originalSize = rawOrder.origSz || size;
    let currentSize = parseFloat(size);
    let origSize = parseFloat(originalSize);
    if (rawOrder.isPositionTpsl && origSize === 0 && position) {
        const absPositionSize = Math.abs(parseFloat(position.size));
        currentSize = absPositionSize;
        origSize = absPositionSize;
        size = absPositionSize.toString();
        originalSize = absPositionSize.toString();
    }
    const filledSize = origSize - currentSize;
    let takeProfitPrice;
    let stopLossPrice;
    let takeProfitOrderId;
    let stopLossOrderId;
    // TODO: We assume that there can only be 1 TP and 1 SL as children but there can be several TPSLs as children
    if (rawOrder.children && rawOrder.children.length > 0) {
        rawOrder.children.forEach((childUnknown) => {
            const child = childUnknown;
            if (child.isTrigger && child.orderType) {
                if (child.orderType.includes('Take Profit')) {
                    takeProfitPrice = child.triggerPx || child.limitPx;
                    takeProfitOrderId = child.oid.toString();
                }
                else if (child.orderType.includes('Stop')) {
                    stopLossPrice = child.triggerPx || child.limitPx;
                    stopLossOrderId = child.oid.toString();
                }
            }
        });
    }
    // Fallback: preserve parent-level TP/SL metadata when children are absent.
    takeProfitPrice ?? (takeProfitPrice = parentTpslMetadata.takeProfitPrice);
    stopLossPrice ?? (stopLossPrice = parentTpslMetadata.stopLossPrice);
    takeProfitOrderId ?? (takeProfitOrderId = parentTpslMetadata.takeProfitOrderId);
    stopLossOrderId ?? (stopLossOrderId = parentTpslMetadata.stopLossOrderId);
    // Build the order object
    const order = {
        orderId,
        symbol,
        side,
        orderType,
        size,
        originalSize,
        price,
        filledSize: filledSize.toString(),
        remainingSize: size,
        status: 'open',
        timestamp: rawOrder.timestamp,
        detailedOrderType,
        isTrigger,
        reduceOnly,
    };
    if (typeof rawOrder.isPositionTpsl === 'boolean') {
        order.isPositionTpsl = rawOrder.isPositionTpsl;
    }
    if (takeProfitPrice) {
        order.takeProfitPrice = takeProfitPrice;
        order.takeProfitOrderId = takeProfitOrderId;
    }
    if (stopLossPrice) {
        order.stopLossPrice = stopLossPrice;
        order.stopLossOrderId = stopLossOrderId;
    }
    if (rawOrder.triggerPx) {
        order.triggerPrice = rawOrder.triggerPx;
    }
    return order;
}
exports.adaptOrderFromSDK = adaptOrderFromSDK;
function adaptMarketFromSDK(sdkMarket) {
    return {
        name: sdkMarket.name,
        szDecimals: sdkMarket.szDecimals,
        maxLeverage: sdkMarket.maxLeverage,
        marginTableId: sdkMarket.marginTableId,
        onlyIsolated: sdkMarket.onlyIsolated,
        isDelisted: sdkMarket.isDelisted,
    };
}
exports.adaptMarketFromSDK = adaptMarketFromSDK;
// Perps-only account adapter. Spot balances are layered on afterwards by
// addSpotBalanceToAccountState, which enforces the USDC-only policy via
// SPOT_COLLATERAL_COINS. Keeping spot logic out of here preserves a single
// source of truth for spot balance math.
function adaptAccountStateFromSDK(perpsState) {
    const { totalUnrealizedPnl, weightedReturnOnEquity } = perpsState.assetPositions.reduce((acc, assetPos) => {
        const unrealizedPnl = parseFloat(assetPos.position.unrealizedPnl || '0');
        const marginUsed = parseFloat(assetPos.position.marginUsed || '0');
        const returnOnEquity = parseFloat(assetPos.position.returnOnEquity || '0');
        acc.totalUnrealizedPnl += unrealizedPnl;
        acc.weightedReturnOnEquity += returnOnEquity * marginUsed;
        return acc;
    }, {
        totalUnrealizedPnl: 0,
        weightedReturnOnEquity: 0,
    });
    const totalMarginUsed = parseFloat(perpsState.marginSummary.totalMarginUsed || '0');
    const totalReturnOnEquityPercentage = totalMarginUsed > 0
        ? ((weightedReturnOnEquity / totalMarginUsed) * 100).toString()
        : '0';
    const perpsBalance = parseFloat(perpsState.marginSummary.accountValue);
    const withdrawable = perpsState.withdrawable || '0';
    const accountState = {
        spendableBalance: withdrawable,
        withdrawableBalance: withdrawable,
        totalBalance: perpsBalance.toString() || '0',
        marginUsed: perpsState.marginSummary.totalMarginUsed || '0',
        unrealizedPnl: totalUnrealizedPnl.toString() || '0',
        returnOnEquity: totalReturnOnEquityPercentage || '0',
    };
    return accountState;
}
exports.adaptAccountStateFromSDK = adaptAccountStateFromSDK;
function buildAssetMapping(params) {
    const { metaUniverse, perpDexIndex } = params;
    const symbolToAssetId = new Map();
    const assetIdToSymbol = new Map();
    metaUniverse.forEach((asset, index) => {
        const assetId = calculateHip3AssetId(perpDexIndex, index);
        symbolToAssetId.set(asset.name, assetId);
        assetIdToSymbol.set(assetId, asset.name);
    });
    return { symbolToAssetId, assetIdToSymbol };
}
exports.buildAssetMapping = buildAssetMapping;
function formatHyperLiquidPrice(params) {
    const { price, szDecimals } = params;
    const priceNum = typeof price === 'string' ? parseFloat(price) : price;
    if (Number.isInteger(priceNum)) {
        return priceNum.toString();
    }
    const maxDecimalPlaces = perpsConfig_1.DECIMAL_PRECISION_CONFIG.MaxPriceDecimals - szDecimals;
    let formattedPrice = priceNum.toFixed(maxDecimalPlaces);
    formattedPrice = parseFloat(formattedPrice).toString();
    const significantDigits = (0, significantFigures_1.countSignificantFigures)(formattedPrice);
    if (significantDigits > perpsConfig_1.DECIMAL_PRECISION_CONFIG.MaxSignificantFigures) {
        formattedPrice = (0, significantFigures_1.roundToSignificantFigures)(formattedPrice);
    }
    return formattedPrice;
}
exports.formatHyperLiquidPrice = formatHyperLiquidPrice;
function formatHyperLiquidSize(params) {
    const { size, szDecimals } = params;
    const number = typeof size === 'string' ? parseFloat(size) : size;
    if (isNaN(number)) {
        return '0';
    }
    const formatted = number.toFixed(szDecimals);
    if (!formatted.includes('.')) {
        return formatted;
    }
    return formatted.replace(/\.?0+$/u, '');
}
exports.formatHyperLiquidSize = formatHyperLiquidSize;
function calculatePositionSize(params) {
    const { usdValue, leverage, assetPrice } = params;
    return (usdValue * leverage) / assetPrice;
}
exports.calculatePositionSize = calculatePositionSize;
function calculateHip3AssetId(perpDexIndex, indexInMeta) {
    if (perpDexIndex === 0) {
        return indexInMeta;
    }
    return (hyperLiquidConfig_1.HIP3_ASSET_ID_CONFIG.BaseAssetId +
        perpDexIndex * hyperLiquidConfig_1.HIP3_ASSET_ID_CONFIG.DexMultiplier +
        indexInMeta);
}
exports.calculateHip3AssetId = calculateHip3AssetId;
function parseAssetName(assetName) {
    const colonIndex = assetName.indexOf(':');
    if (colonIndex === -1) {
        return { dex: null, symbol: assetName };
    }
    return {
        dex: assetName.substring(0, colonIndex),
        symbol: assetName.substring(colonIndex + 1),
    };
}
exports.parseAssetName = parseAssetName;
function adaptHyperLiquidLedgerUpdateToUserHistoryItem(rawLedgerUpdates) {
    return (rawLedgerUpdates || [])
        .filter((update) => {
        if (update.delta.type === 'deposit') {
            return true;
        }
        if (update.delta.type === 'withdraw') {
            return true;
        }
        if (update.delta.type === 'internalTransfer') {
            const usdc = Number.parseFloat(update.delta.usdc ?? '0');
            if (Number.isNaN(usdc)) {
                return false;
            }
            return usdc > 0;
        }
        return false;
    })
        .map((update) => {
        let amount = '0';
        let asset = 'USDC';
        if ((0, utils_1.hasProperty)(update.delta, 'usdc') && update.delta.usdc) {
            amount = Math.abs(parseFloat(update.delta.usdc)).toString();
        }
        if ((0, utils_1.hasProperty)(update.delta, 'coin') &&
            typeof update.delta.coin === 'string') {
            asset = update.delta.coin;
        }
        return {
            id: `history-${update.hash}`,
            timestamp: update.time,
            amount,
            asset,
            txHash: update.hash,
            status: 'completed',
            type: update.delta.type === 'withdraw' ? 'withdrawal' : 'deposit',
            details: {
                source: '',
                bridgeContract: undefined,
                recipient: undefined,
                blockNumber: undefined,
                chainId: undefined,
                synthetic: undefined,
            },
        };
    });
}
exports.adaptHyperLiquidLedgerUpdateToUserHistoryItem = adaptHyperLiquidLedgerUpdateToUserHistoryItem;
//# sourceMappingURL=hyperLiquidAdapter.cjs.map