import Vue from 'vue';
import { Draggable } from '@shopify/draggable';
import DraggableEvent from './event'


const SORT_TAG = "sort";
function someFunc({ source, over, overContainer, children }) {
  const isDelete = !children.length
    , isInContainer = source.parentNode !== overContainer
    , Q = over && !isInContainer;
  if (isDelete) {
    return moveToContainer(source, overContainer)
  }
  return Q ? $(source, over) : isInContainer ? insertBefore(source, over, overContainer) : null
}

function moveToContainer(el, newContainer) {
  const oldContainer = el.parentNode;
  newContainer.appendChild(el);
  return {
    oldContainer,
    newContainer
  };
}
function $(el, container) {
  console.info('$')
  const tt = indexOf(el)
    , ot = indexOf(container);
  if (tt < ot) {
    el.parentNode.insertBefore(el, container.nextElementSibling);
  } else {
    el.parentNode.insertBefore(el, container);
  }
  return {
    oldContainer: el.parentNode,
    newContainer: el.parentNode
  };
}
function indexOf(A) {
  return Array.prototype.indexOf.call(A.parentNode.children, A)
}
function insertBefore(A, H, tt) {
  console.info('insertBefore')
  const ot = A.parentNode;
  return H ? H.parentNode.insertBefore(A, H) : tt.appendChild(A),
    {
      oldContainer: ot,
      newContainer: A.parentNode
    }
}
function c(A, H, tt) {
  const ot = [...A.slice(0, H), ...A.slice(H + 1, A.length)];
  return [...ot.slice(0, tt), A[H], ...ot.slice(tt, ot.length)]
}

export
const SortableShopify = {
  name: 'Sortable',
  render: function() {
    return this._self._c(this.tag, {
      ref: "sortable",
      tag: "component"
    }, [this._t("default", null, {
      items: this.value
    })], 2)
  },
  props: {
    value: {
      default: ()=>[],
      type: Array
    },
    group: {
      type: String,
      default: "defaultGroup"
    },
    tag: {
      type: String,
      default: "div"
    }
  },
  mounted() {
    Draggable.addContainer(this.$refs.sortable);
    this.$refs.sortable.dataset.group = this.group;
    this.handler = event=>{
      const H = [...this.value];
      H.splice(event.detail.newIndex, 0, event.detail.item),
        this.$emit("input", H),
        this.$emit("receive", {
          ...event.detail,
          newItems: H
        })
    };

    this.$refs.sortable.addEventListener(SORT_TAG, this.handler);
    Draggable.on("drag:start", this.onDragStart);
  },
  beforeDestroy() {
    this.$refs.sortable.removeEventListener(SORT_TAG, this.handler)
  },
  destroyed() {
    Draggable.removeContainer(this.$refs.sortable),
      Draggable.off("drag:start", this.onDragStart),
      this.destroyMirror()
  },
  methods: {
    stopListening() {
      Draggable
        .off("drag:move", this.dragMove)
        .off("drag:over:container", this.onDragOverContainer)
        .off("drag:out:container", this.onDragOutContainer)
        .off("drag:over", this.onDragOver)
        .off("drag:stop", this.onDragStop)
        .off("mirror:created", this.createMirror)
        .off("mirror:destroy", this.destroyMirror);
      this.destroyMirror();
    },
    createMirror(el) {
      console.info(el, this.dragging)
      if (this.dragging && this.$scopedSlots.mirror) {
        const H = this.$scopedSlots.mirror({
          item: this.source.item
        });
        const tt = Vue.ZP.extend({
          parent: this,
          render() {
            return H
          }
        });
        const Y = new tt().$mount();
        this.mirrorComponent = Y;
        el.mirror.innerHTML = "";
        el.mirror.appendChild(Y.$el);
      }
    },
    destroyMirror() {
      if (this.mirrorComponent) {
        this.mirrorComponent.$destroy();
        this.mirrorComponent = null;
      }
    },
    getDraggableElementsForContainer(el) {
      return [...el.children].filter(item => item !== Draggable.originalSource && item !== Draggable.mirror)
    },
    index(el) {
      return this.getDraggableElementsForContainer(el.parentNode).indexOf(el)
    },
    onDragStart(event) {
      if (event.originalEvent.target.tagName === "INPUT") {
        event.cancel();
        return
      }
      if (event.sourceContainer !== this.$refs.sortable) {
        return;
      }
      Draggable
        .on("drag:move", this.dragMove)
        .on("drag:over:container", this.onDragOverContainer)
        .on("drag:out:container", this.onDragOutContainer)
        .on("drag:over", this.onDragOver)
        .on("drag:stop", this.onDragStop)
        .on("mirror:created", this.createMirror)
        .on("mirror:destroy", this.destroyMirror);
      const oldIndex = this.index(event.source);
      this.$emit("start");
      this.oldIndex = oldIndex;
      this.source = {
        oldIndex,
        item: this.value[oldIndex]
      };
    },
    dragMove(el) {
      Draggable.trigger(new DraggableEvent({
        ...el.data,
        sourceComponent: this.$refs.sortable,
        group: this.group,
        draggablePayload: {
          item: this.value[this.oldIndex],
          ...el.originalSource.sortableItemPayload
        }
      }))
    },
    onDragOverContainer(event) {
      const {source, over, overContainer} = event;
      if (overContainer.dataset.group !== this.group) {
        return;
      }
      const children = this.getDraggableElementsForContainer(overContainer);
      this.destinationContainer = overContainer;

      console.info('onDragOverContainer', overContainer, children)
      someFunc({
        source,
        over,
        overContainer,
        children
      });
    },
    onDragOutContainer(el) {
      this.destinationContainer = null;
      const overContainer = this.$refs.sortable
        , children = this.getDraggableElementsForContainer(overContainer)
        , over = children[this.source.oldIndex];
      someFunc({
        source: el.source,
        over,
        overContainer,
        children
      })
    },
    onDragOver(A) {
      const { source, over, overContainer } = A;
      if (overContainer.dataset.group !== this.group || over === A.originalSource || over === source) {
        return;
      }
      const Y = this.getDraggableElementsForContainer(overContainer);
      if (Y.includes(over)) {
        someFunc({
          source: source,
          over: over,
          overContainer: overContainer,
          children: Y
        });
      }
    },
    onDragStop(item) {
      this.stopListening();
      if (!this.destinationContainer || this.destinationContainer.dataset.group !== this.group) {
        return;
      }
      const { source } = this;
      source.newIndex = this.index(item.source);
      source.sortableItemPayload = { ...item.originalSource.sortableItemPayload };
      console.info(item.source, item.originalSource)
      item.originalSource.parentNode.insertBefore(item.source, item.originalSource);
      if (item.sourceContainer === this.destinationContainer) {
        this.onSortItems(source);
      } else {
        this.onRemoveItem(source);
        this.onReceiveItem(source, this.destinationContainer);
      }
      this.$emit("stop");
    },
    onSortItems(item) {
      if (item.oldIndex === item.newIndex) {
        return;
      }
      const newItems = c(this.value, item.oldIndex, item.newIndex);
      this.$emit("input", newItems);
      this.$emit("move", {
        newItems,
        ...item
      });
    },
    onReceiveItem(el, H) {
      H.dispatchEvent(new CustomEvent(SORT_TAG,{
        detail: {
          item: el.item,
          newIndex: el.newIndex,
          oldIndex: el.oldIndex,
          sortableItemPayload: el.sortableItemPayload
        }
      }))
    },
    onRemoveItem(el) {
      const H = this.value.filter(tt=>tt !== el.item);
      this.$emit("input", H)
    }
  }
};

export default {
  Sortable: SortableShopify,
  SortableItem: {
    inserted(el, { value }) {
      el.sortableItemPayload = value?.payload;
      el.classList.add(draggableNode.options.draggableClass);
      el.getAttribute("with-handle") && el.classList.add(draggableNode.options.dragHandleClass);
      value?.mirror && (el.dataset.draggableMirror = JSON.stringify(value.mirror));
    },
    update(el, {value}) {
      el.sortableItemPayload = value?.payload;
      el.classList.add(draggableNode.options.draggableClass);
      el.getAttribute("with-handle") && el.classList.add(draggableNode.options.dragHandleClass);
      value?.mirror && (el.dataset.draggableMirror = JSON.stringify(value.mirror))
    }
  }
};
