// 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;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { SetConfigurationCommand } from "../../../usecase/command/network/setConfiguration/SetConfigurationCommand";
import { SetNetworkCommand } from "../../../usecase/command/network/setNetwork/SetNetworkCommand";
import { CommandBus } from "../../../../core/command/CommandBus";
import { RuntimeError } from "../../../../core/error/RuntimeError";
import Account from "../../../../domain/context/account/Account";
import { HederaNetworks, unrecognized } from "../../../../domain/context/network/Environment";
import { WalletConnectError } from "../../../../domain/context/network/error/WalletConnectError";
import { WalletConnectRejectedError } from "../../../../domain/context/network/error/WalletConnectRejectedError";
import detectEthereumProvider from "@metamask/detect-provider";
import { MirrorNodeAdapter } from "../../../../port/out/mirror/MirrorNodeAdapter";
import EventService from "../../event/EventService";
import LogService from "../../log/LogService";
import { BrowserProvider } from "ethers";
import Injectable from "../../../../core/injectable/Injectable";
import NetworkService from "../../network/NetworkService";
import { singleton } from "tsyringe";
import Service from "../../Service";
import { SupportedWallets } from "../../../../domain/context/network/Wallet";
import { ConnectionState, WalletEvents } from "../../event/WalletEvent";
let MetamaskService = class MetamaskService extends Service {
    signerOrProvider;
    account;
    factories;
    resolvers;
    mirrorNodes;
    jsonRpcRelays;
    eventService;
    commandBus;
    networkService;
    mirrorNodeAdapter;
    constructor(eventService, commandBus, networkService, mirrorNodeAdapter) {
        super();
        this.eventService = eventService;
        this.commandBus = commandBus;
        this.networkService = networkService;
        this.mirrorNodeAdapter = mirrorNodeAdapter;
    }
    async init(debug = false) {
        !debug && (await this.connectMetamask(false));
        const eventData = {
            initData: { account: this.account, pairing: "", topic: "" },
            wallet: SupportedWallets.METAMASK,
        };
        this.eventService.emit(WalletEvents.walletInit, eventData);
        LogService.logTrace("Metamask Initialized ", eventData);
        return this.networkService.environment;
    }
    async register(handler, account, debug = false) {
        if (account) {
            const accountMirror = await this.mirrorNodeAdapter.getAccountInfo(account.id);
            this.account = account;
            this.account.publicKey = accountMirror.publicKey;
        }
        Injectable.registerTransactionHandler(handler);
        !debug && (await this.connectMetamask());
        LogService.logTrace("Metamask registered as handler");
        return Promise.resolve({ account });
    }
    async stop() {
        this.eventService.emit(WalletEvents.walletConnectionStatusChanged, {
            status: ConnectionState.Disconnected,
            wallet: SupportedWallets.METAMASK,
        });
        LogService.logTrace("Metamask stopped");
        this.eventService.emit(WalletEvents.walletDisconnect, {
            wallet: SupportedWallets.METAMASK,
        });
        return Promise.resolve(true);
    }
    async connectMetamask(pair = true) {
        try {
            const ethProvider = await detectEthereumProvider({ silent: true });
            if (!ethProvider || !ethProvider.isMetaMask)
                throw new WalletConnectError("Metamask was not found!");
            if (!ethereum.isConnected())
                throw new WalletConnectError("Metamask is not connected!");
            this.eventService.emit(WalletEvents.walletFound, {
                wallet: SupportedWallets.METAMASK,
                name: SupportedWallets.METAMASK,
            });
            // eslint-disable-next-line @typescript-eslint/no-unused-expressions
            pair && (await this.pairWallet());
            if (pair) {
                const browserProvider = new BrowserProvider(ethereum);
                this.signerOrProvider = await browserProvider.getSigner();
            }
        }
        catch (error) {
            if ("code" in error && error.code === 4001)
                throw new WalletConnectRejectedError(SupportedWallets.METAMASK);
            if (error instanceof WalletConnectError)
                throw error;
            throw new RuntimeError(error.message);
        }
    }
    getSignerOrProvider() {
        return this.signerOrProvider;
    }
    setSignerOrProvider(setSignerOrProvider) {
        this.signerOrProvider = setSignerOrProvider;
    }
    getAccount() {
        return this.account;
    }
    setConfig(config) {
        if (config.mirrorNodes)
            this.mirrorNodes = config.mirrorNodes;
        if (config.jsonRpcRelays)
            this.jsonRpcRelays = config.jsonRpcRelays;
        if (config.factories)
            this.factories = config.factories;
        if (config.resolvers)
            this.resolvers = config.resolvers;
    }
    registerMetamaskEvents() {
        if (typeof globalThis === "undefined" ||
            typeof globalThis.window === "undefined" ||
            !globalThis.window?.ethereum)
            return;
        try {
            const ethereum = globalThis.window.ethereum;
            ethereum.on("accountsChanged", async (acct) => {
                const accounts = acct;
                if (accounts.length === 0) {
                    LogService.logTrace("Metamask disconnected from the wallet");
                    this.eventService.emit(WalletEvents.walletDisconnect, {
                        wallet: SupportedWallets.METAMASK,
                    });
                }
                else if (!this.account || accounts[0] !== this.account.evmAddress) {
                    await this.setMetasmaskAccount(accounts[0]);
                    this.emitWalletPairedEvent();
                }
            });
            ethereum.on("chainChanged", async (chainId) => {
                await this.setMetamaskNetwork(chainId);
                const evmAddress = this.account?.evmAddress ?? (await this.getFirstAccount()) ?? "";
                await this.setMetasmaskAccount(evmAddress);
                this.emitWalletPairedEvent();
            });
        }
        catch (error) {
            LogService.logError(error);
            throw new WalletConnectError("Ethereum is not defined");
        }
    }
    async pairWallet() {
        const accts = await ethereum.request({ method: "eth_requestAccounts" });
        if (accts && "length" in accts && accts.length > 0) {
            const evmAddress = accts[0];
            const chainId = await ethereum.request({ method: "eth_chainId" });
            await this.setMetamaskNetwork(chainId);
            await this.setMetasmaskAccount(evmAddress);
            this.eventService.emit(WalletEvents.walletPaired, {
                data: { account: this.account, pairing: "", topic: "" },
                network: {
                    name: this.networkService.environment,
                    recognized: this.networkService.environment !== unrecognized,
                    factoryId: this.networkService.configuration?.factoryAddress ?? "",
                    resolverId: this.networkService.configuration?.resolverAddress ?? "",
                },
                wallet: SupportedWallets.METAMASK,
            });
        }
        else {
            LogService.logTrace("Paired Metamask failed with no accounts");
            this.eventService.emit(WalletEvents.walletDisconnect, {
                wallet: SupportedWallets.METAMASK,
            });
        }
    }
    async setMetamaskNetwork(chainId) {
        const metamaskNetwork = HederaNetworks.find((i) => "0x" + i.chainId.toString(16) === chainId.toString());
        let network = unrecognized;
        let factoryId = "";
        let resolverId = "";
        let mirrorNode = { baseUrl: "", apiKey: "", headerName: "" };
        let rpcNode = { baseUrl: "", apiKey: "", headerName: "" };
        if (metamaskNetwork) {
            network = metamaskNetwork.network;
            ({ factoryId, resolverId, mirrorNode, rpcNode } = this.getNetworkConfig(metamaskNetwork.network));
            LogService.logTrace("Metamask Network:", chainId);
        }
        else {
            LogService.logError(chainId + " not an hedera network");
        }
        await this.commandBus.execute(new SetNetworkCommand(network, mirrorNode, rpcNode));
        await this.commandBus.execute(new SetConfigurationCommand(factoryId, resolverId));
        const browserProvider = new BrowserProvider(ethereum);
        this.signerOrProvider = await browserProvider.getSigner();
    }
    async setMetasmaskAccount(evmAddress) {
        try {
            const mirrorAccount = await this.mirrorNodeAdapter.getAccountInfo(evmAddress);
            this.account = new Account({
                id: mirrorAccount.id.toString(),
                evmAddress: mirrorAccount.evmAddress,
                publicKey: mirrorAccount.publicKey,
            });
            const browserProvider = new BrowserProvider(ethereum);
            this.signerOrProvider = await browserProvider.getSigner();
            LogService.logTrace("Paired Metamask Wallet Event:", this.account);
        }
        catch (e) {
            LogService.logError("account could not be retrieved from mirror error : " + e);
            this.account = Account.NULL;
        }
    }
    async getFirstAccount() {
        const accts = await ethereum.request({ method: "eth_requestAccounts" });
        return accts && "length" in accts && accts.length > 0 ? accts[0] : null;
    }
    getNetworkConfig(environment) {
        let factoryId = "";
        let resolverId = "";
        let mirrorNode = { baseUrl: "", apiKey: "", headerName: "" };
        let rpcNode = { baseUrl: "", apiKey: "", headerName: "" };
        if (this.factories) {
            const result = this.factories.factories.find((i) => i.environment === environment);
            factoryId = result?.factory.toString() ?? "";
            if (!result)
                LogService.logError(`Factories could not be found for environment ${environment}`);
        }
        if (this.resolvers) {
            const result = this.resolvers.resolvers.find((i) => i.environment === environment);
            resolverId = result?.resolver.toString() ?? "";
            if (!result)
                LogService.logError(`Resolvers could not be found for environment ${environment}`);
        }
        if (this.mirrorNodes) {
            const result = this.mirrorNodes.nodes.find((i) => i.environment === environment);
            mirrorNode = result?.mirrorNode ?? mirrorNode;
            if (!result)
                LogService.logError(`Mirror Nodes could not be found for environment ${environment}`);
        }
        if (this.jsonRpcRelays) {
            const result = this.jsonRpcRelays.nodes.find((i) => i.environment === environment);
            rpcNode = result?.jsonRpcRelay ?? rpcNode;
            if (!result)
                LogService.logError(`RPC Nodes could not be found for environment ${environment}`);
        }
        return { factoryId, resolverId, mirrorNode, rpcNode };
    }
    emitWalletPairedEvent() {
        this.eventService.emit(WalletEvents.walletPaired, {
            data: { account: this.account, pairing: "", topic: "" },
            network: {
                name: this.networkService.environment,
                recognized: this.networkService.environment !== unrecognized,
                factoryId: this.networkService.configuration?.factoryAddress ?? "",
                resolverId: this.networkService.configuration?.resolverAddress ?? "",
            },
            wallet: SupportedWallets.METAMASK,
        });
    }
};
MetamaskService = __decorate([
    singleton(),
    __metadata("design:paramtypes", [EventService,
        CommandBus,
        NetworkService,
        MirrorNodeAdapter])
], MetamaskService);
export default MetamaskService;
