import {
  closestCorners,
  DroppableContainer,
  getFirstCollision,
  getScrollableAncestors,
  KeyboardCode,
  KeyboardCoordinateGetter,
} from '@wix/wix-style-react-incubator/dnd-kit/core';
import { subtract } from '@wix/wix-style-react-incubator/dnd-kit/utilities';
import { hasSortableData } from '@wix/wix-style-react-incubator/dnd-kit/sortable';

const directions: string[] = [
  KeyboardCode.Down,
  KeyboardCode.Right,
  KeyboardCode.Up,
  KeyboardCode.Left,
];

export const sortableGridKeyboardCoordinates =
  ({
    containerDistanceThreshold = 0,
  }: { containerDistanceThreshold?: number } = {}): KeyboardCoordinateGetter =>
  (
    event,
    {
      context: {
        active,
        collisionRect,
        droppableRects,
        droppableContainers,
        over,
        scrollableAncestors,
      },
    },
  ) => {
    if (directions.includes(event.code)) {
      event.preventDefault();

      if (!active || !collisionRect) {
        return;
      }

      const filteredContainers: DroppableContainer[] = [];

      droppableContainers.getEnabled().forEach((entry) => {
        if (!entry || entry?.disabled) {
          return;
        }

        const rect = droppableRects.get(entry.id);

        if (!rect) {
          return;
        }

        switch (event.code) {
          case KeyboardCode.Down:
            if (rect.top - collisionRect.top > containerDistanceThreshold) {
              filteredContainers.push(entry);
            }
            break;
          case KeyboardCode.Up:
            if (collisionRect.top - rect.top > containerDistanceThreshold) {
              filteredContainers.push(entry);
            }
            break;
          case KeyboardCode.Left:
            if (collisionRect.left - rect.left > containerDistanceThreshold) {
              filteredContainers.push(entry);
            }
            break;
          case KeyboardCode.Right:
            if (rect.left - collisionRect.left > containerDistanceThreshold) {
              filteredContainers.push(entry);
            }
            break;
        }
      });

      const collisions = closestCorners({
        active,
        collisionRect,
        droppableRects,
        droppableContainers: filteredContainers,
        pointerCoordinates: null,
      });
      let closestId = getFirstCollision(collisions, 'id');

      if (closestId === over?.id && collisions.length > 1) {
        closestId = collisions[1].id;
      }

      if (closestId != null) {
        const activeDroppable = droppableContainers.get(active.id);
        const newDroppable = droppableContainers.get(closestId);
        const newRect = newDroppable
          ? droppableRects.get(newDroppable.id)
          : null;
        const newNode = newDroppable?.node.current;

        if (newNode && newRect && activeDroppable && newDroppable) {
          const newScrollAncestors = getScrollableAncestors(newNode);
          const hasDifferentScrollAncestors = newScrollAncestors.some(
            (element, index) => scrollableAncestors[index] !== element,
          );
          const hasSameContainer = isSameContainer(
            activeDroppable,
            newDroppable,
          );
          const isAfterActive = isAfter(activeDroppable, newDroppable);
          const offset =
            hasDifferentScrollAncestors || !hasSameContainer
              ? {
                  x: 0,
                  y: 0,
                }
              : {
                  x: isAfterActive ? collisionRect.width - newRect.width : 0,
                  y: isAfterActive ? collisionRect.height - newRect.height : 0,
                };
          const rectCoordinates = {
            x: newRect.left,
            y: newRect.top,
          };

          const newCoordinates =
            offset.x && offset.y
              ? rectCoordinates
              : subtract(rectCoordinates, offset);

          return newCoordinates;
        }
      }
    }

    return undefined;
  };

function isSameContainer(a: DroppableContainer, b: DroppableContainer) {
  if (!hasSortableData(a) || !hasSortableData(b)) {
    return false;
  }

  return (
    a.data.current.sortable.containerId === b.data.current.sortable.containerId
  );
}

function isAfter(a: DroppableContainer, b: DroppableContainer) {
  if (!hasSortableData(a) || !hasSortableData(b)) {
    return false;
  }

  if (!isSameContainer(a, b)) {
    return false;
  }

  return a.data.current.sortable.index < b.data.current.sortable.index;
}
