<template>
  <view class="u-tree">
    <TreeNode
      v-for="node in treeData"
      :key="node[nodeKeyName]"
      :node="node"
      :props="props"
      :show-checkbox="showCheckbox"
      :check-strictly="checkStrictly"
      :expand-on-click-node="expandOnClickNode"
      :highlight-current="highlightCurrent"
      :current-node-key="innerCurrentNodeKey"
      :indent="indent"
      :icon-size="iconSize"
      :checkbox-size="checkboxSize"
      :expand-icon="expandIcon"
      :collapse-icon="collapseIcon"
      @node-click="handleNodeClick"
      @check-change="handleCheckChange"
    >
      <template #default="slotProps">
        <slot v-bind="slotProps"></slot>
      </template>
    </TreeNode>
  </view>
</template>

<script>
import TreeNode from './tree-node.vue';

export default {
  name: 'u-tree',
  components: { TreeNode },
  props: {
    data: {
      type: Array,
      default: () => []
    },
    props: {
      type: Object,
      default: () => ({
        label: 'label',
        children: 'children',
        nodeKey: 'id',
        disabled: 'disabled'
      })
    },
    nodeKey: {
      type: String,
      default: ''
    },
    showCheckbox: {
      type: Boolean,
      default: false
    },
    defaultExpandAll: {
      type: Boolean,
      default: false
    },
    defaultExpandedKeys: {
      type: Array,
      default: () => []
    },
    defaultCheckedKeys: {
      type: Array,
      default: () => []
    },
    expandOnClickNode: {
      type: Boolean,
      default: true
    },
    checkOnClickNode: {
      type: Boolean,
      default: false
    },
    checkStrictly: {
      type: Boolean,
      default: false
    },
    accordion: {
      type: Boolean,
      default: false
    },
    highlightCurrent: {
      type: Boolean,
      default: false
    },
    currentNodeKey: {
      type: [String, Number],
      default: ''
    },
    indent: {
      type: [String, Number],
      default: 32
    },
    iconSize: {
      type: [String, Number],
      default: 14
    },
    checkboxSize: {
      type: [String, Number],
      default: 16
    },
    expandIcon: {
      type: String,
      default: 'play-right-fill'
    },
    collapseIcon: {
      type: String,
      default: 'arrow-down-fill'
    }
  },
  data() {
    return {
      treeData: [],
      innerCurrentNodeKey: ''
    };
  },
  computed: {
    nodeKeyName() {
      return this.nodeKey || this.props.nodeKey || 'id';
    }
  },
  watch: {
    data: {
      handler(newVal) {
        this.initTree(newVal);
      },
      deep: true,
      immediate: true
    },
    currentNodeKey: {
      handler(val) {
        this.innerCurrentNodeKey = val;
      },
      immediate: true
    }
  },
  emits: ['node-click', 'check-change', 'check', 'node-expand', 'node-collapse', 'current-change'],
  methods: {
    initTree(data = this.data) {
      this.treeData = JSON.parse(JSON.stringify(data || []));
      this.applyDefaultState(this.treeData);
    },
    applyDefaultState(nodes, parent = null) {
      nodes.forEach((node) => {
        const key = node[this.nodeKeyName];
        if (this.defaultExpandAll) {
          node.expanded = true;
        } else if (this.defaultExpandedKeys.length) {
          node.expanded = this.defaultExpandedKeys.map(String).includes(String(key));
        } else {
          node.expanded = false;
        }
        if (this.defaultCheckedKeys.length) {
          node.checked = this.defaultCheckedKeys.map(String).includes(String(key));
        }
        const children = node[this.props.children];
        if (children && children.length) {
          this.applyDefaultState(children, node);
        }
      });
    },
    handleNodeClick(node) {
      if (this.highlightCurrent) {
        const oldKey = this.innerCurrentNodeKey;
        this.innerCurrentNodeKey = node[this.nodeKeyName];
        this.$emit('current-change', node, oldKey);
      }
      if (this.checkOnClickNode && this.showCheckbox) {
        node.checked = !node.checked;
        this.handleCheckChange(node, node.checked);
      }
      this.$emit('node-click', node);
    },
    handleCheckChange(node, checked) {
      this.$emit('check-change', node, checked);
      this.$emit('check', node, {
        checkedNodes: this.getCheckedNodes(),
        checkedKeys: this.getCheckedKeys(),
        halfCheckedNodes: [],
        halfCheckedKeys: []
      });
    },
    traverse(nodes, callback) {
      nodes.forEach((node) => {
        callback(node);
        const children = node[this.props.children];
        if (children && children.length) {
          this.traverse(children, callback);
        }
      });
    },
    getCheckedNodes(leafOnly = false) {
      const result = [];
      this.traverse(this.treeData, (node) => {
        if (!node.checked) return;
        const children = node[this.props.children] || [];
        if (leafOnly && children.length) return;
        result.push(node);
      });
      return result;
    },
    getCheckedKeys(leafOnly = false) {
      return this.getCheckedNodes(leafOnly).map((node) => node[this.nodeKeyName]);
    },
    setCurrentKey(key) {
      this.innerCurrentNodeKey = key;
    },
    getCurrentKey() {
      return this.innerCurrentNodeKey;
    },
    getCurrentNode() {
      let current = null;
      this.traverse(this.treeData, (node) => {
        if (String(node[this.nodeKeyName]) === String(this.innerCurrentNodeKey)) {
          current = node;
        }
      });
      return current;
    }
  }
};
</script>

<style scoped>
.u-tree {
  font-size: 28rpx;
}
</style>
