import detectProvider from "@metamask/detect-provider";
import Onboarding from "@metamask/onboarding";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Web3 from "web3";
import { useAsyncMemo } from "../../hooks/async";
import { createSimpleContext } from "../simple";
import Signal from "@equilab/utils/lib/async/signal";
export const isWeb3ChainDetails = (chain) => Object.prototype.hasOwnProperty.call(chain, "chainName");
export const isWeb3RequestError = (err) => !!err &&
    ["code", "message"].every((field) => 
    // @ts-expect-error
    Reflect.has(err, field));
const isProvider = (provider) => !!provider && Reflect.has(provider, "request");
const isHexString = (str) => str?.lastIndexOf("0x") === 0;
// https://docs.metamask.io/guide/rpc-api.html
const useWeb3 = () => {
    const [installed, setInstalled] = useState(false);
    const [chainId, setChainId] = useState();
    const [accounts, setAccounts] = useState();
    const address = useMemo(() => {
        const str = accounts && accounts[0];
        if (isHexString(str)) {
            return str;
        }
    }, [accounts]);
    const onboarding = useRef();
    const [error, setError] = useState();
    const handleAccounts = useCallback((accounts) => {
        if ((accounts?.length ?? 0) > 0 && onboarding.current) {
            setAccounts(accounts);
            onboarding.current.stopOnboarding();
        }
    }, []);
    useEffect(() => {
        if (!onboarding.current) {
            onboarding.current = new Onboarding();
        }
    }, []);
    const provider = useAsyncMemo(async () => (await detectProvider()), []);
    const web3 = useMemo(() => (provider.data ? new Web3(provider.data) : undefined), [provider.data]);
    useEffect(() => {
        const isInstalled = Onboarding.isMetaMaskInstalled();
        setInstalled(isInstalled);
    }, []);
    useEffect(() => {
        if (installed && isProvider(provider.data)) {
            provider.data.on?.("accountsChanged", handleAccounts);
            provider.data.on?.("chainChanged", setChainId);
            provider.data
                .request?.({ method: "eth_accounts" })
                .then(handleAccounts)
                .catch(setError);
            provider.data
                .request?.({ method: "eth_chainId" })
                .then(setChainId)
                .catch(setError);
            return () => {
                provider.data?.off?.("accountsChanged", handleAccounts);
                provider.data?.off?.("chainChanged", setChainId);
            };
        }
    }, [installed, provider.data, web3, handleAccounts]);
    const startOnboarding = useCallback(() => {
        if (!installed) {
            onboarding.current?.startOnboarding();
        }
    }, [installed]);
    const connect = useCallback(() => {
        if (installed && isProvider(provider.data)) {
            const sig = new Signal();
            provider.data
                .request?.({
                method: "eth_requestAccounts",
            })
                .then((acc) => {
                handleAccounts(acc);
                sig.emit(acc);
            })
                .catch((err) => {
                setError(err);
                sig.cancel(err);
            });
            return sig;
        }
    }, [installed, provider.data, handleAccounts]);
    const sign = useCallback(async (message) => {
        if (address) {
            const sig = await web3?.eth.personal.sign(message, address, "");
            return sig;
        }
        throw new Error("no address");
    }, [address, web3]);
    const switchChain = useCallback(async (chain) => {
        if (installed && isProvider(provider.data)) {
            try {
                await provider.data.request?.({
                    method: "wallet_switchEthereumChain",
                    params: [{ chainId: chain.chainId }],
                });
            }
            catch (err) {
                if (err?.code === 4902) {
                    // TODO make more elegant solution
                    await provider.data.request?.({
                        method: "wallet_addEthereumChain",
                        // @ts-ignore
                        params: [chain],
                    });
                }
                else {
                    throw err;
                }
            }
        }
    }, [installed, provider.data]);
    return {
        installed,
        error,
        accounts,
        address,
        chainId,
        web3,
        startOnboarding,
        connect,
        sign,
        switchChain,
    };
};
const Context = createSimpleContext("ethereum", {
    installed: false,
    error: undefined,
    address: undefined,
    accounts: undefined,
    chainId: undefined,
    web3: undefined,
    startOnboarding() {
        throw new Error("not implemented");
    },
    connect() {
        throw new Error("not implemented");
    },
    sign() {
        throw new Error("not implemented");
    },
    switchChain() {
        throw new Error("not implemented");
    },
}, useWeb3);
export const EthereumProvider = Context.Provider;
export const useEthereum = Context.useContext;
//# sourceMappingURL=context.js.map