import * as React from "react";
import { ReactElement } from "react";
import * as R from "ramda";
import { Touchable } from "./Touchable";
import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";

import { useFocusManager } from "@applicaster/zapp-react-native-utils/focusManager";
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";

type ChildrenFunction = (
  focused: boolean,
  nextFocus: ParentFocus,
  ref: React.Ref<Touchable>
) => React.ReactNode;

type Props = {
  children: ReactElement<any, any> | ChildrenFunction;
  id: string;
  disableFocus?: boolean;
  blockFocus?: boolean;
  onPress?: (ref: FocusManager.FocusableRef) => void;
  onFocus?: (
    ref: FocusManager.FocusableRef,
    options: FocusManager.Android.CallbackOptions,
    context?: FocusManager.FocusContext
  ) => void;
  onBlur?: (
    ref: FocusManager.FocusableRef,
    options: FocusManager.Android.CallbackOptions
  ) => void;
  onPressIn?: (ref: FocusManager.FocusableRef) => void;
  onPressOut?: (ref: FocusManager.FocusableRef) => void;
  onLongPress?: (ref: FocusManager.FocusableRef) => void;
  onRegister?: () => void;
  onUnregister?: () => void;
  isFocusableCell?: boolean;
  /** only for FocusableScrollView */
  onSetIsFocusable?: (isFocusable: boolean) => void;
} & ParentFocus;

export const FocusableContext = React.createContext<
  {
    focused: boolean;
    // eslint-disable-next-line
    setIsFocusable: (enableFocus: boolean) => void;
    ref: FocusManager.FocusableRef;
    parentFocusableId: Option<string>;
  } & ParentFocus
>({
  focused: false,
  setIsFocusable: () => {},
  ref: { current: null },
  parentFocusableId: undefined,
});

export const useFocusable = () => React.useContext(FocusableContext);

function FocusableComponent(props: Props, forwardedRef) {
  const {
    children,
    id,
    nextFocusDown,
    nextFocusUp,
    nextFocusLeft,
    nextFocusRight,
    onPress,
    onFocus,
    onBlur,
    blockFocus,
    onPressIn,
    onPressOut,
    onLongPress,
    onRegister = noop,
    onUnregister = noop,
    isFocusableCell = true,
    onSetIsFocusable,
  } = props;

  const isRTL = useIsRTL();
  const focusManager = useFocusManager();
  const { ref: parentFocusableRef, parentFocusableId } = useFocusable();
  const touchableRef = React.useRef(null);

  const [focused, setFocused] = React.useState(() =>
    focusManager.isFocused(id)
  );

  const [disableFocus, setDisableFocus] = React.useState(props.disableFocus);
  const prevDisableFocus = usePrevious(props.disableFocus);

  React.useImperativeHandle(forwardedRef, () => touchableRef.current);

  React.useEffect(() => {
    if (props.disableFocus !== prevDisableFocus) {
      setDisableFocus(props.disableFocus ?? false);
    }
  }, [props.disableFocus, prevDisableFocus]);

  React.useEffect(() => {
    if (disableFocus) {
      focusManager.onDisableFocusChange(id);
    }
  }, [disableFocus]);

  React.useLayoutEffect(() => {
    if (id) {
      const unregister = focusManager.registerFocusable({
        touchableRef,
        parentFocusableRef,
        isFocusableCell,
        parentFocusableId,
      });

      onRegister();

      return () => {
        unregister();
        onUnregister();
      };
    }
  }, [id, onRegister, onUnregister, isFocusableCell, parentFocusableId]);

  if (R.isNil(id)) {
    // eslint-disable-next-line no-console
    console.error(
      "Focusable: Id parameter or Setting ref on focusable component is required"
    );
  }

  const _onFocus = React.useCallback(
    (ref, options, context) => {
      setFocused(true);
      onFocus?.(ref, options, context);
    },
    [onFocus]
  );

  const _onBlur = React.useCallback(
    (ref, options = {}) => {
      if (ref.props.id !== focusManager.focusedId) {
        setFocused(false);
      }

      onBlur?.(ref, options);
    },
    [onBlur]
  );

  const setIsFocusable = React.useCallback(
    (blockFocus) => {
      onSetIsFocusable?.(blockFocus);
      setDisableFocus(!blockFocus);
    },
    [onSetIsFocusable]
  );

  const parentFocus = React.useMemo(
    () => ({
      nextFocusRight: isRTL ? nextFocusLeft : nextFocusRight,
      nextFocusLeft: isRTL ? nextFocusRight : nextFocusLeft,
      nextFocusUp,
      nextFocusDown,
    }),
    [isRTL, nextFocusDown, nextFocusUp, nextFocusLeft, nextFocusRight]
  );

  const contextState = React.useMemo(() => {
    return {
      focused,
      ...parentFocus,
      ref: touchableRef,
      setIsFocusable,
      parentFocusableId: id,
    };
  }, [parentFocus, focused, id]);

  return (
    <Touchable
      ref={touchableRef}
      id={id}
      onFocus={_onFocus}
      onBlur={_onBlur}
      onPress={onPress}
      onLongPress={onLongPress}
      disableFocus={disableFocus}
      blockFocus={blockFocus}
      onPressIn={onPressIn}
      onPressOut={onPressOut}
      {...parentFocus}
    >
      <FocusableContext.Provider value={contextState}>
        {React.useMemo(
          () =>
            typeof children === "function"
              ? children(focused, parentFocus, touchableRef)
              : children,
          [children, focused, parentFocus]
        )}
      </FocusableContext.Provider>
    </Touchable>
  );
}

export const Focusable = React.forwardRef(FocusableComponent);
