import { PublicKey, TransactionMessage, VersionedTransaction, } from '@solana/web3.js';
import { buildFetch, createAssociatedTokenAccountIdempotentInstruction, } from './utils';
export const MANGO_ROUTER_API_URL = 'https://api.mngo.cloud/router/v1';
const fetchJupiterRoutes = async (inputMint, outputMint, amount = '0', slippage = 50, swapMode = 'ExactIn', feeBps = '0') => {
    {
        const paramsString = new URLSearchParams({
            inputMint: inputMint.toString(),
            outputMint: outputMint.toString(),
            amount: amount.toString(),
            slippageBps: Math.ceil(slippage * 100).toString(),
            feeBps: feeBps.toString(),
            swapMode,
        }).toString();
        const response = await (await buildFetch())(`https://quote-api.jup.ag/v4/quote?${paramsString}`);
        const res = await response.json();
        const data = res.data;
        return {
            routes: res.data,
            bestRoute: (data.length ? data[0] : null),
        };
    }
};
const fetchMangoRoutes = async (inputMint, outputMint, amount = '0', slippage = 50, swapMode = 'ExactIn', feeBps = '0', wallet = PublicKey.default) => {
    {
        const defaultOtherAmount = swapMode === 'ExactIn' ? 0 : Number.MAX_SAFE_INTEGER;
        const paramsString = new URLSearchParams({
            inputMint: inputMint.toString(),
            outputMint: outputMint.toString(),
            amount: amount.toString(),
            slippage: ((slippage * 1) / 100).toString(),
            feeBps: feeBps.toString(),
            mode: swapMode,
            wallet: wallet.toString(),
            otherAmountThreshold: defaultOtherAmount.toString(),
        }).toString();
        const response = await fetch(`${MANGO_ROUTER_API_URL}/swap?${paramsString}`);
        const res = await response.json();
        const data = res.map((route) => ({
            ...route,
            priceImpactPct: route.priceImpact,
            slippageBps: slippage,
            marketInfos: route.marketInfos.map((mInfo) => ({
                ...mInfo,
                lpFee: {
                    ...mInfo.fee,
                    pct: mInfo.fee.rate,
                },
            })),
            mints: route.mints.map((x) => new PublicKey(x)),
            instructions: route.instructions.map((ix) => ({
                ...ix,
                programId: new PublicKey(ix.programId),
                data: Buffer.from(ix.data, 'base64'),
                keys: ix.keys.map((key) => ({
                    ...key,
                    pubkey: new PublicKey(key.pubkey),
                })),
            })),
            routerName: 'Mango',
        }));
        return {
            routes: data,
            bestRoute: (data.length ? data[0] : null),
        };
    }
};
export const fetchRoutes = async (inputMint, outputMint, amount = '0', slippage = 50, swapMode = 'ExactIn', feeBps = '0', wallet = PublicKey.default) => {
    try {
        const responses = await Promise.allSettled([
            fetchMangoRoutes(inputMint, outputMint, amount, slippage, swapMode, feeBps, wallet),
            fetchJupiterRoutes(inputMint, outputMint, amount, slippage, swapMode, feeBps),
        ]);
        const routes = responses
            .filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
            .map((x) => x.value.routes)
            .flat();
        const sortedBestQuoteFirst = routes.sort((a, b) => swapMode == 'ExactIn'
            ? Number(b.outAmount) - Number(a.outAmount) // biggest out
            : Number(a.inAmount) - Number(b.inAmount));
        return {
            routes: sortedBestQuoteFirst,
            bestRoute: sortedBestQuoteFirst[0],
        };
    }
    catch (e) {
        return {
            routes: [],
            bestRoute: null,
        };
    }
};
export const prepareMangoRouterInstructions = async (selectedRoute, inputMint, outputMint, userPublicKey) => {
    if (!selectedRoute || !selectedRoute.mints || !selectedRoute.instructions) {
        return [[], []];
    }
    const mintsToFilterOut = [inputMint, outputMint];
    const filteredOutMints = [
        ...selectedRoute.mints.filter((routeMint) => !mintsToFilterOut.find((filterOutMint) => filterOutMint.equals(routeMint))),
    ];
    const additionalInstructions = [];
    for (const mint of filteredOutMints) {
        const ix = await createAssociatedTokenAccountIdempotentInstruction(userPublicKey, userPublicKey, mint);
        additionalInstructions.push(ix);
    }
    const instructions = [
        ...additionalInstructions,
        ...selectedRoute.instructions,
    ];
    return [instructions, []];
};
const deserializeJupiterIxAndAlt = async (connection, swapTransaction) => {
    const parsedSwapTransaction = VersionedTransaction.deserialize(Buffer.from(swapTransaction, 'base64'));
    const message = parsedSwapTransaction.message;
    // const lookups = message.addressTableLookups
    const addressLookupTablesResponses = await Promise.all(message.addressTableLookups.map((alt) => connection.getAddressLookupTable(alt.accountKey)));
    const addressLookupTables = addressLookupTablesResponses
        .map((alt) => alt.value)
        .filter((x) => x !== null);
    const decompiledMessage = TransactionMessage.decompile(message, {
        addressLookupTableAccounts: addressLookupTables,
    });
    return [decompiledMessage.instructions, addressLookupTables];
};
export const fetchJupiterTransaction = async (connection, selectedRoute, userPublicKey, slippage, inputMint, outputMint) => {
    const transactions = await (await (await buildFetch())('https://quote-api.jup.ag/v4/swap', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            // route from /quote api
            route: selectedRoute,
            // user public key to be used for the swap
            userPublicKey,
            // feeAccount is optional. Use if you want to charge a fee.  feeBps must have been passed in /quote API.
            // This is the ATA account for the output token where the fee will be sent to. If you are swapping from SOL->USDC then this would be the USDC ATA you want to collect the fee.
            // feeAccount: 'fee_account_public_key',
            slippageBps: Math.ceil(slippage * 100),
        }),
    })).json();
    const { swapTransaction } = transactions;
    const [ixs, alts] = await deserializeJupiterIxAndAlt(connection, swapTransaction);
    const isSetupIx = (pk) => pk.toString() === 'ComputeBudget111111111111111111111111111111' ||
        pk.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
    const isDuplicateAta = (ix) => {
        return (ix.programId.toString() ===
            'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' &&
            (ix.keys[3].pubkey.toString() === inputMint.toString() ||
                ix.keys[3].pubkey.toString() === outputMint.toString()));
    };
    const filtered_jup_ixs = ixs
        .filter((ix) => !isSetupIx(ix.programId))
        .filter((ix) => !isDuplicateAta(ix));
    return [filtered_jup_ixs, alts];
};
