import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
import * as __typia_transform__accessExpressionAsString from "typia/lib/internal/_accessExpressionAsString.js";
import { getDefaultResolver } from '../../config.js';
import { assertKeetaSupportedAssetsMetadataItem, convertAssetLocationToString, convertAssetOrPairSearchInputToCanonical, convertAssetSearchInputToCanonical, getKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequestSigningData, isKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateResponse, getKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequestSigningData, getKeetaAssetMovementAnchorCreatePersistentForwardingRequestSigningData, getKeetaAssetMovementAnchorExecuteTransferRequestSigningData, getKeetaAssetMovementAnchorGetTransferStatusRequestSigningData, getKeetaAssetMovementAnchorInitiateTransferRequestSigningData, getKeetaAssetMovementAnchorListForwardingAddressTemplateRequestSigningData, getKeetaAssetMovementAnchorListPersistentForwardingRequestSigningData, getKeetaAssetMovementAnchorlistTransactionsRequestSigningData, getKeetaAssetMovementAnchorShareKYCRequestSigningData, isKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateResponse, isKeetaAssetMovementAnchorCreatePersistentForwardingResponse, isKeetaAssetMovementAnchorExecuteTransferResponse, isKeetaAssetMovementAnchorGetExchangeStatusResponse, isKeetaAssetMovementAnchorInitiateTransferResponse, isKeetaAssetMovementAnchorListForwardingAddressTemplateResponse, isKeetaAssetMovementAnchorListPersistentForwardingResponse, isKeetaAssetMovementAnchorlistPersistentForwardingTransactionsResponse, isKeetaAssetMovementAnchorShareKYCResponse, toAssetLocationFromString, isExternalChainAsset, isAnchorTokenLocationMetadata, isKeetaAssetMovementAnchorSimulateTransferResponse, getKeetaAssetMovementAnchorSimulateTransferRequestSigningData, Errors } from './common.js';
import Resolver from "../../lib/resolver.js";
import crypto from '../../lib/utils/crypto.js';
import { createAssertEquals } from 'typia';
import { addSignatureToURL } from '../../lib/http-server/common.js';
import { SignData } from '../../lib/utils/signing.js';
import { KeetaAnchorError } from '../../lib/error.js';
import { KeetaNet } from '../../client/index.js';
import { resolveSharedAnchorMetadataLegalExtension } from '../../lib/metadata.types.js';
/**
 * An opaque type that represents an Asset Movement Anchor request ID
 */
// type RequestID = BrandedString<'AssetMovementRequestID'>;
const KeetaAssetMovementAnchorClientAccessToken = Symbol('KeetaAssetMovementAnchorClientAccessToken');
function typedAssetMovementServiceEntries(obj) {
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return Object.entries(obj);
}
const assertServiceMetadataEndpoint = (() => { const _io0 = (input, _exceptionable = true) => "string" === typeof input.url && (undefined === input.options || "object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) && _io1(input.options, true && _exceptionable)) && (1 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["url", "options"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _io1 = (input, _exceptionable = true) => (undefined === input.authentication || "object" === typeof input.authentication && null !== input.authentication && _io2(input.authentication, true && _exceptionable)) && (0 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["authentication"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _io2 = (input, _exceptionable = true) => "keeta-account" === input.method && ("optional" === input.type || "required" === input.type || "none" === input.type) && (2 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["method", "type"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.url || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".url",
    expected: "string",
    value: input.url
}, _errorFactory)) && (undefined === input.options || ("object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".options",
    expected: "(__type.o1 | undefined)",
    value: input.options
}, _errorFactory)) && _ao1(input.options, _path + ".options", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".options",
    expected: "(__type.o1 | undefined)",
    value: input.options
}, _errorFactory)) && (1 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["url", "options"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const _ao1 = (input, _path, _exceptionable = true) => (undefined === input.authentication || ("object" === typeof input.authentication && null !== input.authentication || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".authentication",
    expected: "(ServiceMetadataAuthenticationType | undefined)",
    value: input.authentication
}, _errorFactory)) && _ao2(input.authentication, _path + ".authentication", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".authentication",
    expected: "(ServiceMetadataAuthenticationType | undefined)",
    value: input.authentication
}, _errorFactory)) && (0 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["authentication"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const _ao2 = (input, _path, _exceptionable = true) => ("keeta-account" === input.method || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".method",
    expected: "\"keeta-account\"",
    value: input.method
}, _errorFactory)) && ("optional" === input.type || "required" === input.type || "none" === input.type || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".type",
    expected: "(\"none\" | \"optional\" | \"required\")",
    value: input.type
}, _errorFactory)) && (2 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["method", "type"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const __is = (input, _exceptionable = true) => null !== input && undefined !== input && ("string" === typeof input || "object" === typeof input && null !== input && _io0(input, true)); let _errorFactory; return (input, errorFactory) => {
    if (false === __is(input)) {
        _errorFactory = errorFactory;
        ((input, _path, _exceptionable = true) => (null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && (undefined !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && ("string" === typeof input || ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)))(input, "$input", true);
    }
    return input;
}; })();
;
function validateURL(url) {
    if (url === undefined || url === null) {
        throw (new Error('Invalid URL: null or undefined'));
    }
    const parsedURL = new URL(url);
    return (parsedURL);
}
async function getEndpoints(resolver, request, shared, logger) {
    const asset = request.asset ? { asset: convertAssetOrPairSearchInputToCanonical(request.asset) } : undefined;
    const from = request.from ? { from: convertAssetLocationToString(request.from) } : {};
    const to = request.to ? { to: convertAssetLocationToString(request.to) } : {};
    const rail = request.rail ? { rail: request.rail } : {};
    const response = await resolver.lookup('assetMovement', {
        ...asset,
        ...from,
        ...to,
        ...rail
    }, shared);
    if (response === undefined) {
        return (null);
    }
    const serviceInfoPromises = Object.entries(response).map(async function ([id, serviceInfo]) {
        const supportedAssetsMetadata = await Resolver.Metadata.fullyResolveValuizable(serviceInfo.supportedAssets);
        if (!Array.isArray(supportedAssetsMetadata)) {
            throw (new Error('Invalid supportedAssets metadata: expected an array'));
        }
        const supportedAssets = [];
        for (const item of supportedAssetsMetadata) {
            try {
                supportedAssets.push(assertKeetaSupportedAssetsMetadataItem(item));
            }
            catch (error) {
                logger?.debug('getEndpoints', `Failed to resolve supportedAssets metadata item for provider ${id}`, error, item);
            }
        }
        const locationMetadata = await (async () => {
            let locationMetadataVal;
            if (serviceInfo.locationMetadata) {
                locationMetadataVal = await serviceInfo.locationMetadata('object');
            }
            if (!locationMetadataVal) {
                return (undefined);
            }
            const chainsResult = await Promise.allSettled(Object.entries(locationMetadataVal).map(async ([location, assetsValue]) => {
                const parsedLocation = toAssetLocationFromString(location);
                if (parsedLocation.type !== 'chain') {
                    throw (new Error(`Invalid location type in AssetLocation string: ${parsedLocation.type}`));
                }
                if (parsedLocation.chain.type === 'keeta') {
                    throw (new Error('Keeta chain type is not supported in AssetLocation metadata'));
                }
                const chainType = parsedLocation.chain.type;
                // We can assert here as we have validated the chain type in the parsing function
                // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                const locationString = convertAssetLocationToString(parsedLocation);
                let resolvedAssetsObject = undefined;
                if (assetsValue) {
                    resolvedAssetsObject = await assetsValue('object');
                }
                if (!resolvedAssetsObject) {
                    return (null);
                }
                let assets;
                if (resolvedAssetsObject.assets) {
                    const assetsValue = await resolvedAssetsObject.assets('object');
                    const resolvedAssets = await Promise.allSettled(Object.entries(assetsValue).map(async ([assetId, assetMetadata]) => {
                        if (!isExternalChainAsset(assetId, chainType)) {
                            throw (new Error(`Invalid asset ID for chain type ${chainType}: ${assetId}`));
                        }
                        let assetMetadataVal;
                        if (assetMetadata) {
                            assetMetadataVal = await assetMetadata('object');
                        }
                        if (!assetMetadataVal) {
                            return (null);
                        }
                        const anchorTokenLocationMetadata = {
                            displayName: await assetMetadataVal.displayName?.('string'),
                            ticker: await assetMetadataVal.ticker?.('string'),
                            logoURI: await assetMetadataVal.logoURI?.('string'),
                            decimalPlaces: await assetMetadataVal.decimalPlaces?.('primitive')
                        };
                        if (!isAnchorTokenLocationMetadata(anchorTokenLocationMetadata)) {
                            throw (new Error(`Invalid asset metadata for asset ID ${assetId} in chain type ${chainType}`));
                        }
                        return [assetId, anchorTokenLocationMetadata];
                    }));
                    for (const result of resolvedAssets) {
                        if (result.status === 'rejected') {
                            logger?.debug('Failed to resolve asset metadata', result.reason);
                            continue;
                        }
                        if (!result.value) {
                            continue;
                        }
                        if (!assets) {
                            assets = {};
                        }
                        assets[result.value[0]] = result.value[1];
                    }
                }
                return [
                    locationString,
                    {
                        ...(assets ? { assets } : {})
                    }
                ];
            }));
            let chains;
            for (const result of chainsResult) {
                if (result.status === 'rejected') {
                    logger?.debug('Failed to resolve location metadata', result.reason);
                    continue;
                }
                if (!result.value) {
                    continue;
                }
                if (!chains) {
                    chains = {};
                }
                // XXX:TODO Add comment
                // @ts-ignore
                chains[result.value[0]] = result.value[1];
            }
            if (!chains) {
                return (undefined);
            }
            return (chains);
        })();
        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 endpointInfo = assertServiceMetadataEndpoint(await Resolver.Metadata.fullyResolveValuizable(operation));
                    let url;
                    let authentication = {
                        type: 'none',
                        method: 'keeta-account'
                    };
                    if (typeof endpointInfo === 'string') {
                        url = endpointInfo;
                    }
                    else {
                        url = endpointInfo.url;
                        if (endpointInfo.options?.authentication) {
                            authentication = endpointInfo.options.authentication;
                        }
                    }
                    return ({
                        url: function (params) {
                            let substitutedURL;
                            try {
                                substitutedURL = decodeURI(url);
                            }
                            catch (error) {
                                logger?.debug('getEndpoints', 'Failed to decode URI, using original URL for substitution', error, url);
                                substitutedURL = url;
                            }
                            for (const [paramKey, paramValue] of Object.entries(params ?? {})) {
                                substitutedURL = substitutedURL.replace(`{${paramKey}}`, encodeURIComponent(paramValue));
                            }
                            return (validateURL(substitutedURL));
                        },
                        options: { authentication }
                    });
                },
                enumerable: true,
                configurable: true
            });
        }
        return ([
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            id,
            {
                ...(await resolveSharedAnchorMetadataLegalExtension(serviceInfo.legal, { logger })),
                operations: operationsFunctions,
                supportedAssets: supportedAssets,
                ...(locationMetadata ? { locationMetadata } : {})
            }
        ]);
    });
    const retval = Object.fromEntries(await Promise.all(serviceInfoPromises));
    return (retval);
}
class KeetaAssetMovementAnchorBase {
    logger;
    client;
    constructor(config) {
        this.client = config.client;
        this.logger = config.logger;
    }
}
class BaseKeetaAssetMovementTransfer {
    provider;
    request;
    transfer;
    constructor({ request, transfer, provider }) {
        this.provider = provider;
        this.request = request;
        this.transfer = transfer;
    }
    get instructions() {
        return (this.transfer.instructionChoices);
    }
}
/**
 * Represents an in-progress Asset Movement request.
 */
class KeetaAssetMovementTransfer extends BaseKeetaAssetMovementTransfer {
    isSimulation = false;
    get transferId() {
        return (this.transfer.id);
    }
    async getTransferStatus() {
        const account = this.request.account ? { account: this.request.account } : undefined;
        return (await this.provider.getTransferStatus({ id: this.transfer.id, ...account }));
    }
    async executeTransfer(input) {
        const account = this.request.account ? { account: this.request.account } : undefined;
        return (await this.provider.executeTransfer({ id: this.transfer.id, ...input, ...account }));
    }
}
class KeetaAssetMovementSimulatedTransfer extends BaseKeetaAssetMovementTransfer {
    isSimulation = true;
    async createTransfer(overrides) {
        const to = {
            ...this.request.to,
            ...overrides?.to
        };
        if (!to.recipient) {
            throw (new Error('Recipient is required to create a transfer from a simulation'));
        }
        return (await this.provider.initiateTransfer({
            ...this.request,
            to: {
                ...to,
                recipient: to.recipient
            }
        }));
    }
}
export class KeetaAssetMovementAnchorProvider extends KeetaAssetMovementAnchorBase {
    serviceInfo;
    providerID;
    parent;
    constructor(serviceInfo, providerID, parent) {
        const parentPrivate = parent._internals(KeetaAssetMovementAnchorClientAccessToken);
        super(parentPrivate);
        this.serviceInfo = serviceInfo;
        this.providerID = providerID;
        this.parent = parent;
    }
    async #getOperationData(operationName, params) {
        const endpoint = await this.serviceInfo.operations[operationName];
        if (endpoint === undefined) {
            throw (new Errors.OperationNotSupported({}, `Asset Movement service does not support ${operationName} operation`));
        }
        if (endpoint.options.authentication.method !== 'keeta-account') {
            throw (new Error(`Unsupported authentication method: ${endpoint.options.authentication.method}`));
        }
        return ({
            url: endpoint.url(params),
            auth: endpoint.options.authentication
        });
    }
    async isOperationSupported(operationName) {
        try {
            await this.#getOperationData(operationName);
            return (true);
        }
        catch (error) {
            if (error instanceof Errors.OperationNotSupported) {
                return (false);
            }
            throw (error);
        }
    }
    async #parseResponseError(data) {
        if (typeof data !== 'object' || data === null) {
            throw (new Error('Response is not an error'));
        }
        if (!('ok' in data) || data.ok !== false) {
            throw (new Error('Response is not an error'));
        }
        let errorStr;
        let parsedError = null;
        try {
            parsedError = await KeetaAnchorError.fromJSON(data);
        }
        catch (error) {
            this.logger?.debug('Failed to parse error response as KeetaAnchorError', error, data);
        }
        if (parsedError) {
            return (parsedError);
        }
        else {
            if ('error' in data && typeof data.error === 'string') {
                errorStr = data.error;
            }
            else {
                errorStr = 'Unknown error';
            }
            return (new Error(`asset movement request failed: ${errorStr}`));
        }
    }
    async #makeRequest(input) {
        const { url, auth } = await this.#getOperationData(input.endpoint, input.params);
        let serializedRequest;
        if (input.body && input.serializeRequest) {
            serializedRequest = await input.serializeRequest(input.body);
        }
        else {
            serializedRequest = input.body;
        }
        let signed;
        if (auth.type === 'required' || (auth.type === 'optional' && input.account)) {
            if (!input.account) {
                throw (new Error('Account information is required for this operation'));
            }
            if (!input.getSignedData) {
                throw (new Error('getSignedData function is required for signing the request'));
            }
            // We need this assertion because TypeScript cannot infer that the type is correct here, it is correct in the arguments.
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            const signable = input.getSignedData(serializedRequest);
            signed = await SignData(input.account.assertAccount(), signable);
        }
        let usingUrl = url;
        const headers = {
            'Accept': 'application/json'
        };
        let body = null;
        if (input.method === 'POST') {
            headers['Content-Type'] = 'application/json';
            body = JSON.stringify({ ...serializedRequest, signed });
        }
        else {
            if (signed) {
                if (!input.account) {
                    throw (new Error('invariant: Account information is required for this operation, which should exist at this point'));
                }
                usingUrl = addSignatureToURL(usingUrl, { signedField: signed, account: input.account.assertAccount() });
            }
            if (input.body) {
                throw (new Error('Body cannot be sent with GET requests'));
            }
        }
        const requestInformation = await fetch(usingUrl, {
            method: input.method, headers, body
        });
        const requestInformationJSON = await requestInformation.json();
        if (!input.isResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from asset movement service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (await this.#parseResponseError(requestInformationJSON));
        }
        // We need this assertion because TypeScript cannot infer that the type is correct here, it is correct because we checked it above.
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        return requestInformationJSON;
    }
    async #initiateOrSimulateTransfer(simulate, request) {
        this.logger?.debug(`Starting Asset Movement Transfer for provider ID: ${String(this.providerID)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: simulate ? 'simulateTransfer' : 'initiateTransfer',
            account: request.account,
            serializeRequest(body) {
                const { account, ...rest } = body;
                return ({
                    ...rest,
                    value: String(body.value),
                    from: {
                        location: convertAssetLocationToString(body.from.location),
                        ...(request.from.source ? { source: request.from.source } : {})
                    },
                    to: {
                        location: convertAssetLocationToString(body.to.location),
                        ...(body.to.recipient ? { recipient: body.to.recipient } : {}),
                        ...(body.to.depositMessage ? { depositMessage: body.to.depositMessage } : {})
                    },
                    asset: convertAssetOrPairSearchInputToCanonical(body.asset),
                    ...(account ? { account: account.assertAccount().publicKeyString.get() } : {})
                });
            },
            body: request,
            getSignedData: simulate ? getKeetaAssetMovementAnchorSimulateTransferRequestSigningData : getKeetaAssetMovementAnchorInitiateTransferRequestSigningData,
            isResponse(data) {
                if (simulate) {
                    return (isKeetaAssetMovementAnchorSimulateTransferResponse(data));
                }
                else {
                    return (isKeetaAssetMovementAnchorInitiateTransferResponse(data));
                }
            }
        });
        if (simulate) {
            this.logger?.debug(`asset movement simulation request successful`);
            return (new KeetaAssetMovementSimulatedTransfer({
                provider: this,
                request,
                transfer: requestInformationJSON
            }));
        }
        else {
            if (!('id' in requestInformationJSON)) {
                throw (new Error('expected provider to return id but did not'));
            }
            this.logger?.debug(`asset movement transfer request successful, request ID ${requestInformationJSON.id}`);
            if (!('recipient' in request.to)) {
                throw (new Error('Recipient information is required in the request to create a transfer'));
            }
            return (new KeetaAssetMovementTransfer({
                provider: this,
                request: {
                    ...request,
                    to: {
                        ...request.to,
                        recipient: request.to.recipient
                    }
                },
                transfer: requestInformationJSON
            }));
        }
    }
    async initiateTransfer(request) {
        return (await this.#initiateOrSimulateTransfer(false, request));
    }
    async simulateTransfer(request) {
        return (await this.#initiateOrSimulateTransfer(true, request));
    }
    async executeTransfer(request) {
        this.logger?.debug(`Starting Asset Movement Transfer for provider ID: ${String(this.providerID)}`);
        const { id, ...rest } = request;
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'executeTransfer',
            params: { id: request.id },
            account: request.account,
            body: rest,
            getSignedData: (body) => {
                return (getKeetaAssetMovementAnchorExecuteTransferRequestSigningData({ id, ...body }));
            },
            isResponse: isKeetaAssetMovementAnchorExecuteTransferResponse
        });
        this.logger?.debug(`asset movement execute transfer successful, transaction ID ${requestInformationJSON.transaction.id}`);
        return (requestInformationJSON);
    }
    async getTransferStatus(request) {
        const requestInformationJSON = await this.#makeRequest({
            method: 'GET',
            endpoint: 'getTransferStatus',
            account: request.account,
            params: { id: request.id },
            getSignedData: () => getKeetaAssetMovementAnchorGetTransferStatusRequestSigningData(request),
            isResponse: isKeetaAssetMovementAnchorGetExchangeStatusResponse
        });
        this.logger?.debug(`asset movement request successful, request ID ${request.id}`);
        return (requestInformationJSON);
    }
    async initiatePersistentForwardingTemplate(request) {
        this.logger?.debug(`Initiating persistent forwarding template for provider ID: ${String(this.providerID)}, request: ${JSON.stringify(request)}`);
        const result = await this.#makeRequest({
            method: 'POST',
            endpoint: 'initiatePersistentForwardingTemplate',
            account: request.account,
            serializeRequest(body) {
                const { account, ...rest } = body;
                return ({
                    ...rest,
                    location: convertAssetLocationToString(body.location),
                    asset: convertAssetOrPairSearchInputToCanonical(body.asset),
                    ...(account ? { account: account.assertAccount().publicKeyString.get() } : {})
                });
            },
            body: request,
            getSignedData: getKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateResponse
        });
        this.logger?.debug(`initiate persistent forwarding template successful, session ID: ${result.id}`);
        return (result);
    }
    async createPersistentForwardingTemplate(request) {
        this.logger?.debug(`Creating persistent forwarding for provider ID: ${String(this.providerID)}, request: ${JSON.stringify(request)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'createPersistentForwardingTemplate',
            account: request.account,
            serializeRequest(body) {
                const { account, ...rest } = body;
                const sharedFields = {
                    ...rest,
                    ...(account ? { account: account.assertAccount().publicKeyString.get() } : {})
                };
                if ('data' in body) {
                    return ({
                        ...sharedFields,
                        data: body.data
                    });
                }
                else {
                    return ({
                        ...sharedFields,
                        location: convertAssetLocationToString(body.location),
                        asset: convertAssetOrPairSearchInputToCanonical(body.asset)
                    });
                }
            },
            body: request,
            getSignedData: getKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateResponse
        });
        this.logger?.debug(`create persistent forwarding request successful`, requestInformationJSON.address);
        return (requestInformationJSON);
    }
    async createPersistentForwardingAddress(request) {
        this.logger?.debug(`Creating persistent forwarding for provider ID: ${String(this.providerID)}, request: ${JSON.stringify(request)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'createPersistentForwarding',
            account: request.account,
            serializeRequest(body) {
                const base = {
                    sourceLocation: convertAssetLocationToString(body.sourceLocation),
                    asset: convertAssetOrPairSearchInputToCanonical(body.asset),
                    account: body.account?.assertAccount().publicKeyString.get()
                };
                if ('persistentAddressTemplateId' in body) {
                    return ({
                        ...base,
                        persistentAddressTemplateId: body.persistentAddressTemplateId
                    });
                }
                else {
                    return ({
                        ...base,
                        destinationAddress: body.destinationAddress,
                        destinationLocation: convertAssetLocationToString(body.destinationLocation)
                    });
                }
            },
            body: request,
            getSignedData: getKeetaAssetMovementAnchorCreatePersistentForwardingRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorCreatePersistentForwardingResponse
        });
        this.logger?.debug(`create persistent forwarding request successful`, requestInformationJSON.address);
        return (requestInformationJSON);
    }
    async listForwardingAddressTemplates(request) {
        this.logger?.debug(`Listing persistent forwarding address templates for provider ID: ${String(this.providerID)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'listPersistentForwardingTemplate',
            account: request.account,
            body: request,
            serializeRequest(body) {
                return ({
                    ...(body.account ? { account: body.account.assertAccount().publicKeyString.get() } : {}),
                    asset: body.asset?.map(a => convertAssetSearchInputToCanonical(a)),
                    location: body.location?.map(l => convertAssetLocationToString(l))
                });
            },
            getSignedData: getKeetaAssetMovementAnchorListForwardingAddressTemplateRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorListForwardingAddressTemplateResponse
        });
        this.logger?.debug(`list persistent forwarding address templates request successful`, requestInformationJSON.templates);
        return ({
            templates: requestInformationJSON.templates,
            total: Number(requestInformationJSON.total)
        });
    }
    async listForwardingAddresses(request) {
        this.logger?.debug(`Listing persistent forwarding address templates for provider ID: ${String(this.providerID)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'listPersistentForwarding',
            account: request.account,
            body: request,
            serializeRequest(body) {
                return ({
                    ...body,
                    account: body.account?.assertAccount().publicKeyString.get(),
                    search: body.search ? body.search.map(function (searchItem) {
                        return ({
                            ...searchItem,
                            asset: searchItem.asset ? convertAssetSearchInputToCanonical(searchItem.asset) : undefined,
                            sourceLocation: searchItem.sourceLocation ? convertAssetLocationToString(searchItem.sourceLocation) : undefined,
                            destinationLocation: searchItem.destinationLocation ? convertAssetLocationToString(searchItem.destinationLocation) : undefined
                        });
                    }) : undefined
                });
            },
            getSignedData: getKeetaAssetMovementAnchorListPersistentForwardingRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorListPersistentForwardingResponse
        });
        this.logger?.debug('KeetaAssetMovementAnchorProvider::listPersistentForwardingAddresses', `list persistent forwarding address request successful`, requestInformationJSON.addresses);
        return ({
            addresses: requestInformationJSON.addresses,
            total: Number(requestInformationJSON.total)
        });
    }
    async listTransactions(request) {
        this.logger?.debug(`List persistent forwarding transactions provider ID: ${String(this.providerID)}, request: ${JSON.stringify(request)}`);
        const requestInformationJSON = await this.#makeRequest({
            method: 'POST',
            endpoint: 'listTransactions',
            account: request.account,
            serializeRequest(body) {
                return ({
                    account: body.account?.assertAccount().publicKeyString.get(),
                    pagination: body.pagination,
                    persistentAddresses: body.persistentAddresses?.map(pa => ({
                        location: convertAssetLocationToString(pa.location),
                        ...('persistentAddressTemplate' in pa ?
                            { persistentAddressTemplate: pa.persistentAddressTemplate } :
                            { persistentAddress: pa.persistentAddress })
                    })),
                    from: body.from ? {
                        location: convertAssetLocationToString(body.from.location),
                        userAddress: body.from.userAddress,
                        asset: body.from.asset ? convertAssetSearchInputToCanonical(body.from.asset) : undefined
                    } : undefined,
                    to: body.to ? {
                        location: convertAssetLocationToString(body.to.location),
                        userAddress: body.to.userAddress,
                        asset: body.to.asset ? convertAssetSearchInputToCanonical(body.to.asset) : undefined
                    } : undefined,
                    transactions: body.transactions?.map(tx => ({
                        location: convertAssetLocationToString(tx.location),
                        transaction: tx.transaction
                    }))
                });
            },
            body: request,
            getSignedData: getKeetaAssetMovementAnchorlistTransactionsRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorlistPersistentForwardingTransactionsResponse
        });
        this.logger?.debug(`list persistent transactions request successful, ${requestInformationJSON.transactions}`);
        return (requestInformationJSON);
    }
    async #awaitPromiseURL(promiseURL, options) {
        const startTime = Date.now();
        const timeout = options?.timeoutMs ?? (5 * 60 * 1000);
        while (true) {
            if (options?.abortSignal?.aborted) {
                break;
            }
            let response;
            try {
                response = await fetch(promiseURL);
            }
            catch (error) {
                this.logger?.debug('KeetaAssetMovementAnchorProvider::awaitPromiseURL', 'Error fetching promise URL', error);
                throw (new Error(`Error fetching promise URL: ${String(error)}`));
            }
            if (response.status === 200) {
                return;
            }
            if (response.status !== 202) {
                let errorData;
                try {
                    errorData = await response.json();
                }
                catch {
                    throw (new Error(`Error parsing error response json from promise, status code ${response.status}`));
                }
                throw (await this.#parseResponseError(errorData));
            }
            if (Date.now() - startTime > timeout) {
                throw (new Error('Timeout waiting for promise URL to complete'));
            }
            let retryAfterMS;
            const retryAfterHeader = response.headers.get('Retry-After');
            if (retryAfterHeader) {
                if (!isNaN(Number(retryAfterHeader))) {
                    retryAfterMS = Number(retryAfterHeader) * 1000;
                }
                else {
                    const retryAfterDate = new Date(retryAfterHeader);
                    if (!isNaN(retryAfterDate.getTime())) {
                        retryAfterMS = retryAfterDate.getTime() - Date.now();
                    }
                }
            }
            if (!retryAfterMS) {
                retryAfterMS = options?.defaultPollIntervalMs ?? 1000;
            }
            await KeetaNet.lib.Utils.Helper.asleep(retryAfterMS);
        }
    }
    async shareKYCAttributes(request, awaitOptions) {
        this.logger?.debug('Sharing KYC attributes');
        const response = await this.#makeRequest({
            method: 'POST',
            endpoint: 'shareKYC',
            account: request.account,
            async serializeRequest(body) {
                let attributes;
                if (typeof body.attributes === 'string') {
                    attributes = body.attributes;
                }
                else {
                    attributes = await body.attributes.export({ format: 'string' });
                }
                return ({
                    account: body.account.assertAccount().publicKeyString.get(),
                    attributes: attributes,
                    ...(body.tosAgreement ? { tosAgreement: body.tosAgreement } : {})
                });
            },
            body: request,
            getSignedData: getKeetaAssetMovementAnchorShareKYCRequestSigningData,
            isResponse: isKeetaAssetMovementAnchorShareKYCResponse
        });
        if (response.isPending && response.promiseURL) {
            this.logger?.debug('KYC attribute sharing is pending, awaiting promise URL');
            let promiseURL;
            if (response.promiseURL.startsWith('/')) {
                const operationData = await this.#getOperationData('shareKYC');
                promiseURL = new URL(response.promiseURL, operationData.url).toString();
            }
            else {
                promiseURL = response.promiseURL;
            }
            await this.#awaitPromiseURL(promiseURL, { ...awaitOptions });
        }
        this.logger?.debug(`done sharing KYC attributes`);
    }
    getAssetMetadataForLocation(location, asset) {
        const locationMetadata = this.serviceInfo.locationMetadata;
        if (!locationMetadata) {
            return (null);
        }
        const locationString = convertAssetLocationToString(location);
        if (!(locationString in locationMetadata)) {
            return (null);
        }
        // We can assert here as we have validated the key is included above
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        const locationSpecificMetadata = locationMetadata[locationString];
        if (!locationSpecificMetadata?.assets || !(asset in locationSpecificMetadata.assets)) {
            return (null);
        }
        const assetMetadata = locationSpecificMetadata.assets[asset];
        if (!assetMetadata) {
            return (null);
        }
        return (assetMetadata);
    }
    getLegalDisclaimers() {
        const disclaimers = this.serviceInfo.legal?.disclaimers;
        if (!disclaimers) {
            return (null);
        }
        return (disclaimers);
    }
}
class KeetaAssetMovementAnchorClient extends KeetaAssetMovementAnchorBase {
    resolver;
    id;
    // eslint-disable-next-line no-unused-private-class-members
    #signer;
    // eslint-disable-next-line no-unused-private-class-members
    #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('KeetaAssetMovementAnchorClient 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('KeetaAssetMovementAnchorClient requires an Account or a UserClient with an associated Account'));
        }
    }
    async #lookup(request, shared) {
        const endpoints = await getEndpoints(this.resolver, request, shared, this.logger);
        if (endpoints === null) {
            return (null);
        }
        const providers = typedAssetMovementServiceEntries(endpoints).map(([id, serviceInfo]) => {
            return (new KeetaAssetMovementAnchorProvider(serviceInfo, id, this));
        });
        return (providers);
    }
    async getProvidersForTransfer(request, shared) {
        return (await this.#lookup(request, shared));
    }
    async getProviderByID(providerID) {
        const providers = await this.#lookup({}, { providerIDs: [providerID] });
        return (providers?.[0] ?? null);
    }
    async getProviderLegalDisclaimersByID(providerID) {
        const provider = await this.getProviderByID(providerID);
        if (!provider) {
            return (null);
        }
        const disclaimers = provider.serviceInfo.legal?.disclaimers;
        if (!disclaimers) {
            return (null);
        }
        return (disclaimers);
    }
    async getProviderByAccount(account) {
        const providers = await this.#lookup({}, { accounts: [account] });
        return (providers?.[0] ?? null);
    }
    /** @internal */
    _internals(accessToken) {
        if (accessToken !== KeetaAssetMovementAnchorClientAccessToken) {
            throw (new Error('invalid access token'));
        }
        return ({
            resolver: this.resolver,
            logger: this.logger,
            client: this.client
        });
    }
}
export default KeetaAssetMovementAnchorClient;
//# sourceMappingURL=client.js.map