import { BNS } from "@leekone/bnsjs";
// eslint-disable-next-line import/no-extraneous-dependencies
import { providers } from "ethers";
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";

const opts: ConstructorParameters<typeof BNS>[0] = {};

if (process.env.REACT_APP_GRAPHURI) {
  opts.graphURI = process.env.REACT_APP_GRAPHURI;
}
const defaultValue: BNS = new BNS(opts);
defaultValue.staticNetwork = false;

const BnsContext = createContext({ ...defaultValue, ready: false });
const chainID = process.env.NODE_ENV === "production" ? 137 : 137;
function EnsProvider({ children }: { children: React.ReactNode }) {
  const [ready, setReady] = useState(false);
  const chainIdRef = useRef<number | null>(null);
  const chainSetPromise = useRef<Promise<any> | null>(null);
  const setChainPromise = () => {
    const provider = new providers.JsonRpcProvider("https://polygon-bor.publicnode.com");
    // eslint-disable-next-line promise/always-return
    return defaultValue.setProvider(provider as any).then(() => {
      chainIdRef.current = chainID;
      chainSetPromise.current = null;
      setReady(true);
    });
  };

  useEffect(() => {
    if (typeof window !== "undefined" && !chainSetPromise.current) {
      chainSetPromise.current = setChainPromise();
    }
  }, []);

  const value = useMemo(
    () =>
      new Proxy(
        { ...defaultValue, ready },
        {
          // chain id safety check
          get(target, prop, reciever) {
            const targetFn = target[prop as keyof typeof target];
            // if on client + target is async function
            if (typeof window !== "undefined" && typeof targetFn === "function" && targetFn.constructor?.name === "AsyncFunction") {
              const currentChainId = 137;
              // if reference chainId isn't up to date
              if (chainIdRef.current !== currentChainId) {
                // if there is no ongoing chain set promise
                if (!chainSetPromise.current) {
                  // set ready to false before making changes
                  setReady(false);
                  // set chain set promise to new promise
                  chainSetPromise.current = setChainPromise();
                }
                // eslint-disable-next-line func-names
                return async function (this: any, ...args: any[]) {
                  // wait for existing chain set promise
                  await chainSetPromise.current;
                  // return result of target function
                  return (targetFn as any)(...args);
                };
              }
            }
            // pass through all other getters
            return Reflect.get(target, prop, reciever);
          },
        }
      ),
    [ready]
  );

  return <BnsContext.Provider value={value}>{children}</BnsContext.Provider>;
}

const BNSContext: any = () => useContext(BnsContext);
export { BNSContext, EnsProvider };
