/* eslint-disable no-unused-vars */

import { connect } from "react-redux";
import memoizeOne from "memoize-one";
import Droppable from "./droppable";
import isStrictEqual from "../is-strict-equal";
import whatIsDraggedOver from "../../state/droppable/what-is-dragged-over";
import { updateViewportMaxScroll as updateViewportMaxScrollAction } from "../../state/action-creators";
import StoreContext from "../context/store-context";
import whatIsDraggedOverFromResult from "../../state/droppable/what-is-dragged-over-from-result";
import { invariant } from "../../invariant.js";

const isMatchingType = (type, critical) => type === critical.droppable.type;
const getDraggable = (critical, dimensions) => dimensions.draggables[critical.draggable.id];

// Returning a function to ensure each
// Droppable gets its own selector
export const makeMapStateToProps = () => {
  const idleWithAnimation = {
    placeholder: null,
    shouldAnimatePlaceholder: true,
    snapshot: {
      isDraggingOver: false,
      draggingOverWith: null,
      draggingFromThisWith: null,
      isUsingPlaceholder: false
    },
    useClone: null
  };
  const idleWithoutAnimation = {
    ...idleWithAnimation,
    shouldAnimatePlaceholder: false
  };
  const getDraggableRubric = memoizeOne((descriptor) => ({
    draggableId: descriptor.id,
    type: descriptor.type,
    source: {
      index: descriptor.index,
      droppableId: descriptor.droppableId
    }
  }));
  const getMapProps = memoizeOne(
    (id, isEnabled, isDraggingOverForConsumer, isDraggingOverForImpact, dragging, renderClone) => {
      const draggableId = dragging.descriptor.id;
      const isHome = dragging.descriptor.droppableId === id;
      if (isHome) {
        const useClone = renderClone
          ? {
              render: renderClone,
              dragging: getDraggableRubric(dragging.descriptor)
            }
          : null;
        const snapshot = {
          isDraggingOver: isDraggingOverForConsumer,
          draggingOverWith: isDraggingOverForConsumer ? draggableId : null,
          draggingFromThisWith: draggableId,
          isUsingPlaceholder: true
        };
        return {
          placeholder: dragging.placeholder,
          shouldAnimatePlaceholder: false,
          snapshot,
          useClone
        };
      }
      if (!isEnabled) {
        return idleWithoutAnimation;
      }

      // not over foreign list - return idle
      if (!isDraggingOverForImpact) {
        return idleWithAnimation;
      }
      const snapshot = {
        isDraggingOver: isDraggingOverForConsumer,
        draggingOverWith: draggableId,
        draggingFromThisWith: null,
        isUsingPlaceholder: true
      };
      return {
        placeholder: dragging.placeholder,
        // Animating placeholder in foreign list
        shouldAnimatePlaceholder: true,
        snapshot,
        useClone: null
      };
    }
  );

  function getBody() {
    invariant(document.body, "document.body is not ready");
    return document.body;
  }

  return (
    state,
    {
      mode = "standard",
      type = "DEFAULT",
      direction = "vertical",
      isDropDisabled = false,
      isCombineEnabled = false,
      ignoreContainerClipping = false,
      renderClone,
      getContainerForClone = getBody,
      ...ownProps
    }
  ) => {
    // not checking if item is disabled as we need the home list to display a placeholder
    const id = ownProps.droppableId;
    const isEnabled = !isDropDisabled;
    if (state.isDragging) {
      const critical = state.critical;
      if (!isMatchingType(type, critical)) {
        return idleWithoutAnimation;
      }
      const dragging = getDraggable(critical, state.dimensions);
      const isDraggingOver = whatIsDraggedOver(state.impact) === id;
      return getMapProps(id, isEnabled, isDraggingOver, isDraggingOver, dragging, renderClone);
    }
    if (state.phase === "DROP_ANIMATING") {
      const completed = state.completed;
      if (!isMatchingType(type, completed.critical)) {
        return idleWithoutAnimation;
      }
      const dragging = getDraggable(completed.critical, state.dimensions);

      // Snapshot based on result and not impact
      // The result might be null (cancel) but the impact is populated
      // to move everything back
      return getMapProps(
        id,
        isEnabled,
        whatIsDraggedOverFromResult(completed.result) === id,
        whatIsDraggedOver(completed.impact) === id,
        dragging,
        renderClone
      );
    }
    if (state.phase === "IDLE" && state.completed && !state.shouldFlush) {
      const completed = state.completed;
      if (!isMatchingType(type, completed.critical)) {
        return idleWithoutAnimation;
      }

      // Looking at impact as this controls the placeholder
      const wasOver = whatIsDraggedOver(completed.impact) === id;
      const wasCombining = Boolean(completed.impact.at && completed.impact.at.type === "COMBINE");
      const isHome = completed.critical.droppable.id === id;
      if (wasOver) {
        // if reordering we need to cut an animation immediately
        // if merging: animate placeholder closed after drop
        return wasCombining ? idleWithAnimation : idleWithoutAnimation;
      }

      // we need to animate the home placeholder closed if it is not
      // being dropped into
      if (isHome) {
        return idleWithAnimation;
      }
      return idleWithoutAnimation;
    }

    // default: including when flushed
    return idleWithoutAnimation;
  };
};
const mapDispatchToProps = {
  updateViewportMaxScroll: updateViewportMaxScrollAction
};

// Abstract class allows to specify props and defaults to component.
// All other ways give any or do not let add default props.
/*::
class DroppableType extends Component<OwnProps> {
  static defaultProps = defaultProps;
}
*/

// Leaning heavily on the default shallow equality checking
// that `connect` provides.
// It avoids needing to do it own within `Droppable`
const ConnectedDroppable = connect(
  // returning a function so each component can do its own memoization
  makeMapStateToProps,
  // no dispatch props for droppable
  mapDispatchToProps,
  // mergeProps - using default
  null,
  // $FlowFixMe: current react-redux type does not know about context property
  {
    // Ensuring our context does not clash with consumers
    context: StoreContext,
    // When pure, compares the result of mapStateToProps to its previous value.
    // Default value: shallowEqual
    // Switching to a strictEqual as we return a memoized object on changes
    areStatePropsEqual: isStrictEqual
  }
)(Droppable);

export default ConnectedDroppable;
