import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
import { isFunction } from 'lodash-es';
import { reactive, watch, nextTick } from 'vue';

function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const UPDATE_MODEL_EVENT = 'update:modelValue';
const DRAG_START_EVENT = 'dragstart';
const DRAG_END_EVENT = 'dragend';
let dragSourceCxt;
let sourceBackup;
function pushAt() {
  let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  let value = arguments.length > 1 ? arguments[1] : undefined;
  let index = arguments.length > 2 ? arguments[2] : undefined;
  if (index < 0) {
    return array.unshift(value);
  }
  if (index >= array.length) {
    return array.push(value);
  }
  const evens = array.splice(index, array.length - index);
  array.push(value, ...evens);
}

/**
 * 数组移动
 */
function arrayMove() {
  let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
  let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : -1;
  let value = arguments.length > 3 ? arguments[3] : undefined;
  if (source < 0) {
    // add target
    pushAt(array, value, target);
    return [value];
  }
  if (target < 0) {
    // remove source
    return array.splice(source, 1);
  }
  const evens = array.splice(source, 1);
  pushAt(array, evens[0], target);
  return evens;
}

// 根据目标元素
function findElement(target, parent) {
  if (!parent || !target) {
    return;
  }
  for (let index = 0; index < parent.children.length; index++) {
    const el = parent.children[index];
    if (el.contains(target)) {
      return {
        el: el,
        index
      };
    }
  }
}
class DraggableItem {
  constructor() {
    _defineProperty(this, "draggable", null);
    _defineProperty(this, "first", {
      x: 0,
      y: 0
    });
    _defineProperty(this, "last", {
      x: 0,
      y: 0
    });
    _defineProperty(this, "style", {
      transition: '',
      transform: '',
      opacity: null
    });
    _defineProperty(this, "elStyle", {});
  }
  // 静态样式

  setDraggable() {
    let draggable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    this.draggable = draggable || null;
    if (!draggable) {
      this.style.opacity = null;
    }
  }
  setOpacity() {
    let opacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.4;
    this.style.opacity = opacity;
  }
}
const useDraggable = (containerRef, propsRef, ctx) => {
  const draggableItems = reactive([]);
  const current = {};
  const backup = {};
  const emit = (ctx === null || ctx === void 0 ? void 0 : ctx.emit) || (() => null);
  let mousedownEvent;
  const nextTickQueue = [];
  const newNextTick = fn => {
    if (propsRef.value.isDirective) {
      isFunction(fn) && nextTickQueue.push(fn);
    } else {
      nextTick(fn);
    }
  };
  const onUpdated = () => {
    while (nextTickQueue.length) {
      nextTickQueue.shift()();
    }
  };
  const FLIP = isFirst => {
    if (!containerRef.value) {
      return;
    }
    for (let index = 0; index < containerRef.value.children.length; index++) {
      const node = containerRef.value.children[index];
      if (!draggableItems[index]) {
        draggableItems[index] = new DraggableItem();
        const elStyle = {};
        for (let index = 0; index < node.style.length; index++) {
          const key = node.style[index];
          elStyle[key] = node.style[key];
        }
        draggableItems[index].elStyle = elStyle;
      }
      const item = draggableItems[index];
      const rect = node.getBoundingClientRect();
      if (isFirst) {
        // First
        item.first.x = rect.left;
        item.first.y = rect.top;
      } else {
        // Last
        item.last.x = rect.left;
        item.last.y = rect.top;
        // Invert
        item.style.transform = `translate3d(${item.first.x - item.last.x}px, ${item.first.y - item.last.y}px , 0)`;
        item.style.transition = '';
      }
    }
    if (isFirst) {
      return;
    }
    requestAnimationFrame(() => {
      // Play
      draggableItems.forEach(item => {
        item.style.transition = 'transform 200ms ease';
        item.style.transform = '';
      });
    });
  };

  // 从backup中恢复状态
  const revertStatus = () => {
    FLIP(true);
    backup.list.forEach((item, index) => {
      var _backup$draggableItem;
      draggableItems[index] = (_backup$draggableItem = backup.draggableItems) === null || _backup$draggableItem === void 0 ? void 0 : _backup$draggableItem[index];
    });
    emit(UPDATE_MODEL_EVENT, backup.list);
    newNextTick(() => {
      FLIP(false);
    });
  };
  const computeDropTarget = event => {
    const target = findElement(event.target, containerRef.value);
    if (!target) {
      if (event.target === containerRef.value) {
        // 放最后
        return {
          el: null,
          index: propsRef.value.list.length
        };
      }
      return null;
    }
    return target;
  };
  const resetDragWhenEnd = event => {
    var _current$drag, _sourceBackup;
    const index = (_current$drag = current.drag) === null || _current$drag === void 0 ? void 0 : _current$drag.index;
    if (event && index >= 0) {
      emit(DRAG_END_EVENT, event, propsRef.value.list[index], index);
    }
    backup.list = null;
    if ((_sourceBackup = sourceBackup) !== null && _sourceBackup !== void 0 && _sourceBackup.list) {
      sourceBackup.list = null;
    }
    backup.draggableItems = [];
    backup.index = -1;
    current.drag = null;
    current.animationEnd = true;
    current.isDropOverItem = false;
    draggableItems.forEach(item => {
      item.setDraggable(false);
      item.style.transition = null;
    });
  };
  const shareSource = () => {
    dragSourceCxt = {
      propsRef,
      current,
      draggableItems,
      FLIP,
      emit,
      resetDragWhenEnd,
      newNextTick
    };
  };
  const backupStatus = () => {
    var _current$drag2;
    backup.list = [...propsRef.value.list];
    backup.draggableItems = [...draggableItems];
    backup.index = (_current$drag2 = current.drag) === null || _current$drag2 === void 0 ? void 0 : _current$drag2.index;
    backup.revertStatus = revertStatus;
    backup.resetDragWhenEnd = resetDragWhenEnd;
  };
  const onAnimationEnd = () => {
    current.animationEnd = true;
    current.isDropOverItem = false;
  };

  /** 拖拽开始 */
  const onDragstart = event => {
    mousedownEvent = event;
    const {
      disabled,
      droppable,
      list
    } = propsRef.value;
    if (disabled) {
      return;
    }
    current.drag = findElement(event.target, containerRef.value);
    if (!current.drag) {
      return;
    }
    const index = current.drag.index;
    const item = draggableItems[index];
    onAnimationEnd(); // 动画结束
    item.setDraggable(true); // 设置选中元素为可拖拽
    backupStatus();
    if (droppable) {
      shareSource(); // 跨容器，当前即是源，分享其他方法给目标容器
      sourceBackup = _objectSpread({}, backup);
    }
    emit(DRAG_START_EVENT, event, list[index], index);
  };
  const onMousemove = event => {
    var _current$drag3;
    if (!mousedownEvent) {
      return;
    }
    const item = draggableItems[current === null || current === void 0 || (_current$drag3 = current.drag) === null || _current$drag3 === void 0 ? void 0 : _current$drag3.index];
    if (item && (Math.abs(event.x - mousedownEvent.x) || Math.abs(event.y - mousedownEvent.y))) {
      item.setOpacity();
    }
  };
  const onDragover = event => {
    event.preventDefault();
    const {
      droppable,
      list
    } = propsRef.value;
    const {
      animationEnd,
      isDropOverItem,
      drag
    } = current;
    const s = dragSourceCxt;

    // 如果动画没结束，就直接结束
    if (!animationEnd || s && !s.current.animationEnd) {
      return;
    }
    // 目标位置
    const drop = computeDropTarget(event);
    if (!drop) {
      return;
    }
    let listEvens; // 差值
    let draggableItemEvens; // 差值
    let dragIndex = -1;
    if (droppable && !s) {
      return;
    } // 跨容器，不存在source
    if (droppable && s && !containerRef.value.contains(s.current.drag.el)) {
      // 从source容器拖拽到当前容器，source容器移除
      s.FLIP(true);
      current.isDropOverItem = !!drop.el;
      const sDragIndex = s.current.drag.index;
      if (!backup.list) {
        // 跨框时，记录当前首次状态
        backupStatus();
        backup.index = sDragIndex;
      }
      listEvens = arrayMove(s.propsRef.value.list, sDragIndex)[0];
      draggableItemEvens = arrayMove(s.draggableItems, sDragIndex)[0];
      s.current.animationEnd = false; // 动画开始
      s.current.drag = null;
      s.emit(UPDATE_MODEL_EVENT, s.propsRef.value.list);
    } else {
      // 拖拽目标在当前容器上
      dragIndex = drag.index;
      if (drop.index < 0) {
        drop.index = 0;
      }
      if (drop.index >= list.length) {
        drop.index = list.length - 1;
      }
      if (dragIndex === drop.index || isDropOverItem) {
        return;
      }
    }
    // 更新当前容器数据
    FLIP(true);
    arrayMove(list, dragIndex, drop.index, listEvens);
    arrayMove(draggableItems, dragIndex, drop.index, draggableItemEvens);
    emit(UPDATE_MODEL_EVENT, list);
    current.animationEnd = false; // 动画开始
    if (droppable && s) {
      s.newNextTick(() => {
        s.FLIP(false);
        shareSource();
      });
    }
    newNextTick(() => {
      current.drag = {
        index: drop.index === -1 ? 0 : drop.index,
        el: null
      };
      current.drag.el = containerRef.value.children[drop.index];
      FLIP(false);
    });
  };
  const checkDragEnd = async () => {
    var _current$drag4;
    const {
      beforeDragend,
      list
    } = propsRef.value;
    const index = current === null || current === void 0 || (_current$drag4 = current.drag) === null || _current$drag4 === void 0 ? void 0 : _current$drag4.index;
    if (isFunction(beforeDragend) && index >= 0) {
      var _sourceBackup2;
      // 校验是否需要可以放置
      let isTrue = false;
      let drag = {
        list: backup.list,
        index: backup.index,
        item: backup.list[backup.index],
        resultList: list // 预期结果
      };
      const drop = _objectSpread(_objectSpread({}, drag), {}, {
        index
      });
      if ((_sourceBackup2 = sourceBackup) !== null && _sourceBackup2 !== void 0 && _sourceBackup2.list && backup.list !== sourceBackup.list) {
        const resultList = [...sourceBackup.list];
        arrayMove(resultList, sourceBackup.index);
        drag = {
          list: sourceBackup.list,
          index: sourceBackup.index,
          item: sourceBackup.list[sourceBackup.index],
          resultList
        };
        drop.item = drag.item;
      }
      try {
        isTrue = await beforeDragend(drag, drop);
      } catch (error) {
        console.error(error);
        isTrue = false;
      }
      if (!isTrue) {
        var _sourceBackup3;
        revertStatus();
        (_sourceBackup3 = sourceBackup) === null || _sourceBackup3 === void 0 || _sourceBackup3.revertStatus();
      }
    }
  };
  const onDragend = async event => {
    mousedownEvent = null;
    const {
      droppable
    } = propsRef.value;
    await checkDragEnd();
    if (droppable && dragSourceCxt) {
      var _sourceBackup4;
      (_sourceBackup4 = sourceBackup) === null || _sourceBackup4 === void 0 || _sourceBackup4.resetDragWhenEnd(event);
      dragSourceCxt = null;
    }
    resetDragWhenEnd(event);
  };
  resetDragWhenEnd();
  watch(containerRef, () => FLIP(true), {
    immediate: true
  });
  watch(propsRef, () => {
    if (draggableItems.length !== propsRef.value.list.length) {
      if (propsRef.value.isDirective) {
        FLIP(true); // 指令的list更新在updated之后
      } else {
        nextTick(() => FLIP(true));
      }
    }
  }, {
    immediate: true,
    deep: true
  });
  return {
    onAnimationEnd,
    onDragover,
    onDragstart,
    onDragend,
    draggableItems,
    nextTickQueue,
    onMousemove,
    onUpdated
  };
};

export { DRAG_END_EVENT, DRAG_START_EVENT, DraggableItem, UPDATE_MODEL_EVENT, useDraggable };
