import type { Position } from 'css-box-model';
import type {
  Viewport,
  DraggableDimension,
  DroppableDimension,
  LiftEffect,
} from '../../../types';
import { distance } from '../../position';
import { isTotallyVisible } from '../../visibility/is-visible';
import withDroppableDisplacement from '../../with-scroll-change/with-droppable-displacement';
import {
  getCurrentPageBorderBox,
  getCurrentPageBorderBoxCenter,
} from './without-starting-displacement';

interface Args {
  pageBorderBoxCenter: Position;
  viewport: Viewport;
  // the droppable that is being moved to
  destination: DroppableDimension;
  // the droppables inside the destination
  insideDestination: DraggableDimension[];
  afterCritical: LiftEffect;
}

export default ({
  pageBorderBoxCenter,
  viewport,
  destination,
  insideDestination,
  afterCritical,
}: Args): DraggableDimension | null => {
  const sorted: DraggableDimension[] = insideDestination
    .filter(
      (
        draggable: DraggableDimension,
      ): boolean => // Allowing movement to draggables that are not visible in the viewport
        // but must be visible in the droppable
        // We can improve this, but this limitation is easier for now
        isTotallyVisible({
          target: getCurrentPageBorderBox(draggable, afterCritical),
          destination,
          viewport: viewport.frame,
          withDroppableDisplacement: true,
        }),
    )
    .sort((a: DraggableDimension, b: DraggableDimension): number => {
      // Need to consider the change in scroll in the destination
      const distanceToA = distance(
        pageBorderBoxCenter,
        withDroppableDisplacement(
          destination,
          getCurrentPageBorderBoxCenter(a, afterCritical),
        ),
      );
      const distanceToB = distance(
        pageBorderBoxCenter,
        withDroppableDisplacement(
          destination,
          getCurrentPageBorderBoxCenter(b, afterCritical),
        ),
      );

      // if a is closer - return a
      if (distanceToA < distanceToB) {
        return -1;
      }

      // if b is closer - return b
      if (distanceToB < distanceToA) {
        return 1;
      }

      // if the distance to a and b are the same:
      // return the one with the lower index (it will be higher on the main axis)
      return a.descriptor.index - b.descriptor.index;
    });

  return sorted[0] || null;
};
