import React, {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState,
} from "react";
import * as R from "ramda";

type ProviderProps = {
  children: React.ReactChild;
};

type ContextType<T> = {
  context: T;
  setContext: (T) => void;
};

type ContextOptions<T, S> = {
  selector: (context: S, ...hookArgs: any[]) => T | null;
  prepareContext: (newContext: T, currentContext?: S, ...hookArgs: any[]) => S;
};

export function createZappPipesContext<T, S = T>(
  initialContext: ContextType<S>,
  options?: ContextOptions<T, S>
) {
  const Context = createContext<ContextType<S>>(initialContext);

  const { selector = R.identity, prepareContext = R.identity } = options || {};

  function useZappPipesContext(...hookArgs: any[]): [T, (T) => void] {
    const { context, setContext } = useContext(Context);

    const contextValue = useMemo(
      () => selector(context, ...hookArgs),
      [context, R.join("-", hookArgs)]
    );

    const contextSetter = useCallback(
      (newContext: T) => {
        setContext(prepareContext(newContext, context, ...hookArgs));
      },
      [context, setContext, R.join("-", hookArgs)]
    );

    return useMemo(
      () => [contextValue, contextSetter],
      [contextValue, contextSetter]
    );
  }

  function Provider({ children }: ProviderProps) {
    const [context, setContext] = useState<S>(initialContext.context);

    return (
      <Context.Provider value={{ context, setContext }}>
        {children}
      </Context.Provider>
    );
  }

  function withProvider(Component: React.ComponentType<any>) {
    return function WithProvider(props: any) {
      return (
        <Provider>
          <Component {...props} />
        </Provider>
      );
    };
  }

  return {
    Context,
    useZappPipesContext,
    Provider,
    withProvider,
  };
}
