<template>
  <view class="ml-tree" :class="[className || '', {'is-disabled': disabled}]">
    <view v-if="showCheckbox && (showAllCheckbox || showCheckboxBtn)" class="ml-tree-checkbox-btns">
      <view v-if="showAllCheckbox" class="ml-tree-checkbox-btns-left">
        <ml-checkbox v-model="isAllCheckboxChecked" :indeterminate="isAllCheckboxIndeterminate" :disabled="disabled" @change="toggleAllCheckboxEvent">全选</ml-checkbox>
      </view>
      <view v-if="showCheckboxBtn" class="ml-tree-checkbox-btns-right">
        <ml-button v-if="isExpandAll" size="mini" status="primary" @click="setAllTreeExpand(false)">一键收起</ml-button>
        <ml-button v-else size="mini" status="primary" @click="setAllTreeExpand(true)">一键展开</ml-button>
      </view>
    </view>
    <ml-tree-node v-for="(item, index) in treeData" :key="index" :node-data="item"></ml-tree-node>
  </view>
</template>

<script>
import XEUtils from 'xe-utils'

export default {
  name: 'MlTree',
  props: {
    data: Array,
    height: String,
    disabled: Boolean,
    // 是否显示顶部全选按钮
    showAllCheckbox: Boolean,
    // 是是否显示顶部操作按钮
    showCheckboxBtn: Boolean,
    // 单选框
    showRadio: Boolean,
    // 复选框
    showCheckbox: Boolean,
    // （复选框）父子不关联
    checkStrictly: {
      type: Boolean,
      default: false
    },
    // (暂时没用)
    indeterminateToChecked: {
      type: Boolean,
      default: false
    },
    checkRowKeys: Array,
    halfRowKeys: Array,
    optionProps: Object,
    // 已废弃，统一使用 optionProps
    nodeProps: Object
  },
  provide () {
    return {
      mlTree: this
    }
  },
  data () {
    return {
      isExpandAll: false,
      treeData: [],
      isAllCheckboxChecked: false,
      isAllCheckboxIndeterminate: false,
      indeterminateRowKeys: [],
      selectCheckboxRowKeys: [],
      expandRowKeys: []
    }
  },
  computed: {
    styles () {
      return this.height ? `height: ${this.height};` : ''
    },
    nodeOpts () {
      const nodeProps = this.nodeProps || {}
      const optionProps = this.optionProps || {}
      return {
        title: optionProps.label || nodeProps.name || 'name',
        key: optionProps.value || nodeProps.key || 'key',
        children: optionProps.children || nodeProps.children || 'children'
      }
    },
    titleField () {
      return this.nodeOpts.title
    },
    keyField () {
      return this.nodeOpts.key
    },
    childrenField () {
      return this.nodeOpts.children
    }
  },
  watch: {
    data (value) {
      this.treeData = value
    },
    checkRowKeys (value) {
      if (!this.isCheckboxModelUpdate) {
        this.initCheckboxRows(value)
      }
      this.isCheckboxModelUpdate = false
    }
  },
  created () {
    this.treeData = this.data
    this.initCheckboxRows(this.checkRowKeys)
  },
  methods: {
    selectCheckboxNodeEvent ({ nodeData, nodeKey }) {
      const { keyField, childrenField } = this
      const checked = this.selectCheckboxRowKeys.indexOf(nodeKey) === -1
      if (checked) {
        if (this.checkStrictly) {
          if (!this.selectCheckboxRowKeys.includes(nodeKey)) {
            this.selectCheckboxRowKeys.push(nodeKey)
          }
          const halfIndex = this.indeterminateRowKeys.indexOf(nodeKey)
          if (halfIndex > -1) {
            this.indeterminateRowKeys.splice(halfIndex, 1)
          }
        } else {
          XEUtils.eachTree([nodeData], (item) => {
            if (!this.selectCheckboxRowKeys.includes(item[keyField])) {
              this.selectCheckboxRowKeys.push(item[keyField])
            }
            const halfIndex = this.indeterminateRowKeys.indexOf(item[keyField])
            if (halfIndex > -1) {
              this.indeterminateRowKeys.splice(halfIndex, 1)
            }
          }, { children: childrenField })
        }
      } else {
        if (this.checkStrictly) {
          const nodeIndex = this.selectCheckboxRowKeys.indexOf(nodeKey)
          if (nodeIndex > -1) {
            this.selectCheckboxRowKeys.splice(nodeIndex, 1)
          }
          const halfIndex = this.indeterminateRowKeys.indexOf(nodeKey)
          if (halfIndex > -1) {
            this.indeterminateRowKeys.splice(halfIndex, 1)
          }
        } else {
          XEUtils.eachTree([nodeData], (item) => {
            const nodeIndex = this.selectCheckboxRowKeys.indexOf(item[keyField])
            if (nodeIndex > -1) {
              this.selectCheckboxRowKeys.splice(nodeIndex, 1)
            }
            const halfIndex = this.indeterminateRowKeys.indexOf(item[keyField])
            if (halfIndex > -1) {
              this.indeterminateRowKeys.splice(halfIndex, 1)
            }
          }, { children: childrenField })
        }
      }
      if (!this.checkStrictly) {
        this.updateParentIndeterminateStatus(nodeData)
      }
      this.updateCheckStatus()
      this.isCheckboxModelUpdate = true
      this.$emit('checkbox-change', { row: nodeData, checked, checkedKeys: this.selectCheckboxRowKeys })
      this.$emit('update:checkRowKeys', [...this.selectCheckboxRowKeys])
      this.$emit('update:halfRowKeys', [...this.indeterminateRowKeys])
    },
    expandNodeEvent ({ nodeData, nodeKey }) {
      const nodeIndex = this.expandRowKeys.indexOf(nodeKey)
      const expanded = nodeIndex === -1
      if (expanded) {
        this.expandRowKeys.push(nodeKey)
      } else {
        this.expandRowKeys.splice(nodeIndex, 1)
      }
      this.$emit('expand-change', { row: nodeData, expanded, expandedKeys: this.expandRowKeys })
    },
    updateParentIndeterminateStatus (nodeData) {
      const { keyField, childrenField } = this
      const rest = XEUtils.findTree(this.treeData, item => item[keyField] === nodeData[keyField])
      if (rest) {
        XEUtils.lastEach(rest.nodes.slice(0, rest.nodes.length - 1), (item) => {
          const currKey = item[keyField]
          const childs = item[childrenField]
          const isAllChecked = childs.every(item => this.selectCheckboxRowKeys.includes(item[keyField]))
          const isAllIndeterminate = !isAllChecked && childs.some(item => this.selectCheckboxRowKeys.includes(item[keyField]) || this.indeterminateRowKeys.includes(item[keyField]))
          const nodeIndex = this.selectCheckboxRowKeys.indexOf(currKey)
          if (isAllChecked) {
            if (nodeIndex === -1) {
              this.selectCheckboxRowKeys.push(currKey)
            }
          } else {
            if (nodeIndex > -1) {
              this.selectCheckboxRowKeys.splice(nodeIndex, 1)
            }
          }
          if (this.indeterminateToChecked) {
            const halfIndex = this.selectCheckboxRowKeys.indexOf(currKey)
            if (halfIndex === -1) {
              this.selectCheckboxRowKeys.push(currKey)
            }
          } else {
            const halfIndex = this.indeterminateRowKeys.indexOf(currKey)
            if (isAllIndeterminate) {
              if (halfIndex === -1) {
                this.indeterminateRowKeys.push(currKey)
              }
            } else {
              if (halfIndex > -1) {
                this.indeterminateRowKeys.splice(halfIndex, 1)
              }
            }
          }
        }, { children: childrenField })
      }
    },
    updateCheckStatus () {
      const { keyField } = this
      this.isAllCheckboxChecked = this.treeData.length && this.treeData.every(item => this.selectCheckboxRowKeys.includes(item[keyField]))
      this.isAllCheckboxIndeterminate = !this.isAllCheckboxChecked && this.treeData.some(item => this.selectCheckboxRowKeys.includes(item[keyField]) || this.indeterminateRowKeys.includes(item[keyField]))
    },
    initCheckboxRows (selecKeys) {
      this.selectCheckboxRowKeys = []
      this.indeterminateRowKeys = []
      selecKeys.forEach(nodeKey => {
        if (!this.selectCheckboxRowKeys.includes(nodeKey)) {
          this.selectCheckboxRowKeys.push(nodeKey)
        }
      })
      // 更新全部节点半选状态
      this.updateCheckStatus()
    },
    setCheckboxRow (key, checked) {
      let keys = key
      if (!XEUtils.isArray(key)) {
        keys = [key]
      }
      if (checked) {
        keys.forEach(nodeKey => {
          if (!this.selectCheckboxRowKeys.includes(nodeKey)) {
            this.selectCheckboxRowKeys.push(nodeKey)
          }
        })
      } else {
        keys.forEach(nodeKey => {
          const nodeIndex = this.selectCheckboxRowKeys.indexOf(nodeKey)
          if (nodeIndex > -1) {
            this.selectCheckboxRowKeys.splice(nodeIndex, 1)
          }
        })
      }
      this.updateCheckStatus()
    },
    getCheckboxRecords () {
      const { keyField } = this
      const keyMaps = {}
      XEUtils.eachTree(this.treeData, item => {
        keyMaps[item[keyField]] = item
      })
      return this.selectCheckboxRowKeys.map(key => keyMaps[key]).filter(item => item)
    },
    toggleAllCheckboxEvent () {
      this.toggleAllCheckboxRow()
      this.isCheckboxModelUpdate = true
      this.$emit('checkbox-all', { checkedKeys: this.selectCheckboxRowKeys, checkedRows: [] })
      this.$emit('update:checkRowKeys', [...this.selectCheckboxRowKeys])
      this.$emit('update:halfRowKeys', [...this.indeterminateRowKeys])
    },
    toggleAllCheckboxRow () {
      const { keyField, childrenField } = this
      const rowKeys = []
      if (this.isAllCheckboxChecked) {
        XEUtils.eachTree(this.treeData, (item) => {
          rowKeys.push(item[keyField])
        }, { children: childrenField })
      }
      this.selectCheckboxRowKeys = rowKeys
      this.updateCheckStatus()
    },
    setAllTreeExpand (expanded) {
      const { keyField, childrenField } = this
      const rowKeys = []
      if (expanded) {
        XEUtils.eachTree(this.treeData, (item) => {
          if (item[childrenField] && item[childrenField].length > 0) {
            rowKeys.push(item[keyField])
          }
        }, { children: childrenField })
      }
      this.isExpandAll = expanded
      this.expandRowKeys = rowKeys
    }
  }
}
</script>

<style lang="scss">
.ml-tree {
  padding-left: 16rpx;
  overflow: auto;
  background: #ffffff;
  .ml-tree-checkbox-btns {
    position: sticky;
    left: 0;
    top: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    height: 80rpx;
    border-bottom: 1px solid #e5e5e5;
  }
  .ml-tree-checkbox-btns-left {
    flex-grow: 1;
  }
  .ml-tree-checkbox-btns-right {
    display: flex;
    flex-direction: row;
    flex-shrink: 0;
  }
}
</style>
