import { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';
import { getDefaultResolver } from '../../config.js';
import { Buffer } from '../../lib/utils/buffer.js';
import crypto from '../../lib/utils/crypto.js';
import { validateURL } from '../../lib/utils/url.js';
import { assertKeetaNetTokenPublicKeyString, isKeetaFXAnchorEstimateResponse, isKeetaFXAnchorExchangeResponse, isKeetaFXAnchorQuoteResponse, Errors as FXErrors } from './common.js';
import { KeetaAnchorError, KeetaAnchorUserError } from '../../lib/error.js';
import { resolveSharedAnchorMetadataLegalExtension } from '../../lib/metadata.types.js';
function typedFxServiceEntries(obj) {
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return Object.entries(obj);
}
const KeetaFXAnchorClientAccessToken = Symbol('KeetaFXAnchorClientAccessToken');
async function getEndpoints(resolver, request, _ignored_account, sharedCriteria, options) {
    const criteria = {};
    if (request.from !== undefined) {
        criteria.inputCurrencyCode = request.from.publicKeyString.get();
    }
    if (request.to !== undefined) {
        criteria.outputCurrencyCode = request.to.publicKeyString.get();
    }
    if (request.requiredOperations) {
        criteria.requiredOperations = request.requiredOperations;
    }
    if (request.affinity !== undefined) {
        criteria.supportedAffinity = request.affinity;
    }
    const response = await resolver.lookup('fx', {
        ...criteria
        // kycProviders: 'TODO' XXX:TODO
    }, sharedCriteria);
    if (response === undefined) {
        return (null);
    }
    const serviceInfoPromises = Object.entries(response).map(async function ([id, serviceInfo]) {
        const operations = await serviceInfo.operations('object');
        const operationsFunctions = {};
        for (const [key, operation] of Object.entries(operations)) {
            if (operation === undefined) {
                continue;
            }
            Object.defineProperty(operationsFunctions, key, {
                get: async function () {
                    const url = await operation('string');
                    return (function (params) {
                        let substitutedURL = url;
                        for (const [paramKey, paramValue] of Object.entries(params ?? {})) {
                            substitutedURL = substitutedURL.replace(`{${paramKey}}`, encodeURIComponent(paramValue));
                        }
                        return (validateURL(substitutedURL));
                    });
                },
                enumerable: true,
                configurable: true
            });
        }
        const fromInfo = await serviceInfo.from('array');
        const allFrom = await Promise.all(fromInfo.map(async function (fromFn) {
            const from = await fromFn('object');
            const currencyCodes = await Promise.all((await from.currencyCodes('array')).map(async (currencyCode) => {
                const code = await currencyCode('string');
                return (assertKeetaNetTokenPublicKeyString(code));
            }));
            const to = await Promise.all((await from.to('array')).map(async (currencyCode) => {
                const code = await currencyCode('string');
                return (assertKeetaNetTokenPublicKeyString(code));
            }));
            const kycProvidersEntry = (await from.kycProviders?.('array'))?.map(async (providerFn) => {
                const provider = await providerFn('string');
                return (provider);
            });
            // eslint-disable-next-line @typescript-eslint/no-empty-object-type
            let kycProviders = {};
            if (kycProvidersEntry && kycProvidersEntry.length > 0) {
                kycProviders = { kycProviders: await Promise.all(kycProvidersEntry) };
            }
            return ({ currencyCodes, to, ...kycProviders });
        }));
        return ([
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            id,
            {
                ...(await resolveSharedAnchorMetadataLegalExtension(serviceInfo.legal, { logger: options?.logger })),
                // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                operations: operationsFunctions,
                from: allFrom
            }
        ]);
    });
    if (serviceInfoPromises.length === 0) {
        return (null);
    }
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const retval = Object.fromEntries(await Promise.all(serviceInfoPromises));
    return (retval);
}
class KeetaFXAnchorBase {
    logger;
    client;
    constructor(config) {
        this.client = config.client;
        this.logger = config.logger;
    }
}
export class KeetaFXAnchorProviderBase extends KeetaFXAnchorBase {
    serviceInfo;
    providerID;
    conversion;
    options;
    parent;
    constructor(serviceInfo, providerID, conversion, parent, options) {
        const parentPrivate = parent._internals(KeetaFXAnchorClientAccessToken);
        super(parentPrivate);
        this.serviceInfo = serviceInfo;
        this.providerID = providerID;
        this.conversion = conversion;
        this.parent = parent;
        this.options = options;
    }
    #parseConversionRequest(input) {
        return ({
            from: KeetaNetLib.Account.fromPublicKeyString(input.from),
            to: KeetaNetLib.Account.fromPublicKeyString(input.to),
            amount: BigInt(input.amount),
            affinity: input.affinity
        });
    }
    async #parseResponseError(data) {
        if (typeof data !== 'object' || data === null) {
            throw (new Error('Response is not an error'));
        }
        if (!('ok' in data) || data.ok) {
            throw (new Error('Response is not an error'));
        }
        let errorStr;
        try {
            return (await KeetaAnchorError.fromJSON(data));
        }
        catch (error) {
            this.logger?.debug('Failed to parse error response as KeetaAnchorError', error, data);
        }
        if ('error' in data && typeof data.error === 'string') {
            errorStr = data.error;
        }
        else {
            errorStr = 'Unknown error';
        }
        return (new Error(`FX request failed: ${errorStr}`));
    }
    async #getEndpoint(operation, params) {
        const operationFn = await this.serviceInfo.operations[operation];
        if (operationFn === undefined) {
            throw (new KeetaAnchorUserError(`Provider ${String(this.providerID)} does not support "${operation}" operation`));
        }
        return (operationFn(params));
    }
    async getEstimate() {
        const estimateURL = await this.#getEndpoint('getEstimate');
        const requestInformation = await fetch(estimateURL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                request: KeetaNetLib.Utils.Conversion.toJSONSerializable(this.conversion)
            })
        });
        const requestInformationJSON = await requestInformation.json();
        if (!isKeetaFXAnchorEstimateResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from FX estimate service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (await this.#parseResponseError(requestInformationJSON));
        }
        this.logger?.debug(`FX estimate request successful, to provider ${estimateURL} for ${JSON.stringify(KeetaNetLib.Utils.Conversion.toJSONSerializable(this.conversion))}`);
        const estimateJSON = requestInformationJSON.estimate;
        return ({
            request: this.#parseConversionRequest(estimateJSON.request),
            convertedAmount: BigInt(estimateJSON.convertedAmount),
            expectedCost: {
                min: BigInt(estimateJSON.expectedCost.min),
                max: BigInt(estimateJSON.expectedCost.max),
                token: KeetaNetLib.Account.fromPublicKeyString(estimateJSON.expectedCost.token)
            },
            ...(estimateJSON.convertedAmountBound !== undefined ? { convertedAmountBound: BigInt(estimateJSON.convertedAmountBound) } : {}),
            ...(() => {
                if (estimateJSON.canPerformExchange === false) {
                    return ({ canPerformExchange: false });
                }
                else if (estimateJSON.requiresQuote === undefined) {
                    return ({});
                }
                else if (estimateJSON.requiresQuote) {
                    return ({ requiresQuote: true });
                    // We have to disable this as doing !estimateJSON.requiresQuote breaks the compiler, and that is what eslint wants us to do
                    // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
                }
                else if (estimateJSON.requiresQuote === false) {
                    return ({
                        requiresQuote: false,
                        account: KeetaNetLib.Account.fromPublicKeyString(estimateJSON.account)
                    });
                }
            })()
        });
    }
    /**
     * Get a quote from the provider.  If an estimate is provided, it will
     * be used to validate the quote is within the tolerance range.
     *
     * @param estimate An optional estimate to validate the quote against
     * @param tolerance The tolerance, in percentage points, to allow the
     *                  quote to vary from the estimate.  For example, a
     *                  tolerance of 1.0 allows the quote to be 100% more
     *                  or less than the estimate.  The default is 0.10
     *                  (10%).
     * @returns A promise that resolves to the quote response
     */
    async getQuote(estimate, tolerance = 0.1) {
        const serviceURL = await this.#getEndpoint('getQuote');
        const requestInformation = await fetch(serviceURL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                request: KeetaNetLib.Utils.Conversion.toJSONSerializable(this.conversion)
            })
        });
        const requestInformationJSON = await requestInformation.json();
        if (!isKeetaFXAnchorQuoteResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from FX quote service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (await this.#parseResponseError(requestInformationJSON));
        }
        if (estimate !== undefined && tolerance !== undefined) {
            const quoteAmount = BigInt(requestInformationJSON.quote.convertedAmount);
            const estimateAmount = BigInt(estimate.convertedAmount);
            const lowerBound = estimateAmount * BigInt(Math.round((1 - tolerance) * 100)) / 100n;
            const upperBound = estimateAmount * BigInt(Math.round((1 + tolerance) * 100)) / 100n;
            if (quoteAmount > upperBound || quoteAmount < lowerBound) {
                throw (new Error(`FX Quote amount: ${requestInformationJSON.quote.convertedAmount} differs more than tolerance limit: ${tolerance} from estimate`));
            }
        }
        this.logger?.debug(`FX quote request successful, to provider ${serviceURL} for ${JSON.stringify(KeetaNetLib.Utils.Conversion.toJSONSerializable(this.conversion))}`);
        const quoteJSON = requestInformationJSON.quote;
        return ({
            request: this.#parseConversionRequest(quoteJSON.request),
            account: KeetaNetLib.Account.fromPublicKeyString(quoteJSON.account),
            convertedAmount: BigInt(quoteJSON.convertedAmount),
            cost: {
                amount: BigInt(quoteJSON.cost.amount),
                token: KeetaNetLib.Account.fromPublicKeyString(quoteJSON.cost.token)
            },
            signed: quoteJSON.signed
        });
    }
    async createExchange(input, block) {
        let swapBlock = block;
        if (swapBlock === undefined) {
            /* Liquidity Provider that will complete the swap */
            let liquidityProvider;
            let request;
            let convertedAmountBound;
            if ('estimate' in input) {
                if (input.estimate.canPerformExchange === false) {
                    throw (new KeetaAnchorUserError('The provided estimate indicates that the exchange cannot be performed'));
                }
                if (input.estimate.requiresQuote !== false) {
                    throw (new FXErrors.QuoteRequired());
                }
                liquidityProvider = input.estimate.account;
                request = input.estimate.request;
                if (input.estimate.convertedAmountBound !== undefined) {
                    convertedAmountBound = input.estimate.convertedAmountBound;
                }
                else {
                    convertedAmountBound = input.estimate.convertedAmount;
                }
            }
            else {
                liquidityProvider = input.quote.account;
                request = input.quote.request;
                convertedAmountBound = input.quote.convertedAmount;
            }
            let sendAmount;
            let receiveAmount;
            if (request.affinity === 'to') {
                sendAmount = convertedAmountBound;
                receiveAmount = request.amount;
            }
            else if (request.affinity === 'from') {
                sendAmount = request.amount;
                receiveAmount = convertedAmountBound;
            }
            else {
                throw (new Error('Invalid affinity in conversion request'));
            }
            /* Construct the required operations for the swap request */
            const builder = this.client.initBuilder(this.options);
            if ('quote' in input) {
                /* If cost is required then send the required amount as well */
                if (input.quote.cost.amount > 0) {
                    builder.send(liquidityProvider, input.quote.cost.amount, input.quote.cost.token);
                }
            }
            else if ('estimate' in input) {
                if (input.estimate.expectedCost.max > 0) {
                    builder.send(liquidityProvider, input.estimate.expectedCost.max, input.estimate.expectedCost.token);
                }
            }
            builder.receive(liquidityProvider, receiveAmount, request.to, request.affinity === 'to');
            builder.send(liquidityProvider, sendAmount, request.from);
            const blocks = await builder.computeBlocks();
            if (blocks.blocks.length !== 1) {
                throw (new Error('Creating Swap Generated more than 1 block'));
            }
            swapBlock = blocks.blocks[0];
        }
        if (swapBlock == undefined) {
            throw (new Error('User Swap Block is undefined'));
        }
        let bodyAdditionalData;
        if ('quote' in input) {
            bodyAdditionalData = { quote: input.quote };
        }
        else {
            bodyAdditionalData = { request: input.estimate.request };
        }
        const serviceURL = await this.#getEndpoint('createExchange');
        const requestInformation = await fetch(serviceURL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                request: {
                    ...(KeetaNetLib.Utils.Conversion.toJSONSerializable(bodyAdditionalData)),
                    block: Buffer.from(swapBlock.toBytes()).toString('base64')
                }
            })
        });
        const requestInformationJSON = await requestInformation.json();
        // Ensure status defaults to 'completed' if not provided (for backward compatibility)
        if (typeof requestInformationJSON === 'object' && requestInformationJSON !== null && !('status' in requestInformationJSON)) {
            Object.assign(requestInformationJSON, { status: 'completed' });
            Object.assign(requestInformationJSON, { blockhash: swapBlock.hash.toString() });
        }
        // Validate response
        if (!isKeetaFXAnchorExchangeResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from FX exchange service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (await this.#parseResponseError(requestInformationJSON));
        }
        this.logger?.debug(`FX exchange request successful, to provider ${serviceURL} for ${swapBlock.hash.toString()}`);
        return (requestInformationJSON);
    }
    async getExchangeStatus(exchangeID) {
        const serviceURL = await this.#getEndpoint('getExchangeStatus', { id: exchangeID });
        const requestInformation = await fetch(serviceURL, {
            method: 'GET',
            headers: {
                'Accept': 'application/json'
            }
        });
        const requestInformationJSON = await requestInformation.json();
        if (!isKeetaFXAnchorExchangeResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from FX exchange status service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (await this.#parseResponseError(requestInformationJSON));
        }
        this.logger?.debug(`FX exchange status request successful, to provider ${serviceURL} for ${exchangeID}`);
        return (requestInformationJSON);
    }
    /** @internal */
    _internals(accessToken) {
        if (accessToken !== KeetaFXAnchorClientAccessToken) {
            throw (new Error('invalid access token'));
        }
        return ({
            parent: this.parent
        });
    }
}
/*
 * Various classes for the state machine:
 *   Estimate(optional) -> Quote(optional) -> Exchange -> ExchangeStatus
 */
class KeetaFXAnchorExchangeWithProvider {
    provider;
    exchange;
    constructor(provider, exchange) {
        this.provider = provider;
        this.exchange = exchange;
    }
    async getExchangeStatus() {
        /* XXX:TODO: This should return something useful -- right now  it just returns the exchange ID... */
        return (await this.provider.getExchangeStatus(this.exchange.exchangeID));
    }
}
class KeetaFXAnchorQuoteWithProvider {
    provider;
    quote;
    isQuote = true;
    constructor(provider, quote) {
        this.provider = provider;
        this.quote = quote;
    }
    get request() {
        return (this.quote.request);
    }
    async createExchange(block) {
        const exchange = await this.provider.createExchange({ quote: this.quote }, block);
        return (new KeetaFXAnchorExchangeWithProvider(this.provider, exchange));
    }
    /**
     * Re-fetch the quote from the provider.
     *
     * @returns a new KeetaFXAnchorQuoteWithProvider with the updated quote
     */
    async refetch() {
        const quote = await this.provider.getQuote();
        return (new KeetaFXAnchorQuoteWithProvider(this.provider, quote));
    }
}
class KeetaFXAnchorEstimateWithProvider {
    provider;
    estimate;
    isQuote = false;
    constructor(provider, estimate) {
        this.provider = provider;
        this.estimate = estimate;
    }
    get request() {
        return (this.estimate.request);
    }
    async getQuote(tolerance) {
        const quote = await this.provider.getQuote(this.estimate, tolerance);
        return (new KeetaFXAnchorQuoteWithProvider(this.provider, quote));
    }
    async createExchange(block) {
        const exchange = await this.provider.createExchange({ estimate: this.estimate }, block);
        return (new KeetaFXAnchorExchangeWithProvider(this.provider, exchange));
    }
    /**
     * Re-fetch the estimate from the provider.
     *
     * @returns a new KeetaFXAnchorEstimateWithProvider with the updated estimate
     */
    async refetch() {
        const estimate = await this.provider.getEstimate();
        return (new KeetaFXAnchorEstimateWithProvider(this.provider, estimate));
    }
}
class KeetaFXAnchorClient extends KeetaFXAnchorBase {
    resolver;
    id;
    #signer;
    #account;
    constructor(client, config = {}) {
        super({ client, logger: config.logger });
        this.resolver = config.resolver ?? getDefaultResolver(client, config);
        this.id = config.id ?? crypto.randomUUID();
        if (config.signer) {
            this.#signer = config.signer;
        }
        else if ('signer' in client && client.signer !== null) {
            this.#signer = client.signer;
        }
        else if ('account' in client && client.account.hasPrivateKey) {
            this.#signer = client.account;
        }
        else {
            throw (new Error('KeetaFXAnchorClient requires a Signer or a UserClient with an associated Signer'));
        }
        if (config.account) {
            this.#account = config.account;
        }
        else if ('account' in client) {
            this.#account = client.account;
        }
        else {
            throw (new Error('KeetaFXAnchorClient requires an Account or a UserClient with an associated Account'));
        }
    }
    async canonicalizeConversionTokens(input) {
        let from = {};
        if (input.from !== undefined) {
            let fromToken;
            if (KeetaNetLib.Account.isInstance(input.from) && input.from.isToken()) {
                fromToken = input.from;
            }
            else {
                const tokenLookup = await this.resolver.lookupToken(input.from);
                if (tokenLookup === null) {
                    // eslint-disable-next-line @typescript-eslint/no-base-to-string
                    throw (new Error(`Could not convert from: ${input.from} to a token address`));
                }
                fromToken = KeetaNetLib.Account.fromPublicKeyString(tokenLookup.token);
            }
            from = { from: fromToken };
        }
        let to = {};
        if (input.to !== undefined) {
            let toToken;
            if (KeetaNetLib.Account.isInstance(input.to) && input.to.isToken()) {
                toToken = input.to;
            }
            else {
                const tokenLookup = await this.resolver.lookupToken(input.to);
                if (tokenLookup === null) {
                    // eslint-disable-next-line @typescript-eslint/no-base-to-string
                    throw (new Error(`Could not convert to: ${input.to} to a token address`));
                }
                toToken = KeetaNetLib.Account.fromPublicKeyString(tokenLookup.token);
            }
            to = { to: toToken };
        }
        return ({ ...from, ...to });
    }
    async canonicalizeConversionInput(input) {
        const amount = BigInt(input.amount);
        if (amount < 0) {
            throw (new Error('Invalid Conversion Amount'));
        }
        const { from, to } = await this.canonicalizeConversionTokens(input);
        if (from === undefined || to === undefined) {
            throw (new Error('From and To are both required for a conversion'));
        }
        const canonical = {
            from,
            to,
            amount: amount,
            affinity: input.affinity
        };
        if (input.preferredCostAsset !== undefined) {
            canonical.preferredCostAsset = input.preferredCostAsset;
        }
        return (canonical);
    }
    async listPossibleConversions(input, options = {}, sharedCriteria) {
        if (input.from !== undefined && input.to !== undefined) {
            throw (new KeetaAnchorUserError('Only one of from or two should be provided'));
        }
        if (input.from === undefined && input.to === undefined) {
            throw (new KeetaAnchorUserError('At least one of from or two should be provided'));
        }
        const conversion = await this.canonicalizeConversionTokens(input);
        const account = options.account ?? this.#account;
        const providerEndpoints = await getEndpoints(this.resolver, conversion, account, sharedCriteria, { logger: this.logger });
        if (providerEndpoints === null) {
            return (null);
        }
        const conversions = new Set();
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        for (const [_ignored_providerID, serviceInfo] of typedFxServiceEntries(providerEndpoints)) {
            for (const conversionPair of serviceInfo.from) {
                if (conversion.from !== undefined) {
                    if (conversionPair.currencyCodes.includes(conversion.from.publicKeyString.get())) {
                        conversionPair.to.forEach((token) => {
                            if (conversion.from?.publicKeyString.get() !== token) {
                                conversions.add(token);
                            }
                        });
                    }
                }
                else if (conversion.to !== undefined) {
                    if (conversionPair.to.includes(conversion.to.publicKeyString.get())) {
                        conversionPair.currencyCodes.forEach((token) => {
                            if (conversion.to?.publicKeyString.get() !== token) {
                                conversions.add(token);
                            }
                        });
                    }
                }
            }
        }
        ;
        return ({ conversions: [...conversions] });
    }
    async getBaseProvidersForConversion(request, options = {}, sharedCriteria) {
        const conversion = await this.canonicalizeConversionInput(request);
        const account = options.account ?? this.#account;
        const providerEndpoints = await getEndpoints(this.resolver, conversion, account, sharedCriteria, { logger: this.logger });
        if (providerEndpoints === null) {
            return (null);
        }
        const providers = typedFxServiceEntries(providerEndpoints).map(([providerID, serviceInfo]) => {
            return (new KeetaFXAnchorProviderBase(serviceInfo, providerID, conversion, this, options));
        });
        return (providers);
    }
    async getEstimates(request, options = {}, sharedCriteria) {
        const estimateProviders = await this.getBaseProvidersForConversion(request, {
            ...options,
            requiredOperations: ['getEstimate']
        }, sharedCriteria);
        if (estimateProviders === null) {
            return (null);
        }
        const estimates = await Promise.all(estimateProviders.map(async (provider) => {
            try {
                const estimate = await provider.getEstimate();
                return (new KeetaFXAnchorEstimateWithProvider(provider, estimate));
            }
            catch (error) {
                this.logger?.error(`Failed to get estimate from provider ${String(provider.providerID)}:`, error);
                return (null);
            }
        }));
        const results = estimates.filter(function (result) {
            return (result !== null);
        });
        if (results.length === 0) {
            return (null);
        }
        return (results);
    }
    async #multiRequestQuotes(request, options = {}, sharedCriteria) {
        const estimateProviders = await this.getBaseProvidersForConversion(request, {
            ...options,
            requiredOperations: ['getQuote']
        }, sharedCriteria);
        if (estimateProviders === null) {
            return ([]);
        }
        return (await Promise.all(estimateProviders.map(async (provider) => {
            try {
                const quote = await provider.getQuote();
                return ({ provider, quote: new KeetaFXAnchorQuoteWithProvider(provider, quote) });
            }
            catch (error) {
                return ({ provider, quote: null, error });
            }
        })));
    }
    async getQuotes(request, options = {}, sharedCriteria) {
        const quotes = await this.#multiRequestQuotes(request, options, sharedCriteria);
        const results = quotes
            .map(function (quote) {
            return (quote.quote);
        })
            .filter(function (quote) {
            return (quote !== null);
        });
        if (results.length === 0) {
            return (null);
        }
        return (results);
    }
    async getQuotesOrEstimates(request, options = {}, sharedCriteria) {
        const [quotesAndEstimates, estimateProviders] = await Promise.all([
            this.#multiRequestQuotes(request, options, sharedCriteria),
            this.getBaseProvidersForConversion(request, {
                ...options,
                requiredOperations: ['getEstimate']
            }, sharedCriteria)
        ]);
        const estimateProviderIDs = new Set();
        if (estimateProviders !== null) {
            for (const provider of estimateProviders) {
                estimateProviderIDs.add(provider.providerID);
            }
        }
        const retval = [];
        for (const quoteOrEstimate of quotesAndEstimates) {
            if (!quoteOrEstimate.quote) {
                if (quoteOrEstimate.error && !FXErrors.QuoteIssuanceDisabled.isInstance(quoteOrEstimate.error)) {
                    this.logger?.debug(`Failed to get quote from provider ${String(quoteOrEstimate.provider.providerID)}:`, quoteOrEstimate.error);
                }
                continue;
            }
            retval.push(quoteOrEstimate.quote);
            estimateProviderIDs.delete(quoteOrEstimate.provider.providerID);
        }
        const estimates = await this.getEstimates(request, options, {
            ...sharedCriteria,
            providerIDs: Array.from(estimateProviderIDs).map(function (id) {
                return (String(id));
            })
        });
        if (estimates !== null) {
            retval.push(...estimates);
        }
        if (retval.length === 0) {
            return (null);
        }
        return (retval);
    }
    async getPrices(input) {
        const priceInAsset = input.priceIn ?? 'USD';
        const retval = new Map();
        await Promise.all(input.assets.map(async (asset) => {
            let conversionValue;
            if (input.conversionValue !== undefined) {
                if (typeof input.conversionValue === 'function') {
                    conversionValue = await input.conversionValue(asset);
                }
                else {
                    conversionValue = input.conversionValue;
                }
            }
            else {
                conversionValue = 1n;
            }
            const estimates = await this.getEstimates({
                from: asset,
                to: priceInAsset,
                amount: conversionValue,
                affinity: input.conversionAffinity ?? 'from'
            });
            if (estimates === null || estimates.length === 0) {
                retval.set(asset, null);
            }
            else {
                const convertedAmountSum = estimates.reduce(function (sum, estimate) {
                    return (sum + estimate.estimate.convertedAmount);
                }, 0n);
                const averageConvertedAmount = Number(convertedAmountSum) / estimates.length;
                retval.set(asset, {
                    value: conversionValue,
                    averageConvertedAmount,
                    providerEstimates: estimates
                });
            }
        }));
        return (retval);
    }
    async getLegalDisclaimersById(providerID) {
        const endpoints = await getEndpoints(this.resolver, {}, this.#account, { providerIDs: [providerID] }, { logger: this.logger });
        if (endpoints === null) {
            return (null);
        }
        const serviceEntry = typedFxServiceEntries(endpoints)[0];
        if (!serviceEntry) {
            return (null);
        }
        const disclaimers = serviceEntry[1].legal?.disclaimers;
        if (!disclaimers || disclaimers.length === 0) {
            return (null);
        }
        return (disclaimers);
    }
    /** @internal */
    _internals(accessToken) {
        if (accessToken !== KeetaFXAnchorClientAccessToken) {
            throw (new Error('invalid access token'));
        }
        return ({
            resolver: this.resolver,
            logger: this.logger,
            client: this.client,
            signer: this.#signer,
            account: this.#account
        });
    }
}
export default KeetaFXAnchorClient;
//# sourceMappingURL=client.js.map