// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import axios from "axios";
import { singleton } from "tsyringe";
import { PublicKey as HPublicKey } from "@hiero-ledger/sdk";
import { InvalidResponse } from "../../../core/error/InvalidResponse";
import { REGEX_TRANSACTION } from "../error/TransactionResponseError";
import { BYTES_32_LENGTH } from "../../../core/Constants";
import LogService from "../../../app/service/log/LogService";
import BigDecimal from "../../../domain/context/shared/BigDecimal";
import PublicKey from "../../../domain/context/account/PublicKey";
import { HederaId } from "../../../domain/context/shared/HederaId";
import { KeyType } from "../../../domain/context/account/KeyProps";
import EvmAddress from "../../../domain/context/contract/EvmAddress";
import { Time } from "../../../core/Time";
import { TransactionNotFound } from "./error/TransactionNotFound";
import { TransactionResultNotFound } from "./error/TransactionResultNotFound";
import { BalanceNotFound } from "./error/BalanceNotFound";
import { ErrorRetrievingEvmAddress } from "./error/ErrorRetrievingEvmAddress";
let MirrorNodeAdapter = class MirrorNodeAdapter {
    instance;
    config;
    mirrorNodeConfig;
    set(mnConfig) {
        this.mirrorNodeConfig = mnConfig;
        this.instance = axios.create({
            validateStatus: function (status) {
                return (status >= 200 && status < 300) || status == 404;
            },
        });
        this.mirrorNodeConfig.baseUrl = mnConfig.baseUrl.endsWith("/") ? mnConfig.baseUrl : `${mnConfig.baseUrl}/`;
        if (this.mirrorNodeConfig.headerName && this.mirrorNodeConfig.apiKey)
            this.instance.defaults.headers.common[this.mirrorNodeConfig.headerName] = this.mirrorNodeConfig.apiKey;
    }
    async getHederaIdfromContractAddress(contractAddress) {
        if (!contractAddress)
            return "";
        if (contractAddress.length >= 40)
            return (await this.getContractInfo(contractAddress)).id;
        return contractAddress;
    }
    async getAccountInfo(accountId) {
        try {
            LogService.logTrace("Getting account info -> ", this.mirrorNodeConfig.baseUrl + "accounts/" + accountId);
            const res = await this.instance.get(this.mirrorNodeConfig.baseUrl + "accounts/" + accountId.toString());
            const account = {
                id: HederaId.from(res.data.account),
                evmAddress: res.data.evm_address,
                alias: res.data.alias,
            };
            if (res.data.key)
                account.publicKey = new PublicKey({
                    key: res.data.key ? this.trimLeadingZeros(res.data.key.key) : undefined,
                    type: res.data.key ? this.getPublicKeyType(res.data.key._type) : undefined,
                });
            return account;
        }
        catch (error) {
            LogService.logError(error);
            return Promise.reject(new InvalidResponse(error));
        }
    }
    async getContractResults(transactionId, numberOfResultItems, evmAddress = false, timeout = 15, requestInterval = 2) {
        if (transactionId.match(REGEX_TRANSACTION)) {
            transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
        }
        const url = `${this.mirrorNodeConfig.baseUrl}contracts/results/${transactionId}`;
        let call_OK = false;
        const results = [];
        do {
            await Time.delay(requestInterval, "seconds");
            timeout = timeout - requestInterval;
            this.instance
                .get(url)
                .then((response) => {
                if (response &&
                    response.status === 200 &&
                    response.data.call_result &&
                    response.data.call_result.length > 2) {
                    try {
                        call_OK = true;
                        const data = response.data.call_result;
                        if (evmAddress) {
                            results.push(response.data.address);
                            return results;
                        }
                        if (numberOfResultItems == 0) {
                            numberOfResultItems = (data.length - 2) / BYTES_32_LENGTH;
                        }
                        if (data && data.startsWith("0x") && data.length >= 2 + numberOfResultItems * BYTES_32_LENGTH) {
                            for (let i = 0; i < numberOfResultItems; i++) {
                                const start = 2 + i * BYTES_32_LENGTH;
                                const end = start + BYTES_32_LENGTH;
                                const result = `0x${data.slice(start, end)}`;
                                results.push(result);
                            }
                            return results;
                        }
                        return null;
                    }
                    catch (error) {
                        LogService.logError(error);
                        return Promise.reject(new InvalidResponse(error));
                    }
                }
            })
                .catch((error) => {
                LogService.logError(`Error getting contracts result for transaction ${transactionId}: ${error}`);
            });
        } while (timeout > 0 && !call_OK);
        return results;
    }
    trimLeadingZeros(publicKey) {
        return publicKey.replace(/^0+/, "");
    }
    getPublicKeyType(publicKey) {
        switch (publicKey) {
            case "ECDSA_SECP256K1":
                return KeyType.ECDSA;
            default:
                return publicKey;
        }
    }
    async getContractInfo(contractId) {
        try {
            const url = `${this.mirrorNodeConfig.baseUrl}contracts/${contractId}`;
            LogService.logTrace("Getting contract info -> ", url);
            const retry = 10;
            let i = 0;
            let response;
            do {
                if (i > 0)
                    await new Promise((resolve) => setTimeout(resolve, 2000));
                response = await this.instance.get(url);
                i++;
            } while (response.status !== 200 && i < retry);
            const contract = {
                id: response.data.contract_id,
                evmAddress: response.data.evm_address,
            };
            return contract;
        }
        catch (error) {
            LogService.logError(error);
            return Promise.reject(new InvalidResponse(error));
        }
    }
    async getTransactionResult(transactionId) {
        try {
            const url = this.mirrorNodeConfig.baseUrl + "contracts/results/" + transactionId;
            LogService.logTrace(url);
            const res = await this.instance.get(url);
            if (!res.data.call_result)
                throw new TransactionResultNotFound();
            const result = {
                result: res.data.call_result.toString(),
            };
            return result;
        }
        catch (error) {
            LogService.logError(error);
            return Promise.reject(new InvalidResponse(error));
        }
    }
    async getTransactionFinalError(transactionId) {
        try {
            if (transactionId.match(REGEX_TRANSACTION))
                transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
            const url = this.mirrorNodeConfig.baseUrl + "transactions/" + transactionId;
            LogService.logTrace(url);
            await new Promise((resolve) => setTimeout(resolve, 5000));
            const res = await this.instance.get(url);
            let lastChildTransaction;
            if (res.data.transactions) {
                lastChildTransaction = res.data.transactions[res.data.transactions.length - 1];
                LogService.logError(JSON.stringify(lastChildTransaction));
            }
            else {
                throw new TransactionNotFound();
            }
            const result = {
                result: lastChildTransaction.result,
            };
            return result;
        }
        catch (error) {
            LogService.logError(error);
            return Promise.reject(new InvalidResponse(error));
        }
    }
    async getFileIdFromTransaction(transactionId, timeout = 20, requestInterval = 2) {
        if (transactionId.match(REGEX_TRANSACTION)) {
            transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
        }
        const url = this.mirrorNodeConfig.baseUrl + "transactions/" + transactionId;
        LogService.logTrace(`[MirrorNode] Querying FileCreate transaction: ${url}`);
        let remainingTimeout = timeout;
        while (remainingTimeout > 0) {
            await Time.delay(requestInterval, "seconds");
            remainingTimeout -= requestInterval;
            try {
                const res = await this.instance.get(url);
                const tx = res.data?.transactions?.[0];
                if (tx?.entity_id) {
                    LogService.logTrace(`[MirrorNode] Found entity_id: ${tx.entity_id}`);
                    return tx.entity_id;
                }
                LogService.logTrace(`[MirrorNode] entity_id not ready yet (${remainingTimeout}s remaining), tx: ${JSON.stringify(tx)}`);
            }
            catch (error) {
                LogService.logTrace(`[MirrorNode] Error querying transaction (${remainingTimeout}s remaining): ${error}`);
            }
        }
        return Promise.reject(new InvalidResponse("No entity_id in FileCreateTransaction receipt after timeout"));
    }
    async accountToEvmAddress(accountId) {
        try {
            const accountInfo = await this.getAccountInfo(accountId);
            if (accountInfo.evmAddress) {
                return new EvmAddress(accountInfo.evmAddress);
            }
            else if (accountInfo.publicKey) {
                return this.getAccountEvmAddressFromPrivateKeyType(accountInfo.publicKey.type, accountInfo.publicKey.key, accountId);
            }
            else {
                return Promise.reject("");
            }
        }
        catch (e) {
            throw new ErrorRetrievingEvmAddress(accountId, e);
        }
    }
    async getAccountEvmAddressFromPrivateKeyType(privateKeyType, publicKey, accountId) {
        switch (privateKeyType) {
            case KeyType.ECDSA:
                return new EvmAddress(HPublicKey.fromString(publicKey).toEthereumAddress());
            default:
                return new EvmAddress("0x" + (await this.getContractInfo(accountId)).evmAddress);
        }
    }
    async getHBARBalance(accountId) {
        try {
            const url = `${this.mirrorNodeConfig.baseUrl}balances?account.id=${accountId.toString()}`;
            LogService.logTrace(url);
            const res = await this.instance.get(url);
            if (!res.data.balances)
                throw new BalanceNotFound();
            return BigDecimal.fromString(res.data.balances[res.data.balances.length - 1].balance.toString());
        }
        catch (error) {
            LogService.logError(error);
            return Promise.reject(new InvalidResponse(error));
        }
    }
};
MirrorNodeAdapter = __decorate([
    singleton()
], MirrorNodeAdapter);
export { MirrorNodeAdapter };
