import getDisplacedBy from "../get-displaced-by";
import removeDraggableFromList from "../remove-draggable-from-list";
import isHomeOf from "../droppable/is-home-of";
import { find } from "../../native-with-fallback";
import getDidStartAfterCritical from "../did-start-after-critical";
import calculateReorderImpact from "../calculate-drag-impact/calculate-reorder-impact";
import getIsDisplaced from "../get-is-displaced";
import { horizontal, vertical } from "../axis";

function atIndex({ draggable, closest, inHomeList }) {
  if (!closest) {
    return null;
  }
  if (!inHomeList) {
    return closest.descriptor.index;
  }
  if (closest.descriptor.index > draggable.descriptor.index) {
    return closest.descriptor.index - 1;
  }
  return closest.descriptor.index;
}

const getReorderImpact = ({
  pageBorderBoxWithDroppableScroll: targetRect,
  draggable,
  destination,
  insideDestination,
  last,
  viewport,
  afterCritical
}) => {
  const axis = destination.axis;
  const displacedBy = getDisplacedBy(destination.axis, draggable.displaceBy);
  const displacement = displacedBy.value;
  const targetStart = targetRect[axis.start];
  const targetEnd = targetRect[axis.end];
  const uprightAxis = axis.direction === "horizontal" ? vertical : horizontal;
  const uprightAxisBoundStart = targetRect[uprightAxis.start];
  const withoutDragging = removeDraggableFromList(draggable, insideDestination);
  const closest = find(withoutDragging, (child) => {
    const id = child.descriptor.id;
    const childCenter = child.page.borderBox.center[axis.line];
    if (axis.grid) {
      const uprightChildCenter = child.page.borderBox.center[uprightAxis.line];
      if (!(uprightAxisBoundStart < uprightChildCenter)) return false;
    }
    const didStartAfterCritical = getDidStartAfterCritical(id, afterCritical);
    const isDisplaced = getIsDisplaced({
      displaced: last,
      id
    });

    /*
    Note: we change things when moving *past* the child center - not when it hits the center
    If we make it when we *hit* the child center then there can be
    a hit on the next update causing a flicker.
      - Update 1: targetBottom hits center => displace backwards
    - Update 2: targetStart is now hitting the displaced center => displace forwards
    - Update 3: goto 1 (boom)
    */

    if (didStartAfterCritical) {
      // Continue to displace while targetEnd before the childCenter
      // Move once we *move forward past* the childCenter
      if (isDisplaced) {
        return targetEnd <= childCenter;
      }

      // Has been moved backwards from where it started
      // Displace forwards when targetStart *moves backwards past* the displaced childCenter
      return targetStart < childCenter - displacement;
    }

    // Item has been shifted forward.
    // Remove displacement when targetEnd moves forward past the displaced center
    if (isDisplaced) {
      return targetEnd <= childCenter + displacement;
    }

    // Item is behind the dragging item
    // We want to displace it if the targetStart goes *backwards past* the childCenter
    return targetStart < childCenter;
  });
  const newIndex = atIndex({
    draggable,
    closest,
    inHomeList: isHomeOf(draggable, destination)
  });

  // TODO: index cannot be null?
  // otherwise return null from there and return empty impact
  // that was calculate reorder impact does not need to account for a null index
  return calculateReorderImpact({
    draggable,
    insideDestination,
    destination,
    viewport,
    last,
    displacedBy,
    index: newIndex
  });
};

export default getReorderImpact;
