import { expand, getRect } from "css-box-model";
import { isPartiallyVisible } from "./visibility/is-visible";

const getShouldAnimate = (id, last, forceShouldAnimate) => {
  // Use a forced value if provided
  if (typeof forceShouldAnimate === "boolean") {
    return forceShouldAnimate;
  }

  // nothing to gauge animation from
  if (!last) {
    return true;
  }
  const { invisible, visible } = last;

  // it was previously invisible - no animation
  if (invisible[id]) {
    return false;
  }
  const previous = visible[id];
  return previous ? previous.shouldAnimate : true;
};

// Note: it is also an optimisation to not render the displacement on
// items when they are not longer visible.
// This prevents a lot of .render() calls when leaving / entering a list

function getTarget(draggable, displacedBy) {
  const marginBox = draggable.page.marginBox;

  // ## Visibility overscanning
  // We are expanding rather than offsetting the marginBox.
  // In some cases we want
  // - the target based on the starting position (such as when dropping outside of any list)
  // - the target based on the items position without starting displacement (such as when moving inside a list)
  // To keep things simple we just expand the whole area for this check
  // The worst case is some minor redundant offscreen movements
  const expandBy = {
    // pull backwards into viewport
    top: displacedBy.point.y,
    right: 0,
    bottom: 0,
    // pull backwards into viewport
    left: displacedBy.point.x
  };
  return getRect(expand(marginBox, expandBy));
}

export default function getDisplacementGroups({
  afterDragging,
  destination,
  displacedBy,
  viewport,
  forceShouldAnimate,
  last
}) {
  return afterDragging.reduce(
    function process(groups, draggable) {
      const target = getTarget(draggable, displacedBy);
      const id = draggable.descriptor.id;
      groups.all.push(id);
      const isVisible = isPartiallyVisible({
        target,
        destination,
        viewport,
        withDroppableDisplacement: true
      });
      if (!isVisible) {
        groups.invisible[draggable.descriptor.id] = true;
        return groups;
      }

      // item is visible

      const shouldAnimate = getShouldAnimate(id, last, forceShouldAnimate);
      const displacement = {
        draggableId: id,
        shouldAnimate
      };
      groups.visible[id] = displacement;
      return groups;
    },
    {
      all: [],
      visible: {},
      invisible: {}
    }
  );
}
