import { type ReactNode, createContext, useContext, useState, useMemo } from "react"
import { Unstable } from "@substrate/connect-discovery"
import { createClient, SubstrateClient } from "@polkadot-api/substrate-client"
import { getObservableClient, ObservableClient } from "@polkadot-api/observable-client"
import { createClient as createFullClient } from "polkadot-api"
import useSWR from "swr"

type Context = {
    providerDetails?: Unstable.SubstrateConnectProviderDetail[]
    providerDetail?: Unstable.SubstrateConnectProviderDetail
    connectProviderDetail(detail: Unstable.SubstrateConnectProviderDetail): void
    disconnectProviderDetail(): void
    accounts?: Unstable.Account[]
    account?: Unstable.Account
    connectAccount(account: Unstable.Account): void
    disconnectAccount(): void
    provider?: Unstable.Provider
    chainId: string
    setChainId: (chainId: string) => void
    client?: SubstrateClient
    observableClient?: ObservableClient
    chainHead$?: any
    clientFull?: any
}

const UnstableProvider = createContext<Context>(null!)

export const useUnstableProvider = () => useContext(UnstableProvider)

export const UnstableProviderProvider = ({
    children,
    defaultChainId,
}: {
    children: ReactNode
    defaultChainId: string
}) => {
    const { data: providerDetails } = useSWR("getProviders", () =>
        Unstable.getSubstrateConnectExtensionProviders(),
    )
    const [providerDetail, setProviderDetail] =
        useState<Unstable.SubstrateConnectProviderDetail>()
    const { data: provider } = useSWR(
        () => `providerDetail.${providerDetail!.info.uuid}.provider`,
        () => providerDetail!.provider,
    )

    const [chainId, setChainId_] = useState(defaultChainId)
    const { data: accounts } = useSWR(
        () =>
            `providerDetail.${providerDetail!.info.uuid}.provider.getAccounts(${chainId})`,
        async () => (await providerDetail!.provider).getAccounts(chainId),
    )

    const client = useMemo(() => {
        if (!provider || !chainId) return undefined
        const chain = provider?.getChains()[chainId]
        if (!chain) return undefined
        return createClient(chain.connect)
    }, [provider, chainId])

    const clientFull = useMemo(() => {
        if (!provider || !chainId) return undefined
        const chain = provider?.getChains()[chainId]
        if (!chain) return undefined
        return createFullClient(chain.connect)
    }, [provider, chainId])

    const observableClient = useMemo(() => {
        return client ? getObservableClient(client) : undefined
    }, [client])

    const chainHead$ = useMemo(() => {
        return observableClient ? observableClient.chainHead$() : undefined
    }, [observableClient])

    const [account, setAccount] = useState<Unstable.Account>()
    const disconnectAccount = () => setAccount(undefined)
    const disconnectProviderDetail = () => {
        disconnectAccount()
        setProviderDetail(undefined)
    }
    const setChainId = (chainId: string) => {
        setChainId_(chainId)
        disconnectAccount()
    }

    return (
        <UnstableProvider.Provider
            value={{
                providerDetails,
                providerDetail,
                connectProviderDetail: setProviderDetail,
                disconnectProviderDetail,
                accounts,
                account,
                connectAccount: setAccount,
                disconnectAccount,
                provider,
                chainId,
                setChainId,
                client,
                clientFull,
                observableClient,
                chainHead$
            }}
        >
            {children}
        </UnstableProvider.Provider>
    )
}
