"use strict";
// 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);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const SetConfigurationCommand_1 = require("../../../usecase/command/network/setConfiguration/SetConfigurationCommand");
const SetNetworkCommand_1 = require("../../../usecase/command/network/setNetwork/SetNetworkCommand");
const CommandBus_1 = require("../../../../core/command/CommandBus");
const RuntimeError_1 = require("../../../../core/error/RuntimeError");
const Account_1 = __importDefault(require("../../../../domain/context/account/Account"));
const Environment_1 = require("../../../../domain/context/network/Environment");
const WalletConnectError_1 = require("../../../../domain/context/network/error/WalletConnectError");
const WalletConnectRejectedError_1 = require("../../../../domain/context/network/error/WalletConnectRejectedError");
const detect_provider_1 = __importDefault(require("@metamask/detect-provider"));
const MirrorNodeAdapter_1 = require("../../../../port/out/mirror/MirrorNodeAdapter");
const EventService_1 = __importDefault(require("../../event/EventService"));
const LogService_1 = __importDefault(require("../../log/LogService"));
const ethers_1 = require("ethers");
const Injectable_1 = __importDefault(require("../../../../core/injectable/Injectable"));
const NetworkService_1 = __importDefault(require("../../network/NetworkService"));
const tsyringe_1 = require("tsyringe");
const Service_1 = __importDefault(require("../../Service"));
const Wallet_1 = require("../../../../domain/context/network/Wallet");
const WalletEvent_1 = require("../../event/WalletEvent");
let MetamaskService = class MetamaskService extends Service_1.default {
    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: Wallet_1.SupportedWallets.METAMASK,
        };
        this.eventService.emit(WalletEvent_1.WalletEvents.walletInit, eventData);
        LogService_1.default.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_1.default.registerTransactionHandler(handler);
        !debug && (await this.connectMetamask());
        LogService_1.default.logTrace("Metamask registered as handler");
        return Promise.resolve({ account });
    }
    async stop() {
        this.eventService.emit(WalletEvent_1.WalletEvents.walletConnectionStatusChanged, {
            status: WalletEvent_1.ConnectionState.Disconnected,
            wallet: Wallet_1.SupportedWallets.METAMASK,
        });
        LogService_1.default.logTrace("Metamask stopped");
        this.eventService.emit(WalletEvent_1.WalletEvents.walletDisconnect, {
            wallet: Wallet_1.SupportedWallets.METAMASK,
        });
        return Promise.resolve(true);
    }
    async connectMetamask(pair = true) {
        try {
            const ethProvider = await (0, detect_provider_1.default)({ silent: true });
            if (!ethProvider || !ethProvider.isMetaMask)
                throw new WalletConnectError_1.WalletConnectError("Metamask was not found!");
            if (!ethereum.isConnected())
                throw new WalletConnectError_1.WalletConnectError("Metamask is not connected!");
            this.eventService.emit(WalletEvent_1.WalletEvents.walletFound, {
                wallet: Wallet_1.SupportedWallets.METAMASK,
                name: Wallet_1.SupportedWallets.METAMASK,
            });
            // eslint-disable-next-line @typescript-eslint/no-unused-expressions
            pair && (await this.pairWallet());
            if (pair) {
                const browserProvider = new ethers_1.BrowserProvider(ethereum);
                this.signerOrProvider = await browserProvider.getSigner();
            }
        }
        catch (error) {
            if ("code" in error && error.code === 4001)
                throw new WalletConnectRejectedError_1.WalletConnectRejectedError(Wallet_1.SupportedWallets.METAMASK);
            if (error instanceof WalletConnectError_1.WalletConnectError)
                throw error;
            throw new RuntimeError_1.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_1.default.logTrace("Metamask disconnected from the wallet");
                    this.eventService.emit(WalletEvent_1.WalletEvents.walletDisconnect, {
                        wallet: Wallet_1.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_1.default.logError(error);
            throw new WalletConnectError_1.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(WalletEvent_1.WalletEvents.walletPaired, {
                data: { account: this.account, pairing: "", topic: "" },
                network: {
                    name: this.networkService.environment,
                    recognized: this.networkService.environment !== Environment_1.unrecognized,
                    factoryId: this.networkService.configuration?.factoryAddress ?? "",
                    resolverId: this.networkService.configuration?.resolverAddress ?? "",
                },
                wallet: Wallet_1.SupportedWallets.METAMASK,
            });
        }
        else {
            LogService_1.default.logTrace("Paired Metamask failed with no accounts");
            this.eventService.emit(WalletEvent_1.WalletEvents.walletDisconnect, {
                wallet: Wallet_1.SupportedWallets.METAMASK,
            });
        }
    }
    async setMetamaskNetwork(chainId) {
        const metamaskNetwork = Environment_1.HederaNetworks.find((i) => "0x" + i.chainId.toString(16) === chainId.toString());
        let network = Environment_1.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_1.default.logTrace("Metamask Network:", chainId);
        }
        else {
            LogService_1.default.logError(chainId + " not an hedera network");
        }
        await this.commandBus.execute(new SetNetworkCommand_1.SetNetworkCommand(network, mirrorNode, rpcNode));
        await this.commandBus.execute(new SetConfigurationCommand_1.SetConfigurationCommand(factoryId, resolverId));
        const browserProvider = new ethers_1.BrowserProvider(ethereum);
        this.signerOrProvider = await browserProvider.getSigner();
    }
    async setMetasmaskAccount(evmAddress) {
        try {
            const mirrorAccount = await this.mirrorNodeAdapter.getAccountInfo(evmAddress);
            this.account = new Account_1.default({
                id: mirrorAccount.id.toString(),
                evmAddress: mirrorAccount.evmAddress,
                publicKey: mirrorAccount.publicKey,
            });
            const browserProvider = new ethers_1.BrowserProvider(ethereum);
            this.signerOrProvider = await browserProvider.getSigner();
            LogService_1.default.logTrace("Paired Metamask Wallet Event:", this.account);
        }
        catch (e) {
            LogService_1.default.logError("account could not be retrieved from mirror error : " + e);
            this.account = Account_1.default.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_1.default.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_1.default.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_1.default.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_1.default.logError(`RPC Nodes could not be found for environment ${environment}`);
        }
        return { factoryId, resolverId, mirrorNode, rpcNode };
    }
    emitWalletPairedEvent() {
        this.eventService.emit(WalletEvent_1.WalletEvents.walletPaired, {
            data: { account: this.account, pairing: "", topic: "" },
            network: {
                name: this.networkService.environment,
                recognized: this.networkService.environment !== Environment_1.unrecognized,
                factoryId: this.networkService.configuration?.factoryAddress ?? "",
                resolverId: this.networkService.configuration?.resolverAddress ?? "",
            },
            wallet: Wallet_1.SupportedWallets.METAMASK,
        });
    }
};
MetamaskService = __decorate([
    (0, tsyringe_1.singleton)(),
    __metadata("design:paramtypes", [EventService_1.default,
        CommandBus_1.CommandBus,
        NetworkService_1.default,
        MirrorNodeAdapter_1.MirrorNodeAdapter])
], MetamaskService);
exports.default = MetamaskService;
