<template>
  <div class="el-virtual-tree">
    <!-- 虚拟滚动区域 -->
    <el-virtual-scroller ref='virtualScroller' v-if='filterData.length > 0' :data='filterData' :ceilSize='ceilSize'>
      <template slot-scope='scope'>
        <!-- <div>{{scope.row}}</div> -->
        <!-- 实际显示区域 -->
        <div :class="['el-virtual-tree-ceil', highlightCurrent ? 'el-virtual-tree--highlight-current' : '']">
          <div
              :class="['el-virtual-tree-ceil__content', scope.row.id === curNodeKey ? 'is-current' : '']"
              @click='nodeClick(scope.row, scope.$index)'
              :style="{ paddingLeft: scope.row.level * indent + 'px' }"
          >
              <!-- 展开/关闭状态图标显示区域 -->
              <i
                  @click.stop='toggleNodeExpandStatus(scope.row, scope.$index)'
                  :class="[
                      iconClass ? iconClass: 'el-icon-caret-right',
                      'el-virtual-tree-ceil-icon',
                      scope.row.isLeaf ? 'is-leaf' : '',
                      !scope.row.isLeaf && scope.row.expanded ? 'expanded' : '',
                  ]"
              ></i>
              <!-- 多选图表显示区域，使用多选框实现 -->
              <el-checkbox
                  v-if='showCheckbox'
                  v-model="scope.row.checked"
                  :disabled='scope.row.disabled'
                  :indeterminate="!checkStrictly && scope.row.indeterminate"
                  @click.native.stop
                  @change="handleNodeCheckChange(scope.row, scope.$index)"
              ></el-checkbox>
              <!-- 文字显示区域 -->
              <node-content :node='scope.row'></node-content>
              <!-- <span class="el-virtual-tree-ceil-text">{{ scope.row[mergeProps.label] }}</span> -->
          </div>
        </div>
      </template>
    </el-virtual-scroller>
    <!-- 内容为空时的显示文字 -->
    <div v-else class='el-virtual-tree__empty-block'>
      <span class='el-virtual-tree__empty-text'>{{emptyText}}</span>
    </div>
  </div>
</template>

<script>

import ElVirtualScroller from '@ued_fpi/element-ui/packages/virtual-scroller';
import Node from '../utils/node';
import props from '../utils/props';

export default {
  name: 'ElVirtualTree',
  props,
  data() {
    return {
      flatData: [], // 扁平化数据结构
      filterData: [], // 根据展开的节点和filter筛选后的数据
      curNodeKey: this.currentNodeKey, // 当前选中的节点key
      keyMapping: {}, // 节点key索引 每次初始化flatData都重置keymapping，可认为keyMappinbg中有值，flatData既有对应的节点
      searchMapping: undefined, // 符合检索结果的节点key
      ceilSize: 26 // 单个元素的高度
    };
  },
  computed: {
    mergeProps() {
      return {
        children: 'children',
        label: 'label',
        disabled: 'disabled',
        ...this.props
      };
    }
  },
  components: {
    ElVirtualScroller,
    NodeContent: {
      props: {
        node: {
          required: true
        }
      },
      render(h) {
        const parent = this.$parent.$parent.$parent;
        const node = this.node;
        const data = node.data;
        const label = data[parent.props.label];
        return (
          parent.renderContent
            ? parent.renderContent.call(parent._renderProxy, h, { node, data })
            : <span class="el-virtual-tree-ceil-text">{ label }</span>
        );
      }
    }
  },
  watch: {
    data: {
      handler() {
        this.init();
      },
      deep: true
    },
    // 监听当前选中节点变化
    currentNodeKey() {
      this.curNodeKey = this.currentNodeKey;
      this.setCurrentKey(this.currentNodeKey);
    }
  },
  created() {
    this.init();
  },
  methods: {
    /**
     * 1.将树结构数据扁平化
     * 2.筛选出需要展示的数据
     * 3.设置节点选中状态
     * 3.根据父子节点相互关联，设置节点选中
    */
    init() {
      // 初始化书数据
      if (Array.isArray(this.data)) {
        const data = this.treeDataToFlatData(this.data);
        this.flatData = data;
      } else {
        const data = this.treeDataToFlatData([this.data]);
        this.flatData = data;
      }
      // 设置当前节点
      this.setCurrentKey(this.curNodeKey);

      // 设置展开节点
      if (!this.defaultExpandAll) {
        for (let key of this.defaultExpandedKeys) {
          if (this.keyMapping.hasOwnProperty(key)) {
            this.flatData[this.keyMapping[key]].setExpandStatus(true);
          }
        }
      }
      // 设置节点的选中状态
      this.setCheckedKeys(this.defaultCheckedKeys);
      // 筛选需要的数据
      this.getFilterDataByFlatData();
    },
    /**
    * 将树结构数据，转为扁平结构(数组形式)
    */
    treeDataToFlatData(data) {
      // 每次初始化flatData都重置keymapping
      // 可认为keyMappinbg中有值，flatData既有对应的节点
      this.keyMapping = {};
      let originalIndex = 0;
      // data: 数据
      // parent：父对象
      // level：层级
      const handleNodeData = (child, parent = null, level = 1) => {
        // 扁平化数据
        let fData = [];
        // 当前层级节点
        let directNodes = [];
        for (let item of child) {
          const nodeKey = item[this.nodeKey];
          const children = item[this.mergeProps.children];
          this.keyMapping[nodeKey] = originalIndex++;
          let isLeaf = true;
          // 判断是否有子节点,且子节点为数组，且子节点长度大于0
          if (children && Array.isArray(children) && children.length > 0) {
            isLeaf = false;
          }

          const node = new Node({
            id: nodeKey,
            label: item[this.mergeProps.label],
            data: item,
            disabled: item[this.mergeProps.disabled],
            expanded: this.defaultExpandAll,
            level: level,
            isLeaf,
            parent,
            visible: this.defaultExpandAll || !parent // 只有展开全部时或者节点为顶层节点时，默认为可见
          });
          // 所有子节点
          let childNodes = [];
          // 所有直接子节点
          // 是否有子节点
          if (!isLeaf) {
            const cData = handleNodeData(children, node, level + 1);
            childNodes = cData;
          }
          node.addChildNodes(childNodes);
          directNodes.push(node);
          fData.push(node);
          fData = fData.concat(childNodes);
        }
        return fData;
      };

      return handleNodeData(data);
    },

    /**
     * 根据展开的节点,筛选flatData中的数据
     * */
    getFilterDataByFlatData() {

      // 如果当前有检索
      if (this.searchMapping) {
        this.filterData = this.flatData.filter(item => {
          return item.visible && this.searchMapping[item.id];
        });
      } else {
        this.filterData = this.flatData.filter(item => {
          return item.visible;
        });
      }
    },
    /**
     * 节点点击事件
     * node：节点信息
     * indx: 节点在data中的索引
    */
    nodeClick(node, index) {
      // 节点点击，触发以下几个行为
      // 1.将节点置为当前节点
      this.setCurrentNode(node);
      // 2.根据配置expand-on-click-node是否需要展开或者收起节点
      if (this.expandOnClickNode && !node.isLeaf) {
        this.toggleNodeExpandStatus(node, index);
      }
      // 3.根据配置show-checkbox和check-on-click-node以及当前节点未被禁用是否需要选中节点
      if (this.showCheckbox && this.checkOnClickNode && !node.disabled) {
        this.handleNodeCheckChange(node, index, false);
      }
      this.$emit('node-click', node.data, node, this);
    },
    /**
     * 对树节点进行筛选操作
     * 接收一个任意类型的参数，该参数会在 filter-node-method 中作为第一个参数
    */
    filter(text) {
      if (!this.filterNodeMethod) {
        console.error('缺少filter-node-method方法');
        return;
      }
      if (text) {
        this.searchMapping = {};
        // 遍历所有节点，对于不符合筛选的置为不可见
        for (let item of this.flatData) {
          if (this.filterNodeMethod(text, item.data, item)) {
            this.searchMapping[item.id] = true; // item.id已经在init处理过
            // 将其祖先节点加入到searchMapping中
            // 将其祖先节点的展开状态置为true
            let parent = item.parent;
            parent.setExpandStatus(true);
            while (parent && !this.searchMapping[parent.id]) {
              this.searchMapping[parent.id] = true;
              parent = parent.parent;
            }
          }
        }
      } else {
        this.searchMapping = undefined;
      }
      this.getFilterDataByFlatData();
    },
    /**
     * 获取当前被选中节点的 key，使用此方法必须设置 node-key 属性，若没有节点被选中则返回 null
    */
    getCurrentKey() {
      return this.curNodeKey;
    },
    /**
     * 通过 key 设置某个节点的当前选中状态，使用此方法必须设置 node-key 属性
     * (key) 待被选节点的 key，若为 null 则取消当前高亮的节点
    */
    setCurrentKey(key) {
      // key为null
      if (!key) {
        if (this.curNodeKey && this.keyMapping.hasOwnProperty(this.curNodeKey)) {
          // 将上一次的节点位置未选中
          this.flatData[this.keyMapping[this.curNodeKey]].setCurrentStatus(false);
        }
        this.curNodeKey = null;
        return;
      }
      // 如果和之前选中的节点相同，不处理
      if (key !== this.curNodeKey) {
        if (this.curNodeKey && this.keyMapping.hasOwnProperty(this.curNodeKey)) {
          // 将上一次的节点位置未选中
          this.flatData[this.keyMapping[this.curNodeKey]].setCurrentStatus(false);
        }

        // 判断是否包含该节点
        if (this.keyMapping.hasOwnProperty(key)) {
          // 找到对应的节点
          const node = this.flatData[this.keyMapping[key]];
          node.setCurrentStatus(true);
          // 保存新的key值
          this.curNodeKey = key;
          this.$emit('current-change', node.data, node);
        }
      }
    },
    /**
     * 获取当前被选中节点的 data，若没有节点被选中则返回 null
    */
    getCurrentNode() {
      if (this.curNodeKey && this.keyMapping.hasOwnProperty(this.curNodeKey)) {
        return this.flatData[this.keyMapping[this.curNodeKey]];
      }
      return null;
    },
    /**
     * 通过 node 设置某个节点的当前选中状态，使用此方法必须设置 node-key 属性
     * (node) 待被选节点的 node
    */
    setCurrentNode(node) {
      this.setCurrentKey(node.id);
    },
    /**
     * 通过 keys 设置目前勾选的节点，使用此方法必须设置 node-key 属性
     * (keys, leafOnly) 接收两个参数，1. 勾选节点的 key 的数组 2. boolean 类型的参数，若为 true 则仅设置叶子节点的选中状态，默认值为 false
    */
    setCheckedKeys(keys, leafOnly = false) {
      // 先清除其他节点选中
      // 找到所有顶级节点
      const topNodes = this.flatData.filter(item => {
        return !item.parent;
      });
      for (let item of topNodes) {
        // 清除节点选中
        item.setCheckedStatus(false, true);
      }
      this.setNodeCheckedStatus(keys, true, !this.checkStrictly, leafOnly);
    },
    /**
     * 	若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点的 key 所组成的数组
     * (leafOnly) 接收一个 boolean 类型的参数，若为 true 则仅返回被选中的叶子节点的 keys，默认值为 false
    */
    getCheckedKeys(leafOnly = false) {
      if (!this.showCheckbox) {
        return;
      }
      let keys = [];
      if (leafOnly) {
        for (let item of this.flatData) {
          if (item.checked && item.isLeaf) {
            keys.push(item.id);
          }
        }
      } else {
        for (let item of this.flatData) {
          if (item.checked) {
            keys.push(item.id); // 这里的id已经处理过，对应nodeKey
          }
        }
      }
      return keys;
    },
    /**
     * 设置目前勾选的节点，使用此方法必须设置 node-key 属性
     * (nodes) 接收勾选节点数据的数组
    */
    setCheckedNodes(nodes) {
      const keys = nodes.map(item => item[this.nodeKey]);
      this.setCheckedKeys(keys);
    },
    /**
     * 	若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点所组成的数组
     * (leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数，1. 是否只是叶子节点，默认值为 false 2. 是否包含半选节点，默认值为 false
    */
    getCheckedNodes(leafOnly = false, includeHalfChecked) {
      if (!this.showCheckbox) {
        return;
      }
      let nodes;
      if (leafOnly) {
        // 如果只查询叶子节点，不需要考虑是否包含半选节点，因为叶子节点不存在半选
        nodes = this.flatData.filter(item => {
          return item.isLeaf && item.checked;
        });
      } else {
        if (includeHalfChecked) {
          // 当父级节点相互不关联时，不存在半选节点
          if (this.checkStrictly) {
            nodes = this.flatData.filter(item => {
              return item.checked;
            });
          } else {
            nodes = this.flatData.filter(item => {
              return item.checked || item.childrenCheckedSize > 0;
            });
          }
        } else {
          nodes = this.flatData.filter(item => {
            return item.checked;
          });
        }
      }
      return nodes;
    },
    /**
     * 	通过 key / data 设置某个节点的勾选状态，使用此方法必须设置 node-key 属性
     * (key/data, checked, deep) 接收三个参数，1. 勾选节点的 key 或者 data 2. boolean 类型，节点是否选中 3. boolean 类型，是否设置子节点 ，默认为 false
    */
    setChecked(data, checked, deep) {
      // 找到节点key
      if (typeof data === 'object') {
        data = data[this.nodeKey];
      }
      // 修改节点状态
      this.setNodeCheckedStatus([data], checked, deep);
    },
    /**
     * node:需要改变的节点
     * checked:节点勾选状态
     * deep:是否设置子节点
    */
    setNodeCheckedStatus(keys, checked, deep, leafOnly = false) {
      // 记录所有节点的选中状态
      const checkedMapping = {};
      for (let fItem of this.flatData) {
        checkedMapping[fItem.id] = fItem.checked;
      }

      if (leafOnly) {
        // 记录非叶子节点，后面需要将其选中去除
        // 不在for循环里去除的原因：如果该节点的子节点也被设置了勾选，
        // 在父子节点相互关联的情况下，该节点会再次被选中
        const nonLeafNodes = [];
        for (let item of keys) {
          if (this.keyMapping.hasOwnProperty(item)) {
            const node = this.flatData[this.keyMapping[item]];
            node.setCheckedStatus(checked, deep);
            if (!node.isLeaf) {
              nonLeafNodes.push(node);
            }
          }
        }
        // 移除非叶子节点的选中
        for (let nonLeafItem of nonLeafNodes) {
          nonLeafItem.setCheckedStatus(!checked, false);
        }
      } else {
        for (let item of keys) {
          if (this.keyMapping.hasOwnProperty(item)) {
            this.flatData[this.keyMapping[item]].setCheckedStatus(checked, deep);
          }
        }
      }

      // 所有节点新的选中状态，和之前的记录值比对，用于通知节点选中改变
      for (let item of this.flatData) {
        if (checkedMapping[item.id] !== item.checked) {
          this.$emit('check-change', item.data, item, this);
        }
      }
    },
    /**
     * 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点所组成的数组
    */
    getHalfCheckedNodes() {
      if (!this.showCheckbox) {
        return;
      }
      // 父级节点不相互关联，半选一定为空
      if (this.checkStrictly) {
        return [];
      }
      return this.flatData.filter(item => {
        return !item.checked && item.childrenCheckedSize > 0;
      });
    },
    /**
     * 若节点可被选择（即 show-checkbox 为 true），则返回目前半选中的节点的 key 所组成的数组
    */
    getHalfCheckedKeys() {
      if (!this.showCheckbox) {
        return;
      }
      // 父级节点不相互关联，半选一定为空
      if (this.checkStrictly) {
        return [];
      }
      const keys = [];
      for (let item of this.flatData) {
        if (!item.checked && item.childrenCheckedSize > 0) {
          keys.push(item.id); // 这里的id已经处理过，对应nodeKey
        }
      }
      return keys;
    },
    /**
     * 展开/收缩子节点
    */
    toggleNodeExpandStatus(node, scrollIndex) {
      const expanded = node.expanded;
      // 如果当前处于检索状态，不需要改变子节点的可见状态
      node.toggleExpandStatus();
      if (expanded) {
        this.$emit('node-expand', node.data, node, this);
      } else {
        this.$emit('node-collapse', node.data, node, this);
      }
      // 更新filterData数据
      this.getFilterDataByFlatData();
    },
    /**
     * 根据 data 或者 key 拿到 Tree 组件中的 node
     * (data) 要获得 node 的 key 或者 data
    */
    getNode(data) {
      if (typeof data === 'object') {
        data = data[this.nodeKey];
      }
      if (this.keyMapping.hasOwnProperty(data)) {
        return this.flatData[this.keyMapping[data]];
      }
      return;
    },
    /**
     * 节点勾选状态处理
    */
    handleNodeCheckChange(node, index, needNegation = true) {
      // 因直接点击复选框，会改变node的勾选状态，这里需要先取反，再执行toggle
      // 通过点击节点触发的，不需要
      if (needNegation) {
        node.checked = !node.checked;
      }
      node.toggleCheckedStatus(!this.checkStrictly);
      if (needNegation) {
        const checkedNodes = this.getCheckedNodes();
        const checkedKeys = this.getCheckedKeys();
        const halfCheckedNodes = this.getHalfCheckedNodes();
        const halfCheckedKeys = this.getHalfCheckedKeys();
        this.$emit('check',
          node.data,
          {
            checkedNodes,
            checkedKeys,
            halfCheckedNodes,
            halfCheckedKeys
          }
        );
      }
    },
    /**
     * 获取滚动条距离顶部的高度
    */
    getScrollTop() {
      if (!this.$refs.virtualScroller) return;
      return this.$refs.virtualScroller.getScrollTop();
    },
    /**
     * 设置滚动条距离顶部的高度，默认为0
    */
    setScrollTop(distance = 0) {
      if (!this.$refs.virtualScroller) return;
      this.$refs.virtualScroller.setScrollTop(distance);
    },
    /**
     * 获取指定节点距离顶部的高度
    */
    getNodeTop(data) {
      if (typeof data === 'object') data = data[this.nodeKey];
      let nodeIndex;
      // 找到该节点在可视节点中的索引
      for (let i = 0; i < this.filterData.length; i++) {
        if (this.filterData[i][this.nodeKey] === data) {
          nodeIndex = i;
          break;
        }
      }
      if (typeof nodeIndex !== 'undefined' && this.$refs.virtualScroller) {
        return nodeIndex * this.ceilSize - this.$refs.virtualScroller.getScrollTop();
      }
      return;
    },
    /**
     * 设置指定节点距离顶部的高度(该节点必须可见)
    */
    setNodeTop(data, distance = 0) {
      if (typeof data === 'object') data = data[this.nodeKey];
      let nodeIndex;
      // 找到该节点在可视节点中的索引
      for (let i = 0; i < this.filterData.length; i++) {
        if (this.filterData[i][this.nodeKey] === data) {
          nodeIndex = i;
          break;
        }
      }
      if (typeof nodeIndex !== 'undefined' && this.$refs.virtualScroller) {
        this.$refs.virtualScroller.setScrollTop(nodeIndex * this.ceilSize - Number(distance));
      }
    }
  }
};
</script>
