<template>
  <div
    :style="{
      '--itf-tree-editor-disabled-font-size': `${textDisabledFontSize}px`,
      '--itf-tree-editor-disabled-padding-left': `${textDisabledPaddingLeft}px`,
      '--itf-tree-editor-disabled-background-color': $options.filters.color2hex(textDisabledBackgroundColor),
      '--itf-tree-editor-disabled-color': $options.filters.color2hex(textDisabledColor),
      '--itf-tree-editor-join-block-color': $options.filters.color2hex(joinBlockColor),
      '--itf-tree-editor-shift-block-color': $options.filters.color2hex(shiftBlockColor),
      '--itf-tree-editor-shift-line-color': $options.filters.color2hex(shiftLineColor)
    }"
  >
    <div style="position:relative; user-select:none;">
      <template v-for="(node,knode) in nodes">
        <div
          :key="get(node,itemKey,knode)"
          class="itf-tree-editor_node"
          :class="{ 'hidden': isNodeHidden(node) }"
          :style="`transition:all 0.2s linear; cursor:pointer; outline:none; padding-left:${(node.nk.length - toggleStartLevel - 1)*indent}px;`"
          :dragindex="knode"
          tabindex="0"
          dragtag
          @mouseenter="(e)=>{$emit('mouseenter',{event:e,ele:e.target,node,knode})}"
          @mouseleave="(e)=>{$emit('mouseleave',{event:e,ele:e.target,node,knode})}"
          @keyup.enter="(e)=>{$emit('click',{event:e,ele:e.target,node,knode})}"
          @click="(e)=>{$emit('click',{event:e,ele:e.target,node,knode})}"
        >
          <div v-if="node.level >= toggleStartLevel" class="itf-tree-editor_node_toggle">
            <itf-button
              v-if="autoToggleTree && node.level >= toggleStartLevel && node[itemChildren] && node[itemChildren].length > 0"
              small
              icon
              @click.stop="toggleNode($event, node, knode)"
            >
              <itf-icon v-if="isNodeToggled(node)" name="plus" size="24" />
              <itf-icon v-if="!isNodeToggled(node)" name="minus" size="24" />
            </itf-button>
          </div>
          <div class="itf-tree-editor_node_content" :data-node="node[itemKey]">
            <slot
              name="item"
              :isFirstElement="knode === 0"
              :isLastElement="knode === nodes.length - 1"
              :item="node"
              :node="node"
              :knode="knode"
            />
          </div>
        </div>
      </template>
      <div
        :style="{
          position: 'absolute',
          'z-index': 9999,
          left: `${dgTipLeft}px`,
          top: `${dgTipTop}px`,
          '--itf-tree-editor-tip-width': `${dgTipWidth}px`,
          '--itf-tree-editor-tip-height': `${dgTipHeight}px`,
        }"
      >
        <div v-if="dgTipMode==='lineTop'" class="itf-tree-editor_border-top" />
        <div v-else-if="dgTipMode==='lineBottom'" class="itf-tree-editor_border-bottom" />
        <div v-else-if="dgTipMode==='block'" class="itf-tree-editor_inside" />
        <div v-else-if="dgTipMode==='disabled'" class="itf-tree-editor_disabled">
          <div class="itf-tree-editor_disabled-text">
            <template v-if="isNestingLimited">
              {{ textDisabledNested }}
            </template>
            <template v-else>
              {{ textDisabled }}
            </template>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<style lang="scss">
:root {
  --itf-tree-hover-bg: rgba(0 0 0 / 5%);
}
[data-theme="dark"] {
  --itf-tree-hover-bg: rgba(255 255 255 / 5%);
}

.itf-tree-editor {
  &_inside:after,
  &_disabled:after,
  &_border-top:after,
  &_border-bottom:after {
    content: " ";
    display: block;
    position: absolute;
    top: -5px;
    left: -5px;
    border-left: 7px solid var(--bs-primary);
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-right: 7px solid transparent;
  }
  &_border-top {
    display:inline-block;
    width: var(--itf-tree-editor-tip-width);
    height: var(--itf-tree-editor-tip-height);
    /*background-color: var(--itf-tree-editor-shift-block-color);*/
    border-top: 3px solid var(--bs-primary);
  }
  &_border-bottom {
    display:inline-block;
    width: var(--itf-tree-editor-tip-width);
    height: var(--itf-tree-editor-tip-height);
    /*background-color: var(--itf-tree-editor-shift-block-color);*/
    border-bottom: 3px solid var(--bs-primary);

    &:after {
      top: auto;
      bottom: 1px
    }
  }
  &_inside {
    display:inline-block;
    border-radius: .5rem;
    border: 1px solid var(--bs-primary);
    width: var(--itf-tree-editor-tip-width);
    height: var(--itf-tree-editor-tip-height);
    background-color: var(--itf-tree-editor-shift-block-color);

    &:after {
      top: 50%;
      transform: translateY(-100%);
      left: -7px;
    }
  }
  &_node {
    display: flex;
    position: relative;
  }
  &_node.hidden {
    display: none;
  }
  &_node .selected,
  &_node:hover {
    background-color: var(--itf-tree-hover-bg);
  }
  &_node_toggle {
    margin-left: 0;
    width: 45px;
    display: flex;
    align-items: center;
  }
  &_node_content {
    flex-grow: 1;
    align-items: start;
  }
  &_disabled {
    display:inline-flex;
    width: var(--itf-tree-editor-tip-width);
    height: var(--itf-tree-editor-tip-height);
    background-color: var(--itf-tree-editor-disabled-background-color);
    color: var(--itf-tree-editor-disabled-color);
    line-height: var(--itf-tree-editor-tip-height);
    vertical-align: middle;

    &-text {
      font-size: var(--itf-tree-editor-disabled-font-size);
      padding-left: var(--itf-tree-editor-disabled-padding-left);
    }

    &:after {
      content: '';
      display: none;
    }
  }
}
</style>
<script>
import Vue from 'vue';
import pullAt from 'lodash/pullAt';
import size from 'lodash/size';
import dropRight from 'lodash/dropRight';
import takeRight from 'lodash/takeRight';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import set from 'lodash/set';
import min from 'lodash/min';
import take from 'lodash/take';
import isEqual from 'lodash/isEqual';
import domDrag from '../../helpers/tree/domDrag';
import cint from '../../helpers/tree/cint';
import itfButton from '../button/Button';
import itfIcon from '../icon/Icon';
import color2hex from '../../helpers/vuetifyColor';
import {buildTree, flattenTree} from '../../helpers/tree';

export default {
  components: {
    itfIcon,
    itfButton
  },
  filters: {
    color2hex
  },
  props: {
    items: {
      type: Array,
      default: () => []
    },
    selectedItem: {
      type: Object,
      default: () => null
    },
    itemKey: {
      type: String,
      default: 'Id'
    },
    itemChildren: {
      type: String,
      default: 'Children'
    },
    indent: {
      type: Number,
      default: 10
    },
    textDisabled: {
      type: String,
      default: 'Can not drop here'
    },
    textDisabledNested: {
      type: String,
      default: 'Tree has a limitation in nesting, can not drop here'
    },
    textDisabledColor: {
      type: String,
      default: '#812'
    },
    textDisabledPaddingLeft: {
      type: Number,
      default: 15
    },
    textDisabledFontSize: {
      type: String,
      default: '0.9rem'
    },
    textDisabledBackgroundColor: {
      type: String,
      default: 'rgba(255,220,240,0.6)'
    },
    shiftLineColor: {
      type: String,
      default: '#29f'
    },
    shiftBlockColor: {
      type: String,
      default: 'rgba(80,150,255,0.15)'
    },
    joinBlockColor: {
      type: String,
      default: 'rgba(80,150,255,0.3)'
    },
    maxNestingLevels: {
      type: Number,
      default: null
    },
    allowDrop: {
      type: Function,
      default: null
    },
    handle: {
      type: String,
      default: null
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    flat: {
      type: Boolean,
      default: false
    },
    autoToggleTree: {
      type: Boolean,
      default: false
    },
    toggleStartLevel: {
      type: Number,
      default: 1
    },
    parentKey: {
      type: String,
      default: 'ParentId'
    }
  },
  data () {
    return {
      get,
      dd: null,
      dgTipMode: '',
      dgTipLeft: 0,
      dgTipTop: 0,
      dgTipWidth: 0,
      dgTipHeight: 0,
      isNestingLimited: false,
      toggledItems: this.toggledItems || {},
      hiddenItems: this.hiddenItems || {}
    };
  },
  computed: {
    itemsTrans () {
      let items = cloneDeep(this.items);
      if (this.flat) {
        items = buildTree(items, this.itemKey, this.parentKey, this.itemChildren);
      }
      return items;
    },
    nodes () {
      let items = this.itemsTrans;
      const list = flattenTree(items, 4, 0, null, [], this.itemChildren).map((item) => {
        const [, ...nk] = item.path;
        return { ...item, nk };
      });
      return list;
    }
  },
  watch: {
    selectedItem () {
      if (this.selectedItem && this.firstInited && this.items.length) {
        const node = this.nodes.find(nd => nd[this.itemKey] === this.selectedItem[this.itemKey]);
        if (node) {
          this.firstInited = false;
          this.toggleParentHidden(node, false);
        }
      }
    },
    items () {
      const draggable = get(this, 'dd.draggable');
      if (draggable) {
        draggable.destroy();
      }
      this.$nextTick(() => this.init());
    }
  },
  mounted () {
    this.init();
  },
  beforeDestroy () {
    const draggable = get(this, 'dd.draggable');
    if (draggable) {
      draggable.destroy();
    }
  },
  methods: {
    init () {
      this.firstInited = true;
      // domDrag
      const dd = domDrag(this.$el, {
        attIndex: 'dragindex',
        attGroup: 'draggroup',
        selectors: '[dragtag]',
        handle: this.handle,
        previewBackground: 'rgba(255,255,255,.75)',
        previewBorderColor: color2hex('blue-grey')
      });
      // change
      dd.on('change', (msg) => {
        // console.log('change', msg)
      });
      dd.on('start', (msg) => {
        // console.log('start', msg)
      });
      dd.on('move', (msg) => {
        // console.info('move');
        // belong
        const nodeSelf = this.nodes[msg.startInd];
        const nodeEnter = this.nodes[msg.endInd];
        const belong = this.isNkBelong(nodeEnter.nk, nodeSelf.nk);
        // location
        this.dgTipTop = msg.endEle.offsetTop;
        this.dgTipLeft = msg.endEle.offsetLeft;
        this.dgTipWidth = msg.endEle.offsetWidth;
        this.dgTipHeight = msg.endEle.offsetHeight;
        // dgTipMode
        if (belong) {
          this.dgTipMode = 'disabled';
        } else if (msg.ry <= 0.3) {
          this.dgTipMode = 'lineTop';
        } else if (msg.ry >= 0.7) {
          this.dgTipMode = 'lineBottom';
        } else if (msg.ry > 0.3 && msg.ry < 0.7) {
          this.dgTipMode = 'block';
        } else {
          // 不會有此狀況
          this.dgTipMode = '';
        }

        this.isNestingLimited = false;
        // limit nesting level
        const level = Math.floor(nodeEnter.nk.length / 2);
        if (typeof this.maxNestingLevels !== 'undefined' && ((level > this.maxNestingLevels) || (level >= this.maxNestingLevels && this.dgTipMode === 'block'))) {
          this.dgTipMode = 'disabled';
          this.isNestingLimited = true;
        }
        if (!nodeEnter.isGroup && this.dgTipMode === 'block') {
          this.dgTipMode = 'disabled';
        }
        if (this.dgTipMode !== 'disabled' && this.allowDrop && !this.allowDrop(nodeSelf, nodeEnter, this.dgTipMode)) {
          this.dgTipMode = 'disabled';
        }
      });
      dd.on('enter', (msg) => {
        // console.log('enter', msg)
      });
      dd.on('leave', (msg) => {
        // console.log('leave', msg)
        this.dgTipMode = '';
      });
      dd.on('drop', (msg) => {
        // console.log('drop', msg)
        // check
        if (this.dgTipMode === '') {
          // console.log('純點擊時無move故無有效拖曳模式')
          return;
        }
        // modeDir
        let modeDir = '';
        if (cint(msg.startInd) < cint(msg.endInd)) {
          modeDir = 'backward';
        } else if (cint(msg.startInd) > cint(msg.endInd)) {
          modeDir = 'forward';
        }
        // check
        if (modeDir === '') {
          this.dgTipMode = '';
          return;
        }
        // modeInsert
        let modeInsert;
        if (this.dgTipMode === 'disabled') {
          this.dgTipMode = '';
          return;
        } else if (this.dgTipMode === 'lineTop') {
          modeInsert = 'before';
        } else if (this.dgTipMode === 'lineBottom') {
          modeInsert = 'after';
        } else if (this.dgTipMode === 'block') {
          modeInsert = 'belongto';
        } else {
          // console.log('invalid dgTipMode', this.dgTipMode)
        }
        // nkFrom
        const nodeFrom = this.nodes[msg.startInd];
        const nkFrom = cloneDeep(nodeFrom.nk);
        // nkTo
        const nodeTo = this.nodes[msg.endInd];
        const nkTo = cloneDeep(nodeTo.nk);

        // clear
        this.dgTipMode = '';

        let newItems = this.moveItem(this.itemsTrans, nkFrom, nkTo, modeDir, modeInsert);
        if (this.flat) {
          newItems = flattenTree(newItems, 4, 0, null, [], this.itemChildren).map(node => {
            node.parentId = node.parentNode ? node.parentNode[this.itemKey] : null;
            return node;
          });
        }
        this.$emit('update:items', newItems);
      });
      // save
      this.dd = dd;
    },
    isNodeHidden (node) {
      if (!this.autoToggleTree) {
        return false;
      }
      if (node.parentNode && this.toggledItems[node.parentNode[this.itemKey]] !== undefined) {
        return this.toggledItems[node.parentNode[this.itemKey]];
      }
      // if (this.hiddenItems[node.Id] !== undefined) {
      //   return this.hiddenItems[node.Id];
      // }
      return node.level > 1;
    },
    isNodeToggled (node) {
      if (this.toggledItems[node[this.itemKey]] !== undefined) {
        return this.toggledItems[node[this.itemKey]];
      }
      return node.level > 0;
    },
    toggleNode ($event, node, knode) {
      if (this.toggledItems[node[this.itemKey]] === undefined) {
        Vue.set(this.toggledItems, node[this.itemKey], true);
      }
      Vue.set(this.toggledItems, node[this.itemKey], !this.toggledItems[node[this.itemKey]]);
      if (this.toggledItems[node[this.itemKey]]) { // show only 1st level
        this.toggleChildrenHidden(node[this.itemKey], true);
      } else if (node.Children) {
        for (const subNode of node.Children) {
          this.$set(this.hiddenItems, subNode[this.itemKey], this.toggledItems[node[this.itemKey]]);
        }
      }
    },
    toggleChildrenHidden (id, hide) {
      const subNodes = this.nodes.filter(nd => nd[this.parentKey] === id);
      if (subNodes) {
        for (const subNode of subNodes) {
          this.$set(this.hiddenItems, subNode[this.itemKey], hide);
          this.$set(this.toggledItems, subNode[this.itemKey], hide);
          this.toggleChildrenHidden(subNode[this.itemKey], hide);
        }
      }
    },
    toggleParentHidden (node, hide) {
      this.$set(this.hiddenItems, node[this.itemKey], hide);
      this.$set(this.toggledItems, node[this.itemKey], hide);
      if (node.Children) {
        for (const subNode of node.Children) {
          this.$set(this.hiddenItems, subNode[this.itemKey], hide);
        }
      }
      if (node[this.parentKey]) {
        const parent = this.nodes.find(nd => nd[this.itemKey] === node[this.parentKey]);
        if (parent) {
          this.toggleParentHidden(parent, hide);
        }
      }
    },
    isNkBelong (arEnter, arSelf) {
      function isArrayOverlap (ar1, ar2) {
        // n
        const n = min([size(ar1), size(ar2)]);
        const tr1 = take(ar1, n);
        const tr2 = take(ar2, n);
        // isEqual
        return isEqual(tr1, tr2);
      }
      if (size(arEnter) >= size(arSelf)) {
        return isArrayOverlap(arEnter, arSelf);
      }
      return false;
    },
    deleteItem (items, nkFrom) {
      // console.log('methods deleteItem', items, nkFrom)
      // cloneDeep
      items = cloneDeep(items);
      let ks;
      let ind = null;
      // eslint-disable-next-line no-empty
      if (size(nkFrom) === 0) {
      } else if (size(nkFrom) === 1) {
        ks = nkFrom;
      } else {
        ks = dropRight(nkFrom);
        ind = takeRight(nkFrom);
      }
      if (!ind) {
        // pullAt
        pullAt(items, nkFrom);
      } else {
        const src = get(items, ks, []);
        // pullAt
        pullAt(src, [ind]);
      }
      return items;
    },
    moveItem (items, nkFrom, nkTo, modeDir, modeInsert) {
      // console.log('methods moveItem', items, nkFrom, nkTo, modeDir, modeInsert)
      // check
      if (modeDir !== 'forward' && modeDir !== 'backward') {
        // console.log('invalid modeDir', modeDir);
      }
      if (size(nkFrom) === 0) {
        // console.log('invalid nkFrom', nkTo);
      }
      if (size(nkTo) === 0) {
        // console.log('invalid nkTo', nkTo);
      }
      // cloneDeep
      items = cloneDeep(items);
      const src = get(items, nkFrom);
      // modeDir
      if (modeDir === 'forward') {
        items = this.deleteItem(items, nkFrom);
      }
      let ind = takeRight(nkTo)[0];
      if (modeInsert === 'before' || modeInsert === 'after') {
        let tar;
        if (size(nkTo) === 1) {
          tar = items;
        } else {
          const ks = dropRight(nkTo);
          tar = get(items, ks, []);
        }
        if (modeInsert === 'before') {
          // ind = ind
        } else if (modeInsert === 'after') {
          ind += 1;
        }
        tar.splice(ind, 0, src);
      } else if (modeInsert === 'belongto') {
        const tar = get(items, [...nkTo, this.itemChildren], []);
        // push
        tar.push(src);
        // set
        set(items, [...nkTo, this.itemChildren], tar);
      } else {
        // console.log('invalid modeInsert', modeInsert);
      }
      // modeDir
      if (modeDir === 'backward') {
        items = this.deleteItem(items, nkFrom);
      }
      return items;
    }
  }
};
</script>
