"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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryStringify = exports.QUOTE_DECIMALS = exports.I64_MAX_BN = exports.U64_MAX_BN = void 0;
exports.bpsToDecimal = bpsToDecimal;
exports.percentageToDecimal = percentageToDecimal;
exports.toNativeI80F48ForQuote = toNativeI80F48ForQuote;
exports.toNativeI80F48 = toNativeI80F48;
exports.toNative = toNative;
exports.toNativeSellPerBuyTokenPrice = toNativeSellPerBuyTokenPrice;
exports.toUiSellPerBuyTokenPrice = toUiSellPerBuyTokenPrice;
exports.toUiDecimals = toUiDecimals;
exports.toUiDecimalsForQuote = toUiDecimalsForQuote;
exports.toUiI80F48 = toUiI80F48;
exports.roundTo5 = roundTo5;
exports.buildFetch = buildFetch;
exports.getAssociatedTokenAddress = getAssociatedTokenAddress;
exports.createAssociatedTokenAccountIdempotentInstruction = createAssociatedTokenAccountIdempotentInstruction;
exports.buildVersionedTx = buildVersionedTx;
exports.deepClone = deepClone;
const web3_js_1 = require("@solana/web3.js");
const bn_js_1 = __importDefault(require("bn.js"));
const I80F48_1 = require("./numbers/I80F48");
const spl_1 = require("./utils/spl");
///
/// numeric helpers
///
exports.U64_MAX_BN = new bn_js_1.default('18446744073709551615');
exports.I64_MAX_BN = new bn_js_1.default('9223372036854775807').toTwos(64);
function bpsToDecimal(bps) {
    return bps / 10000;
}
function percentageToDecimal(percentage) {
    return percentage / 100;
}
function toNativeI80F48ForQuote(uiAmount) {
    return I80F48_1.I80F48.fromNumber(uiAmount * Math.pow(10, 6));
}
function toNativeI80F48(uiAmount, decimals) {
    return I80F48_1.I80F48.fromNumber(uiAmount * Math.pow(10, decimals));
}
function toNative(uiAmount, decimals) {
    return new bn_js_1.default((uiAmount * Math.pow(10, decimals)).toFixed(0));
}
function toNativeSellPerBuyTokenPrice(price, sellBank, buyBank) {
    return price * Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals);
}
function toUiSellPerBuyTokenPrice(price, sellBank, buyBank) {
    return toUiDecimals(price, sellBank.mintDecimals - buyBank.mintDecimals);
}
function toUiDecimals(nativeAmount, decimals) {
    // TODO: remove BN and upgrade to bigint https://github.com/solana-labs/solana/issues/27440
    if (nativeAmount instanceof bn_js_1.default) {
        nativeAmount = I80F48_1.I80F48.fromU64(nativeAmount);
    }
    if (nativeAmount instanceof I80F48_1.I80F48) {
        return nativeAmount
            .div(I80F48_1.I80F48.fromNumber(Math.pow(10, decimals)))
            .toNumber();
    }
    return nativeAmount / Math.pow(10, decimals);
}
exports.QUOTE_DECIMALS = 6;
function toUiDecimalsForQuote(nativeAmount) {
    return toUiDecimals(nativeAmount, exports.QUOTE_DECIMALS);
}
function toUiI80F48(nativeAmount, decimals) {
    return nativeAmount.div(I80F48_1.I80F48.fromNumber(Math.pow(10, decimals)));
}
function roundTo5(number) {
    if (number < 1) {
        const numString = number.toString();
        const nonZeroIndex = numString.search(/[1-9]/);
        if (nonZeroIndex === -1 || nonZeroIndex >= numString.length - 5) {
            return number;
        }
        return Number(numString.slice(0, nonZeroIndex + 5));
    }
    else if (number < 10) {
        return (Math.floor(number) +
            Number((number % 1).toString().padEnd(10, '0').slice(0, 6)));
    }
    else if (number < 100) {
        return (Math.floor(number) +
            Number((number % 1).toString().padEnd(10, '0').slice(0, 5)));
    }
    else if (number < 1000) {
        return (Math.floor(number) +
            Number((number % 1).toString().padEnd(10, '0').slice(0, 4)));
    }
    else if (number < 10000) {
        return (Math.floor(number) +
            Number((number % 1).toString().padEnd(10, '0').slice(0, 3)));
    }
    return Math.round(number);
}
///
async function buildFetch() {
    let fetch = globalThis?.fetch;
    if (!fetch && process?.versions?.node) {
        fetch = (await Promise.resolve().then(() => __importStar(require('node-fetch')))).default;
    }
    return fetch;
}
///
///
/// web3js extensions
///
/**
 * Get the address of the associated token account for a given mint and owner
 *
 * @param mint                     Token mint account
 * @param owner                    Owner of the new account
 * @param allowOwnerOffCurve       Allow the owner account to be a PDA (Program Derived Address)
 * @param programId                SPL Token program account
 * @param associatedTokenProgramId SPL Associated Token program account
 *
 * @return Address of the associated token account
 */
async function getAssociatedTokenAddress(mint, owner, allowOwnerOffCurve = true, programId = spl_1.TOKEN_PROGRAM_ID, associatedTokenProgramId = spl_1.ASSOCIATED_TOKEN_PROGRAM_ID) {
    if (!allowOwnerOffCurve && !web3_js_1.PublicKey.isOnCurve(owner.toBuffer()))
        throw new Error('TokenOwnerOffCurve!');
    const [address] = await web3_js_1.PublicKey.findProgramAddress([owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], associatedTokenProgramId);
    return address;
}
async function createAssociatedTokenAccountIdempotentInstruction(payer, owner, mint) {
    const account = await getAssociatedTokenAddress(mint, owner);
    return new web3_js_1.TransactionInstruction({
        keys: [
            { pubkey: payer, isSigner: true, isWritable: true },
            { pubkey: account, isSigner: false, isWritable: true },
            { pubkey: owner, isSigner: false, isWritable: false },
            { pubkey: mint, isSigner: false, isWritable: false },
            {
                pubkey: web3_js_1.SystemProgram.programId,
                isSigner: false,
                isWritable: false,
            },
            { pubkey: spl_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
        ],
        programId: spl_1.ASSOCIATED_TOKEN_PROGRAM_ID,
        data: Buffer.from([0x1]),
    });
}
async function buildVersionedTx(provider, ix, additionalSigners = [], alts = []) {
    const message = web3_js_1.MessageV0.compile({
        payerKey: provider.wallet.publicKey,
        instructions: ix,
        recentBlockhash: (await provider.connection.getLatestBlockhash()).blockhash,
        addressLookupTableAccounts: alts,
    });
    const vTx = new web3_js_1.VersionedTransaction(message);
    vTx.sign([
        provider.wallet.payer,
        ...additionalSigners,
    ]);
    return vTx;
}
function deepClone(obj, hash = new WeakMap()) {
    // Handle non-object types and functions
    if (typeof obj !== 'object' || obj === null)
        return obj;
    // Handle circular references
    if (hash.has(obj))
        return hash.get(obj);
    let result;
    if (obj instanceof Map) {
        result = new Map();
        hash.set(obj, result);
        obj.forEach((value, key) => {
            result.set(deepClone(key, hash), deepClone(value, hash));
        });
    }
    else if (obj instanceof Set) {
        result = new Set();
        hash.set(obj, result);
        for (const item of obj) {
            result.add(deepClone(item, hash));
        }
    }
    else if (Array.isArray(obj)) {
        result = [];
        hash.set(obj, result);
        obj.forEach((item, index) => {
            result[index] = deepClone(item, hash);
        });
    }
    else {
        const prototype = Object.getPrototypeOf(obj);
        result = Object.create(prototype);
        hash.set(obj, result);
        for (const key of Object.keys(obj)) {
            result[key] = deepClone(obj[key], hash);
        }
    }
    return result;
}
const tryStringify = (val) => {
    try {
        return JSON.stringify(val);
    }
    catch (e) {
        return null;
    }
};
exports.tryStringify = tryStringify;
