import React, { useState, useRef, useCallback, useMemo } from 'react';
var MOVEMENT_DIRECTION = /*#__PURE__*/function (MOVEMENT_DIRECTION) {
  MOVEMENT_DIRECTION["UP"] = "UP";
  MOVEMENT_DIRECTION["RIGHT"] = "RIGHT";
  MOVEMENT_DIRECTION["DOWN"] = "DOWN";
  MOVEMENT_DIRECTION["LEFT"] = "LEFT";
  return MOVEMENT_DIRECTION;
}(MOVEMENT_DIRECTION || {});
var PAGE_SCROLL_DIRECTION = /*#__PURE__*/function (PAGE_SCROLL_DIRECTION) {
  PAGE_SCROLL_DIRECTION["UP"] = "UP";
  PAGE_SCROLL_DIRECTION["DOWN"] = "DOWN";
  return PAGE_SCROLL_DIRECTION;
}(PAGE_SCROLL_DIRECTION || {});
export const TableKeyboardNavigationContext = /*#__PURE__*/React.createContext(null);

// due to virtualization not all cell definitions stored in cells map will have non-null ref
// this function will return either first or last non-null cell
function getEdgeRenderedCell(cells, first) {
  let i = 0;
  // if we looking for the last existing cell ref than we should go upwards from the last cell available in the cells map
  if (!first) {
    const arr = Array.from(cells.values());
    arr.sort((a, b) => b.cellRowIndex - a.cellRowIndex);
    i = arr?.[0]?.cellRowIndex;
  }
  while (cells.has(`${i}-0`)) {
    if (cells.get(`${i}-0`)?.cellRef?.current) {
      return cells.get(`${i}-0`);
    }
    if (first) {
      i++;
    } else {
      i--;
    }
  }
}
export function getCellId({
  cellRowIndex,
  cellColumnIndex
}) {
  return `${cellRowIndex}-${cellColumnIndex}`;
}
export function useTableKeyboardNavigation({
  tableRef,
  columnsCount,
  rowsCount
}) {
  const [focusedCell, setFocusedCell] = useState();
  const cells = useRef(new Map());
  const registerCell = useCallback(({
    cellRowIndex,
    cellColumnIndex,
    cellRef,
    cellAction
  }) => {
    cells.current.set(getCellId({
      cellRowIndex,
      cellColumnIndex
    }), {
      cellRowIndex,
      cellColumnIndex,
      cellRef,
      cellAction
    });
  }, []);
  const registerCustomCellAction = useCallback((cellId, actionFn) => {
    const cellToUpdate = cells.current.get(cellId);
    if (!cellToUpdate) {
      // eslint-disable-next-line no-console
      console.error(`[useTableKeyboardNavigation]: No cell for custom cell renderer with id ${cellId}`);
      return;
    }
    cells.current.set(cellId, {
      ...cellToUpdate,
      cellAction: actionFn
    });
  }, []);
  const shouldRespondToKeyboardEvents = () => {
    return tableRef.current?.contains(document.activeElement);
  };
  const isCorrectTable = () => {
    const focusedCellDef = cells.current.get(focusedCell);

    // we might have nested tables. In this case we will be having 2 hooks invoked
    // in case we are currently focused on the inner table cell document.activeElement !== focusedCell.cellRef.current of the parent table
    // thus we should not react to keyboard events on the parent cell. And vice versa
    return !!focusedCellDef && focusedCellDef.cellRef.current === document.activeElement;
  };
  const shouldPerformCellAction = () => {
    if (!focusedCell || cells.current.size === 0 || !shouldRespondToKeyboardEvents() || !isCorrectTable()) {
      return false;
    }
    if (!cells.current.get(focusedCell)) {
      // eslint-disable-next-line no-console
      console.error(`[useTableKeyboardNavigation]: No cell definition found for cell id ${focusedCell}`);
      return false;
    }
    return true;
  };
  const shouldPreventDefaultArrowBehaviour = e => {
    /**
     * In most cases we want to prevent default behaviour of arrow
     * keys on the table because they scroll the table by default and we are using
     * them to move focus between cells. But in some cases, when cells might have interactive
     * elements withing them, e.g. inputs, preventing default behaviour might berak stuff
     * e.g. input cursor is not moved by arrow keys. For this sake we are checking
     * whether element which triggered the event is actually a currently focused cell
     */
    const focusedCellDef = cells.current.get(focusedCell);
    return e.target === focusedCellDef?.cellRef?.current;
  };
  const selectCell = useCallback(nextFocusedCellId => {
    const nextSelectedCell = cells.current.get(nextFocusedCellId);
    if (!nextSelectedCell) {
      // eslint-disable-next-line no-console
      console.error(`[useTableKeyboardNavigation]: No cell definition found for the cell to select above with id: ${nextFocusedCellId}`);
      return;
    }
    nextSelectedCell.cellRef.current?.focus();
    nextSelectedCell.cellRef.current?.scrollIntoView?.({
      behavior: 'smooth',
      block: 'nearest'
    });
    setFocusedCell(nextFocusedCellId);
  }, []);
  const selectNextFocusedCell = useCallback(direction => {
    if (!shouldPerformCellAction()) {
      return;
    }
    const focusedCellDef = cells.current.get(focusedCell);
    const {
      cellColumnIndex,
      cellRowIndex
    } = focusedCellDef;
    switch (direction) {
      case MOVEMENT_DIRECTION.UP:
        // if we are not on the top row
        if (cellRowIndex !== -1) {
          const nextSelectedCellId = getCellId({
            cellRowIndex: cellRowIndex - 1,
            cellColumnIndex
          });
          selectCell(nextSelectedCellId);
        }
        break;
      case MOVEMENT_DIRECTION.RIGHT:
        // if we are not on the most right cell
        if (cellColumnIndex !== columnsCount - 1) {
          const nextSelectedCellId = getCellId({
            cellRowIndex,
            cellColumnIndex: cellColumnIndex + 1
          });
          selectCell(nextSelectedCellId);
        }
        break;
      case MOVEMENT_DIRECTION.DOWN:
        // if we are not on the last row
        if (cellRowIndex !== rowsCount - 1) {
          const nextSelectedCellId = getCellId({
            cellRowIndex: cellRowIndex + 1,
            cellColumnIndex
          });
          selectCell(nextSelectedCellId);
        }
        break;
      case MOVEMENT_DIRECTION.LEFT:
        // if we are not on the first column
        if (cellColumnIndex !== 0) {
          const nextSelectedCellId = getCellId({
            cellRowIndex,
            cellColumnIndex: cellColumnIndex - 1
          });
          selectCell(nextSelectedCellId);
        }
        break;
      default:
        break;
    }
  }, [focusedCell]);

  // TODO check custom params except for event (like when we need to pass rowdata, or smth like that)
  const performCellAction = useCallback(e => {
    if (!shouldPerformCellAction()) {
      return;
    }
    const focusedCellDef = cells.current.get(focusedCell);
    focusedCellDef?.cellAction?.(e);
  }, [focusedCell]);
  const selectLastCellInTheRow = useCallback(() => {
    if (!shouldPerformCellAction()) {
      return;
    }
    const focusedCellDef = cells.current.get(focusedCell);
    const selectedRow = focusedCellDef?.cellRowIndex;
    const nextSelectedCellId = getCellId({
      cellRowIndex: selectedRow,
      cellColumnIndex: columnsCount - 1
    });
    selectCell(nextSelectedCellId);
  }, [focusedCell]);
  const selectFirstCellInTheRow = useCallback(() => {
    if (!shouldPerformCellAction()) {
      return;
    }
    const focusedCellDef = cells.current.get(focusedCell);
    const selectedRowIndex = focusedCellDef?.cellRowIndex;
    const nextSelectedCellId = getCellId({
      cellRowIndex: selectedRowIndex,
      cellColumnIndex: 0
    });
    selectCell(nextSelectedCellId);
  }, [focusedCell]);
  const scrollPage = useCallback(direction => {
    if (!shouldPerformCellAction()) {
      return;
    }
    const focusedCellDef = cells.current.get(focusedCell);
    const selectedColumnIndex = focusedCellDef.cellColumnIndex;
    // in our case we'll just go to the first or last cell that has ref !== null
    // because due to virtualization only ~ 20 items are actiually rendered at the same time
    const nextRowIndex = getEdgeRenderedCell(cells.current, direction === PAGE_SCROLL_DIRECTION.UP)?.cellRowIndex;
    if (nextRowIndex !== undefined) {
      selectCell(getCellId({
        cellRowIndex: nextRowIndex,
        cellColumnIndex: selectedColumnIndex
      }));
    }
  }, [focusedCell]);
  const onTableKeyDownHandler = useCallback(e => {
    switch (e.key) {
      case 'ArrowUp':
        if (shouldPreventDefaultArrowBehaviour(e)) {
          e.preventDefault();
        }
        selectNextFocusedCell(MOVEMENT_DIRECTION.UP);
        break;
      case 'ArrowRight':
        if (shouldPreventDefaultArrowBehaviour(e)) {
          e.preventDefault();
        }
        selectNextFocusedCell(MOVEMENT_DIRECTION.RIGHT);
        break;
      case 'ArrowDown':
        if (shouldPreventDefaultArrowBehaviour(e)) {
          e.preventDefault();
        }
        selectNextFocusedCell(MOVEMENT_DIRECTION.DOWN);
        break;
      case 'ArrowLeft':
        if (shouldPreventDefaultArrowBehaviour(e)) {
          e.preventDefault();
        }
        selectNextFocusedCell(MOVEMENT_DIRECTION.LEFT);
        break;
      case 'Enter':
        // This fix is needed for row actions to work properly because we have global document event listeners
        // in use-dropdown-menu-keyboard-navigation.ts that are negating effects of table-react listeners
        // TODO remove timeout after on of the https://datarobot.atlassian.net/browse/UIUX-11638 & https://datarobot.atlassian.net/browse/UIUX-11271 are done (or both of them)
        setTimeout(() => {
          performCellAction(e);
        }, 10);
        break;
      case 'End':
        e.preventDefault();
        selectLastCellInTheRow();
        break;
      case 'Home':
        e.preventDefault();
        selectFirstCellInTheRow();
        break;
      case 'PageDown':
        e.preventDefault();
        scrollPage(PAGE_SCROLL_DIRECTION.DOWN);
        break;
      case 'PageUp':
        e.preventDefault();
        scrollPage(PAGE_SCROLL_DIRECTION.UP);
        break;
      default:
        break;
    }
  }, [selectNextFocusedCell]);
  return useMemo(() => ({
    focusedCell,
    registerCell,
    onTableKeyDownHandler,
    setFocusedCell,
    registerCustomCellAction
  }), [focusedCell, onTableKeyDownHandler, registerCustomCellAction]);
}