import { useSelector } from '../../useSelector';
import { NestedTableDragAndDropState } from './NestedTableDragAndDropState';
import { NestedTableKeyedItem } from '../../state';

/**
 * Factory function to create a custom hook `useIsDropDisabled` that determines
 * whether the drop is disabled for a given node based on drag-and-drop state.
 */
export const createUseIsDropDisabled = <C extends string>(
  state: NestedTableDragAndDropState<C>,
) => {
  return (targetNodeId: string | number): boolean => {
    // Get the ID of the currently active (dragged) item
    const activeItemId = useSelector(() => state.dnd.activeId);

    // If there's no active (dragged) item, disable dropping
    if (!activeItemId) {
      return true;
    }

    // Retrieve the state of the active (dragged) item and the target (hovered) item
    const activeItemState =
      state.nestedState.getKeyedItem(activeItemId)?.item.state;
    const targetItemState = state.nestedState.getKeyedItem(
      targetNodeId.toString(),
    )?.item.state;

    // If either the active or target node is missing, disable dropping
    if (!activeItemState || !targetItemState) {
      return true;
    }

    const activeKeyedItem = state.dnd.collection.getKeyedItem(activeItemId);
    const targetKeyedItem = state.dnd.collection.getKeyedItem(
      targetNodeId.toString(),
    );

    if (!activeKeyedItem || !targetKeyedItem) {
      return true;
    }

    // If levels contain the same entity, dropping is always allowed
    if (state.isSameEntityAsOtherLevels) {
      return false;
    }

    // Get the index of the active (dragged) item and the target (hovered) item in the nested state
    const activeItemIndex =
      state.nestedState.getKeyedItem(activeItemId)?.index || 0;
    const targetItemIndex =
      state.nestedState.getKeyedItem(targetNodeId.toString())?.index || 0;

    // Get the previous and next items relative to the target item
    const previousItem = (
      activeItemIndex >= targetItemIndex
        ? state.nestedState.keyedItems[targetItemIndex - 1]
        : state.nestedState.keyedItems[targetItemIndex]
    ) as NestedTableKeyedItem<C, any, any> | null;

    const nextItem = (
      activeItemIndex <= targetItemIndex
        ? state.nestedState.keyedItems[targetItemIndex + 1]
        : state.nestedState.keyedItems[targetItemIndex]
    ) as NestedTableKeyedItem<C, any, any> | null;

    // Check if the dragged item is moving down to a sublevel
    const isMovingDownToSublevel =
      targetItemState.childrenDepth === activeItemState.childrenDepth - 1 &&
      nextItem != null &&
      nextItem.item.state.childrenDepth >= activeItemState.childrenDepth &&
      targetItemIndex === previousItem?.index;

    if (isMovingDownToSublevel) {
      return false;
    }

    // Check if the dragged item is moving up to a sublevel
    const isMovingUpToSubLevel =
      targetItemState.childrenDepth === activeItemState.childrenDepth - 1 &&
      previousItem?.item.state.childrenDepth ===
        activeItemState.childrenDepth &&
      (nextItem == null ||
        nextItem.item.state.childrenDepth < activeItemState.childrenDepth) &&
      targetItemIndex === nextItem?.index;

    if (isMovingUpToSubLevel) {
      return false;
    }

    // Determine if the target item has children
    const targetItemHasChildren =
      targetItemState.childrenDepth + 1 === nextItem?.item.state?.childrenDepth;

    // Check if the dragged item is on the same level without children
    const isSameLevelWithoutChildren =
      !targetItemHasChildren &&
      targetItemState.childrenDepth === activeItemState.childrenDepth;

    if (isSameLevelWithoutChildren) {
      return false;
    }

    // Check if the dragged item is moving to the next level after a node without children
    const isNextLevelWithoutChildren =
      !targetItemHasChildren &&
      previousItem?.item.state.childrenDepth ===
        activeItemState.childrenDepth - 1;

    if (isNextLevelWithoutChildren) {
      return false;
    }

    // Check if the dragged item is stepping out to the same level (not entering a sublevel)
    const isStepOutToSameLevel =
      !targetItemHasChildren &&
      previousItem != null &&
      previousItem.item.state.childrenDepth >=
        activeItemState.childrenDepth + 1 &&
      (nextItem == null ||
        nextItem.item.state.childrenDepth <= activeItemState.childrenDepth);

    if (isStepOutToSameLevel) {
      return false;
    }

    return true;
  };
};
