import { getSpecTypes } from "@polkadot/types-known";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useStored } from "../../hooks/common";
import { useApi } from "./api";
import { useNetwork } from "./network";
import { createSimpleContext } from "../simple";
import { useStillNotifications } from "../still-notifications/context";
import { runOnClientSide } from "../../utils/device";
const win = window;
const selectExtension = (name) => {
    const ext = win.injectedWeb3?.[name];
    if (!ext) {
        return;
    }
    delete win.injectedWeb3[name];
    const extensions = { ...win.injectedWeb3 };
    win.injectedWeb3 = { [name]: ext };
    return extensions;
};
const restoreExtensions = (exts) => {
    win.injectedWeb3 = { ...win.injectedWeb3, ...exts };
};
const hasPub = (acc) => !!acc && Object.prototype.hasOwnProperty.call(acc, "pub");
const isWeb3Injected = () => Object.keys(window.injectedWeb3 ?? {}).length > 0;
export const getVersion = (name = "polkadot-js") => ({
    name,
    version: window.injectedWeb3?.[name]?.version,
});
const useConnect = ({ name, useBase }) => {
    const base = useBase();
    const { network } = useNetwork();
    const { api } = useApi();
    const { addNotification, deleteNotification } = useStillNotifications();
    const session = useStored("POLKADOT_SESSION", window.localStorage);
    const prevWallet = useStored("SUBSTRATE_WALLET", window.localStorage);
    const [installed, setInstalled] = useState(isWeb3Injected());
    const [error, setError] = useState();
    const [extensions, setExtensions] = useState();
    const [rawAccounts, setAccounts] = useState();
    const connected = useMemo(() => !!rawAccounts, [rawAccounts]);
    const [_account, setAccount] = useState();
    const [signer, setSigner] = useState();
    const [_debugAddr, _debug] = useState();
    const base64Encode = useMemo(() => base.utilCrypto?.base64Encode, [base.utilCrypto?.base64Encode]);
    const account = useMemo(() => _account
        ? _debugAddr
            ? {
                ..._account,
                address: _debugAddr,
                pub: Buffer.from(base.utilCrypto?.decodeAddress(_debugAddr) ?? []).toString("hex"),
            }
            : _account
        : undefined, [_account, _debugAddr, base.utilCrypto?.decodeAddress]);
    const ss58Prefix = useMemo(() => api?._api.registry.chainSS58 ?? network?.ss58 ?? 42, [api, network]);
    const accounts = useMemo(() => rawAccounts?.map((account) => {
        const pub = base.utilCrypto?.decodeAddress(account.address);
        return {
            ...account,
            address: base.utilCrypto?.encodeAddress(pub ?? new Uint8Array(), ss58Prefix) ?? "",
            pub: Buffer.from(pub ?? []).toString("hex"),
        };
    }), [
        rawAccounts,
        ss58Prefix,
        base.utilCrypto?.decodeAddress,
        base.utilCrypto?.encodeAddress,
    ]);
    useEffect(() => {
        if (!isWeb3Injected()) {
            let t;
            const check = () => {
                if (!isWeb3Injected()) {
                    t = setTimeout(check, 500);
                }
                else {
                    setInstalled(true);
                }
            };
            check();
            return () => {
                if (t) {
                    clearTimeout(t);
                }
            };
        }
    }, []);
    const checkExtensionsForUpdate = useCallback(async () => {
        if (!api || !extensions || !extensions.length) {
            return false;
        }
        extensions.forEach(async (ext) => {
            const current = (await ext.metadata?.get())?.find(({ genesisHash }) => api?._api.genesisHash.eq(genesisHash));
            const updateAvailable = !current ||
                api?._api.runtimeVersion.specVersion.gtn(current.specVersion);
            updateAvailable
                ? addNotification({ type: "metadataUpdate" })
                : deleteNotification("metadataUpdate");
        });
    }, [api, extensions]);
    const updateMetadata = useCallback(async (debug) => {
        if (!api) {
            return;
        }
        if (!extensions || !extensions.length || !base64Encode) {
            return;
        }
        const chain = await api?._api.rpc.system.chain();
        const chainInfo = {
            chain: chain.toString(),
            chainType: "substrate",
            color: "",
            genesisHash: api._api.genesisHash.toHex(),
            icon: "substrate",
            metaCalls: base64Encode(api._api.runtimeMetadata.asCallsOnly.toU8a()),
            specVersion: debug ? 0 : Number(api._api.runtimeVersion.specVersion),
            ss58Format: api._api.registry.chainSS58 || 42,
            tokenDecimals: api.registry.chainDecimals[0],
            tokenSymbol: api.registry.chainTokens[0],
            types: getSpecTypes(api?.registry, chain, api._api.runtimeVersion.specName, api._api.runtimeVersion.specVersion),
        };
        extensions.forEach((e) => e.metadata
            ?.provide(chainInfo)
            .then((isOk) => isOk && checkExtensionsForUpdate()));
    }, [api, extensions, base.extensionDApp, extensions, base64Encode]);
    runOnClientSide(() => {
        // For debug purpose. Allows to clear extension metada from console
        window.clearMetadata = () => updateMetadata(true);
    });
    useEffect(() => {
        checkExtensionsForUpdate();
    }, [extensions, api, checkExtensionsForUpdate]);
    const metaDataShouldBeUpdated = React.useCallback(async () => {
        if (!api || !extensions || !extensions.length) {
            deleteNotification("metadataUpdate");
            return false;
        }
        const updateAvailable = extensions.some(async (ext) => {
            const current = (await ext.metadata?.get())?.find(({ genesisHash }) => api?._api.genesisHash.eq(genesisHash));
            return (!current ||
                api?._api.runtimeVersion.specVersion.gtn(current.specVersion));
        });
        updateAvailable
            ? addNotification({ type: "metadataUpdate" })
            : deleteNotification("metadataUpdate");
    }, [api, extensions]);
    useEffect(() => {
        metaDataShouldBeUpdated();
    }, [metaDataShouldBeUpdated]);
    const connect = useCallback(async (extName) => {
        const extension = typeof extName === "string" ? extName : undefined;
        let result;
        if (!extension) {
            return;
        }
        const cut = selectExtension(extension);
        prevWallet.set(extension);
        try {
            const extensions = await base.extensionDApp?.web3Enable(name);
            setExtensions(extensions);
            const accounts = await base.extensionDApp?.web3Accounts();
            setAccounts(accounts);
            result = accounts;
        }
        catch (e) {
            if (e instanceof Error) {
                setError(e);
            }
        }
        restoreExtensions(cut);
        return result;
    }, [
        name,
        base.extensionDApp?.web3Accounts,
        base.extensionDApp?.web3Enable,
        prevWallet.set,
    ]);
    const disconnect = useCallback(() => {
        setAccounts(undefined);
        setAccount(undefined);
        setExtensions(undefined);
        setSigner(undefined);
        prevWallet.remove();
        session.remove();
        globalThis.localStorage?.removeItem("APP-SESSION");
    }, [prevWallet.remove, session.remove]);
    const select = useCallback(async (accountOrAddress) => {
        if (!accountOrAddress) {
            session.remove();
            globalThis.localStorage?.removeItem("APP-SESSION");
            setAccount(undefined);
            setSigner(undefined);
            return;
        }
        const address = typeof accountOrAddress === "string"
            ? accountOrAddress
            : accountOrAddress.address;
        const pub = hasPub(accountOrAddress)
            ? accountOrAddress.pub
            : Buffer.from(base.utilCrypto?.decodeAddress(address) ?? []).toString("hex");
        const account = accounts?.find((acc) => acc.pub === pub);
        if (account) {
            const ext = await base.extensionDApp?.web3FromAddress(address);
            if (!ext) {
                return;
            }
            setAccount(account);
            session.set(address);
            setSigner(ext?.signer);
            return ext?.signer;
        }
    }, [
        accounts,
        session.set,
        session.remove,
        base.utilCrypto?.decodeAddress,
        base.extensionDApp?.web3FromAddress,
    ]);
    useEffect(() => {
        if (connected) {
            let unsub;
            const timeout = setTimeout(async () => {
                unsub = await base.extensionDApp?.web3AccountsSubscribe(setAccounts);
            }, 50);
            return () => {
                clearTimeout(timeout);
                unsub?.();
            };
        }
    }, [base.extensionDApp?.web3AccountsSubscribe, connected]);
    useEffect(() => {
        if (session.data && prevWallet.data) {
            connect(prevWallet.data);
        }
    }, [connect, session.data, prevWallet.data]);
    useEffect(() => {
        if (!account && accounts) {
            let address;
            if (session.data) {
                try {
                    const pub = Buffer.from(base.utilCrypto?.decodeAddress(session.data) ?? []).toString("hex");
                    const acc = accounts.find(({ address }) => pub ===
                        Buffer.from(base.utilCrypto?.decodeAddress(address) ?? []).toString("hex"));
                    if (acc) {
                        address = acc.address;
                    }
                    else {
                        address = accounts[0]?.address;
                    }
                }
                catch (e) {
                    console.error(e);
                }
            }
            else {
                address = accounts[0]?.address;
            }
            if (address) {
                const timeout = setTimeout(async () => {
                    await select(address);
                }, 50);
                return () => {
                    clearTimeout(timeout);
                };
            }
        }
    }, [select, session, account, accounts, base.utilCrypto?.decodeAddress]);
    React.useEffect(() => {
        if (api && signer) {
            api.setSigner(signer);
        }
    }, [signer, api]);
    // TODO stop using polkadot connect context, for now we have 2 compat hooks
    const compatRef = React.useRef();
    // compat session effect polkadot->substrate connect context;
    React.useEffect(() => {
        // key: APP-SESSION
        // format: {"accountIndex":0,"connected":"talisman"}
        if (globalThis.localStorage && session.data && prevWallet.data) {
            const index = accounts?.findIndex(({ address }) => address === session.data) ?? -1;
            if (index >= 0) {
                globalThis.localStorage.setItem("APP-SESSION", JSON.stringify({ accountIndex: index, connected: prevWallet.data }));
                compatRef.current = { accountIndex: index, connected: prevWallet.data };
            }
        }
    }, [session.data, prevWallet.data, accounts]);
    return {
        name,
        connectError: error,
        connect,
        disconnect,
        select,
        extensions,
        accounts,
        account,
        installed,
        connected,
        signer,
        _debug,
        updateMetadata,
    };
};
const Context = createSimpleContext("eq-accounts", {
    name: "",
    connectError: undefined,
    connect: () => Promise.reject(new Error("not implemented")),
    disconnect: () => Promise.reject(new Error("not implemented")),
    select: () => Promise.reject(new Error("not implemented")),
    extensions: undefined,
    accounts: undefined,
    account: undefined,
    installed: false,
    connected: false,
    signer: undefined,
    _debug: () => {
        throw new Error();
    },
    updateMetadata: () => Promise.reject(new Error("not implemented")),
}, useConnect);
export const ConnectProvider = Context.Provider;
/** @deprecated switch to substrate connect context */
export const useAccounts = Context.useContext;
//# sourceMappingURL=connect.js.map