import { useCallback, useEffect, useReducer, useRef } from 'react';

/**
 * a custom hook that handles keyboard navigation for Arrow keys based on a
 * given listSize, and a step (for up and down arrows).
 *
 * @param {number} listSize
 * @param {number} upDownStep
 *
 * Example usage:
 *    const list = ['Confluence','Jira','Atlaskit'];
 *    const {
 *      selectedItemIndex,
 *      focusedItemIndex,
 *      focusOnSearch,
 *      focusOnViewMore,
 *      setFocusedItemIndex,
 *      onKeyDown
 *    } = useSelectAndFocusOnArrowNavigation(list.length - 1, 1);
 *
 *    return (
 *      <div onKeyDown={onKeyDown}>
 *        <SearchBar onClick={() => setFocusedItemIndex(undefined)} focus={focusOnSearch} />
 *        {list.map((item, index) => (
 *           <ListItem
 *            title={item}
 *            style={{ ..., color: selected ? 'selectedStateColor' : defaultColor }}
 *            onClick={() => {
 *              setFocusedItemIndex(index);
 *            }
 *          />
 *        )}
 *      </div>
 *    );
 *
 *    const SearchBar = ({ focus }) => {
 *      const ref = useRefToFocusOrScroll(focus);
 *      return <input ref={ref} />
 *    }
 *
 */

export let ACTIONS = /*#__PURE__*/function (ACTIONS) {
  ACTIONS["FOCUS_SEARCH"] = "focusOnSearch";
  ACTIONS["UPDATE_STATE"] = "updateState";
  ACTIONS["MOVE"] = "move";
  return ACTIONS;
}({});
const reducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.UPDATE_STATE:
      return {
        ...state,
        ...action.payload
      };
    case ACTIONS.FOCUS_SEARCH:
      return {
        ...state,
        focusedItemIndex: undefined,
        focusOnSearch: true,
        focusOnViewMore: false
      };
    case ACTIONS.MOVE:
      return moveReducer(state, action);
  }
  return state;
};
const moveReducer = (state, action) => {
  const {
    listSize,
    canFocusViewMore
  } = state;
  if (state.focusOnSearch) {
    // up arrow
    if (action.payload.positions && action.payload.positions <= -1) {
      return {
        ...state,
        focusOnSearch: false,
        focusOnViewMore: !!canFocusViewMore,
        focusedItemIndex: canFocusViewMore ? undefined : listSize,
        selectedItemIndex: canFocusViewMore ? undefined : listSize
      };
    } else {
      return {
        ...state,
        focusOnSearch: false,
        focusOnViewMore: false,
        focusedItemIndex: 0,
        selectedItemIndex: 0
      };
    }
  }
  if (state.focusOnViewMore) {
    // down arrow
    if (action.payload.positions === 1) {
      return {
        ...state,
        focusOnSearch: true,
        focusOnViewMore: false,
        focusedItemIndex: undefined,
        // if search is focused then select first item.
        selectedItemIndex: 0
      };
    } else {
      return {
        ...state,
        focusOnSearch: false,
        focusOnViewMore: false,
        focusedItemIndex: listSize,
        selectedItemIndex: listSize
      };
    }
  }
  const newIndex = state.selectedItemIndex ? state.selectedItemIndex + action.payload.positions : action.payload.positions;
  const safeIndex = ensureSafeIndex(newIndex, state.listSize);
  // down arrow key is pressed or right arrow key is pressed.
  if (state.focusedItemIndex !== undefined && action.payload.positions && action.payload.positions >= 1) {
    // when multi column element browser is open and we are in last
    //  row then newIndex will be greater than listSize when
    //  down arrow key is pressed.
    // Or when last item is focused and down or right arrow key is pressed.
    const isLastItemFocused = newIndex > listSize;
    const focusOnSearch = isLastItemFocused && !canFocusViewMore;
    const focusOnViewMore = isLastItemFocused && !!canFocusViewMore;
    // if search is focused, then select first item.
    // otherwise if view more is focused then select item should be undefined.
    const selectedItemIndex = focusOnSearch ? 0 : focusOnViewMore ? undefined : safeIndex;
    return {
      ...state,
      focusOnSearch,
      focusOnViewMore,
      selectedItemIndex,
      focusedItemIndex: isLastItemFocused ? undefined : safeIndex
    };
  }

  // up arrow key is pressed or left arrow key is pressed.
  if (state.focusedItemIndex !== undefined && action.payload.positions && action.payload.positions <= -1) {
    // if arrow up key is pressed when focus is in first row,
    //  or, arrow left key is pressed when first item is focused,
    //  then newIndex will become less than zero.
    // In this case, focus search, and, kept previously selected item.
    const isFirstRowFocused = newIndex < 0;
    // if focus goes to search then kept last selected item in first row.
    const selectedItemIndex = isFirstRowFocused ? state.selectedItemIndex : safeIndex;
    return {
      ...state,
      // focus search if first item is focused on up or left arrow key
      focusOnSearch: isFirstRowFocused,
      focusOnViewMore: false,
      focusedItemIndex: isFirstRowFocused ? undefined : safeIndex,
      selectedItemIndex
    };
  }
  return {
    ...state,
    focusOnSearch: false,
    focusOnViewMore: false,
    selectedItemIndex: safeIndex,
    focusedItemIndex: safeIndex
  };
};
const initialState = {
  focusOnSearch: true,
  focusOnViewMore: false,
  selectedItemIndex: 0,
  focusedItemIndex: undefined,
  listSize: 0
};
const getInitialState = (listSize, canFocusViewMore) => initialState => ({
  ...initialState,
  listSize,
  canFocusViewMore
});
function useSelectAndFocusOnArrowNavigation(listSize, step, canFocusViewMore) {
  const [state, dispatch] = useReducer(reducer, initialState, getInitialState(listSize, canFocusViewMore));
  useEffect(() => {
    dispatch({
      type: ACTIONS.UPDATE_STATE,
      payload: {
        canFocusViewMore
      }
    });
  }, [canFocusViewMore]);
  const {
    selectedItemIndex,
    focusedItemIndex,
    focusOnSearch,
    focusOnViewMore
  } = state;
  const reset = useCallback(listSize => {
    let payload = {
      ...initialState,
      listSize
    };
    dispatch({
      type: ACTIONS.UPDATE_STATE,
      payload
    });
  }, []);
  const removeFocusFromSearchAndSetOnItem = useCallback(index => {
    const payload = {
      focusedItemIndex: index,
      selectedItemIndex: index,
      focusOnSearch: false,
      focusOnViewMore: false
    };
    dispatch({
      type: ACTIONS.UPDATE_STATE,
      payload
    });
  }, [dispatch]);
  const setFocusOnSearch = useCallback(() => {
    dispatch({
      type: ACTIONS.FOCUS_SEARCH,
      payload: {}
    });
  }, [dispatch]);
  const isMoving = useRef(false);
  const move = useCallback((e, positions, actualStep) => {
    e.preventDefault();
    e.stopPropagation();

    // avoid firing 2 moves at the same time when holding an arrow down as this can freeze the screen
    if (!isMoving.current) {
      isMoving.current = true;
      requestAnimationFrame(() => {
        isMoving.current = false;
        dispatch({
          type: ACTIONS.MOVE,
          payload: {
            positions,
            step: actualStep
          }
        });
      });
    }
  }, []);
  const onKeyDown = useCallback(e => {
    const avoidKeysWhileSearching = ['/',
    // While already focused on search bar, let users type in.
    'ArrowRight', 'ArrowLeft'];
    if (focusOnSearch && avoidKeysWhileSearching.includes(e.key)) {
      return;
    }
    switch (e.key) {
      case '/':
        e.preventDefault();
        e.stopPropagation();
        return setFocusOnSearch();
      case 'ArrowRight':
        return move(e, +1);
      case 'ArrowLeft':
        return move(e, -1);
      case 'ArrowDown':
        return move(e, +step);
      case 'ArrowUp':
        return move(e, -step, step);
    }
  }, [focusOnSearch, setFocusOnSearch, move, step]);
  useEffect(() => {
    // To reset selection when user filters
    reset(listSize);
  }, [listSize, reset]);
  return {
    selectedItemIndex,
    onKeyDown,
    focusOnSearch,
    focusOnViewMore,
    setFocusOnSearch,
    focusedItemIndex,
    setFocusedItemIndex: removeFocusFromSearchAndSetOnItem
  };
}
export const ensureSafeIndex = (index, listSize) => {
  if (index < 0) {
    return 0;
  }
  if (index > listSize) {
    return listSize;
  }
  return index;
};
export default useSelectAndFocusOnArrowNavigation;