import * as timings from "../../debug/timings";
import { origin } from "../position";

const clean = () => ({
  additions: {},
  removals: {},
  modified: {}
});
const timingKey = "Publish collection from DOM";
export default function createPublisher({ registry, callbacks }) {
  let staging = clean();
  let frameId = null;
  const collect = () => {
    if (frameId) {
      return;
    }
    callbacks.collectionStarting();
    frameId = requestAnimationFrame(() => {
      frameId = null;
      timings.start(timingKey);
      const { additions, removals, modified } = staging;
      const added = Object.keys(additions)
        .map(
          // Using the origin as the window scroll. This will be adjusted when processing the published values
          (id) => registry.draggable.getById(id).getDimension(origin)
        )
        // Dimensions are not guarenteed to be ordered in the same order as keys
        // So we need to sort them so they are in the correct order
        .sort((a, b) => a.descriptor.index - b.descriptor.index);
      const updated = Object.keys(modified).map((id) => {
        const entry = registry.droppable.getById(id);
        const scroll = entry.callbacks.getScrollWhileDragging();
        return {
          droppableId: id,
          scroll
        };
      });
      const result = {
        additions: added,
        removals: Object.keys(removals),
        modified: updated
      };
      staging = clean();
      timings.finish(timingKey);
      callbacks.publish(result);
    });
  };
  const add = (entry) => {
    const id = entry.descriptor.id;
    staging.additions[id] = entry;
    staging.modified[entry.descriptor.droppableId] = true;
    if (staging.removals[id]) {
      delete staging.removals[id];
    }
    collect();
  };
  const remove = (entry) => {
    const descriptor = entry.descriptor;
    staging.removals[descriptor.id] = true;
    staging.modified[descriptor.droppableId] = true;
    if (staging.additions[descriptor.id]) {
      delete staging.additions[descriptor.id];
    }
    collect();
  };
  const stop = () => {
    if (!frameId) {
      return;
    }
    cancelAnimationFrame(frameId);
    frameId = null;
    staging = clean();
  };
  return {
    add,
    remove,
    stop
  };
}
