<template>
  <div :class="_class">
    <TreeNode
      v-for="(item, index) in stateTree"
      :key="index"
      :data="item"
      visible
      :selectable="selectable"
      :children-key="childrenKey"
      :nodeKey="nodeKey"
    >
    </TreeNode>
    <div
      :class="_emptyClass"
      v-if="!stateTree.length">{{ emptyText }}
    </div>
  </div>
</template>

<script>
  import Emitter from 'utils-lib/dist/emitter';
  import TreeNode from './tree-node.vue';

  const name = 'hive-tree';

  export default {
    name,
    components: {
      TreeNode,
    },
    mixins: [
      Emitter,
    ],
    provide() {
      return {
        tree: this,
      };
    },
    props: {
      /**
       * 数据
       */
      data: {
        type: Array,
        default() {
          return [];
        },
      },
      /**
       * 数据子数据的key
       */
      childrenKey: {
        type: String,
        default: 'children',
      },
      /**
       * 是否可选
       */
      selectable: Boolean,
      /**
       * 数据为空提示
       */
      emptyText: {
        type: String,
        default: '暂无数据',
      },
      /**
       * 异步加载数据函数
       */
      onLoad: Function,
    },
    data() {
      return {
        name: name,
        stateTree: this.data,
        flatState: [],
        nodeKey: null,
      };
    },
    computed: {
      _class() {
        return [
          name,
        ];
      },
      _emptyClass() {
        return [
          `${name}-empty`,
        ];
      },
    },
    watch: {
      data: {
        deep: true,
        handler() {
          this.stateTree = this.data;
          this.updateTree();
        },
      },
    },
    created() {
      this.updateTree();
    },
    mounted() {
      this.$on('check', this.onCheck);
      this.$on('expandtoggle', this.onToggles);
    },
    methods: {
      updateTree() {
        this.flatState = this.compileFlatState();
        this.rebuildTree();
      },
      compileFlatState() { // so we have always a relation parent/children of each node
        let keyCounter = 0;
        let childrenKey = this.childrenKey;
        const flatTree = [];

        function flattenChildren(node, parent) {
          node.nodeKey = keyCounter++;
          flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
          if (parent !== undefined) {
            flatTree[node.nodeKey].parent = parent.nodeKey;
            flatTree[parent.nodeKey][childrenKey].push(node.nodeKey);
          }

          if (node[childrenKey]) {
            flatTree[node.nodeKey][childrenKey] = [];
            node[childrenKey].forEach(child => flattenChildren(child, node));
          }
        }

        this.stateTree.forEach(rootNode => {
          flattenChildren(rootNode);
        });
        return flatTree;
      },
      updateTreeUp(nodeKey) {
        const parentKey = this.flatState[nodeKey].parent;
        if (parentKey === undefined) return;

        const node = this.flatState[nodeKey].node;
        const parent = this.flatState[parentKey].node;
        if (node.checked === parent.checked && node.indeterminate === parent.indeterminate) return; // no need to update upwards

        if (node.checked === true) {
          this.$set(parent, 'checked', parent[this.childrenKey].every(node => node.checked));
          this.$set(parent, 'indeterminate', !parent.checked);
        } else {
          this.$set(parent, 'checked', false);
          this.$set(parent, 'indeterminate', parent[this.childrenKey].some(node => node.checked || node.indeterminate));
        }
        this.updateTreeUp(parentKey);
      },
      rebuildTree() { // only called when `data` prop changes
        const checkedNodes = this.getCheckedNodes();
        checkedNodes.forEach(node => {
          this.updateTreeDown(node, { checked: true });
          // propagate upwards
          const parentKey = this.flatState[node.nodeKey].parent;
          if (!parentKey && parentKey !== 0) return;
          const parent = this.flatState[parentKey].node;
          const childHasCheckSetter = node.checked !== undefined && node.checked;
          if (childHasCheckSetter && parent.checked !== node.checked) {
            this.updateTreeUp(node.nodeKey); // update tree upwards
          }
        });
      },

      getSelectedNodes() {
        return this.flatState.filter(obj => obj.node.selected).map(obj => obj.node);
      },
      getCheckedNodes() {
        return this.flatState.filter(obj => obj.node.checked).map(obj => obj.node);
      },
      updateTreeDown(node, changes = {}) {
        for (let key in changes) {
          this.$set(node, key, changes[key]);
        }
        if (node[this.childrenKey]) {
          node[this.childrenKey].forEach(child => {
            this.updateTreeDown(child, changes);
          });
        }
      },
      onToggles (e) {
        this.nodeKey = e.nodeKey;
        /**
         * 节点点击事件
         */
        this.$emit('onNodeClick', e);
        if (!this.selectable) {
          /**
           * 节点展开收缩事件
           */
          this.$emit('onExpand', e);
        }
      },
      onCheck({ checked, nodeKey }) {
        this.nodeKey = nodeKey;
        const node = this.flatState[nodeKey].node;

        this.$set(node, 'checked', checked);
        this.$set(node, 'indeterminate', false);

        this.updateTreeUp(nodeKey); // propagate up
        this.updateTreeDown(node, { checked, indeterminate: false }); // reset `indeterminate` when going down

        /**
         * 节点复选状态改变事件
         */
        this.$emit('onChange', this.getCheckedNodes(), node);
      },
    },
  };
</script>
