import { invariant } from "../../invariant";
import createPublisher from "./while-dragging-publisher";
import getInitialPublish from "./get-initial-publish";
import { warning } from "../../dev-warning";

function shouldPublishUpdate(registry, dragging, entry) {
  // do not publish updates for the critical draggable
  if (entry.descriptor.id === dragging.id) {
    return false;
  }
  // do not publish updates for draggables that are not of a type that we care about
  if (entry.descriptor.type !== dragging.type) {
    return false;
  }
  const home = registry.droppable.getById(entry.descriptor.droppableId);
  if (home.descriptor.mode !== "virtual") {
    warning(`
      You are attempting to add or remove a Draggable [id: ${entry.descriptor.id}]
      while a drag is occurring. This is only supported for virtual lists.

      See https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/virtual-lists.md
    `);
    return false;
  }
  return true;
}

const dimensionMarashal = (registry, callbacks) => {
  let collection = null;
  const publisher = createPublisher({
    callbacks: {
      publish: callbacks.publishWhileDragging,
      collectionStarting: callbacks.collectionStarting
    },
    registry
  });
  const updateDroppableIsEnabled = (id, isEnabled) => {
    invariant(
      registry.droppable.exists(id),
      `Cannot update is enabled flag of Droppable ${id} as it is not registered`
    );

    // no need to update the application state if a collection is not occurring
    if (!collection) {
      return;
    }

    // At this point a non primary droppable dimension might not yet be published
    // but may have its enabled state changed. For now we still publish this change
    // and let the reducer exit early if it cannot find the dimension in the state.
    callbacks.updateDroppableIsEnabled({
      id,
      isEnabled
    });
  };
  const updateDroppableIsCombineEnabled = (id, isCombineEnabled) => {
    // no need to update
    if (!collection) {
      return;
    }
    invariant(
      registry.droppable.exists(id),
      `Cannot update isCombineEnabled flag of Droppable ${id} as it is not registered`
    );
    callbacks.updateDroppableIsCombineEnabled({
      id,
      isCombineEnabled
    });
  };
  const updateDroppableScroll = (id, newScroll) => {
    // no need to update the application state if a collection is not occurring
    if (!collection) {
      return;
    }
    invariant(registry.droppable.exists(id), `Cannot update the scroll on Droppable ${id} as it is not registered`);
    callbacks.updateDroppableScroll({
      id,
      newScroll
    });
  };
  const scrollDroppable = (id, change) => {
    if (!collection) {
      return;
    }
    registry.droppable.getById(id).callbacks.scroll(change);
  };
  const stopPublishing = () => {
    // This function can be called defensively
    if (!collection) {
      return;
    }
    // Stop any pending dom collections or publish
    publisher.stop();

    // Tell all droppables to stop watching scroll
    // all good if they where not already listening
    const home = collection.critical.droppable;
    registry.droppable.getAllByType(home.type).forEach((entry) => entry.callbacks.dragStopped());

    // Unsubscribe from registry updates
    collection.unsubscribe();
    // Finally - clear our collection
    collection = null;
  };
  const subscriber = (event) => {
    invariant(collection, "Should only be subscribed when a collection is occurring");
    // The dragging item can be add and removed when using a clone
    // We do not publish updates for the critical item
    const dragging = collection.critical.draggable;
    if (event.type === "ADDITION") {
      if (shouldPublishUpdate(registry, dragging, event.value)) {
        publisher.add(event.value);
      }
    }
    if (event.type === "REMOVAL") {
      if (shouldPublishUpdate(registry, dragging, event.value)) {
        publisher.remove(event.value);
      }
    }
  };
  const startPublishing = (request) => {
    invariant(!collection, "Cannot start capturing critical dimensions as there is already a collection");
    const entry = registry.draggable.getById(request.draggableId);
    const home = registry.droppable.getById(entry.descriptor.droppableId);
    const critical = {
      draggable: entry.descriptor,
      droppable: home.descriptor
    };
    const unsubscribe = registry.subscribe(subscriber);
    collection = {
      critical,
      unsubscribe
    };
    return getInitialPublish({
      critical,
      registry,
      scrollOptions: request.scrollOptions
    });
  };
  const marshal = {
    // Droppable changes
    updateDroppableIsEnabled,
    updateDroppableIsCombineEnabled,
    scrollDroppable,
    updateDroppableScroll,
    // Entry
    startPublishing,
    stopPublishing
  };
  return marshal;
};

export default dimensionMarashal;
