<template>
  <div>
    <v-menu
      v-model="show"
      offset-y
      nudge-top="25"
      :attach="attach"
      content-class="noShadow"
      :close-on-content-click="false"
    >
      <template v-slot:activator="{ on, attrs }">
        <div
          v-bind="attrs"
          v-on="on"
          class="cursor-pointer"
        >
          <v-combobox
            v-model="modelValue"
            :items="[]"
            hide-selected
            readonly
            outlined
            clearable
            dense
            :multiple="multiple"
            :small-chips="multiple"
            v-bind="$attrs"
            @click:clear="handleClear"
          >
            <template v-slot:selection="{ attrs, item, index }" v-if="multiple">
              <v-chip
                class="mt-2"
                v-bind="attrs"
                color="#f5f5f5"
                label
                small
              >
                <span class="pr-2 primary--text">{{ item[itemLabel] || item }}</span>
                <v-icon v-if="!item.$isActive" color="primary" small @click.stop="handleDelete(index)">$delete</v-icon>
              </v-chip>
            </template>
          </v-combobox>
        </div>
      </template>
      <div class="d-flex">
        <div class="d-flex shadow white">
          <div
            v-for="(item, i) in dataItems"
            :key="i"
            class="content"
          >
            <template v-if="item.data">
              <div
                v-for="_item in item.data"
                :key="_item[itemValue]"
                class="d-flex justify-space-between content-item"
                :class="{
                  active: item.choose?.[itemValue] === _item[itemValue]
                }"
                @click="handleTo(_item, i)"
              >
                <div class="content-item-hide d-flex align-center">
                  <v-checkbox
                    v-if="multiple"
                    class="ml-2 pt-0"
                    dense
                    value
                    hide-details
                    indeterminate-icon="mdi-minus-box primary--text"
                    :indeterminate="handleIndeterminate(_item, i)"
                    :input-value="handleInputValue(_item, i)"
                    color="primary"
                    @click.stop="handleChooseMultiple(_item, i)"
                  ></v-checkbox>
                  {{ _item[itemLabel] }}
                </div>
                <template v-if="_item.data && _item.data.length > 0">
                  <v-icon
                    :color="item.choose?.[itemValue] === _item[itemValue] ? 'primary' : ''"
                    class="ml-3"
                  >mdi-chevron-right</v-icon>
                </template>
                <template v-if="item.choose?.[itemValue] === _item[itemValue] && !_item.data?.length && !multiple">
                  <v-icon color="primary" class="ml-3">mdi-check</v-icon>
                </template>
              </div>
            </template>
          </div>
          <div class="content" v-if="!dataItems.length">
            <div class="noData">{{ noData }}</div>
          </div>
        </div>
      </div>
    </v-menu>
  </div>
</template>

<script>
// 级联选择器
export default {
  name: 'm-data-picker',
  props: {
    attach: {
      type: Boolean,
      default: false
    },
    value: {
      type: [Array, String, Number, Object],
      default: null
    },
    // 折叠显示
    collapseTags: {
      type: Boolean,
      default: false
    },
    // 最大折叠显示个数
    maxCollapseTags: {
      type: Number,
      default: 1
    },
    items: { // 树结构数组
      type: Array,
      default: () => []
    },
    multiple: { // 暂时不支持多选
      type: Boolean,
      default: false
    },
    itemLabel: {
      type: String,
      default: 'label'
    },
    itemValue: {
      type: String,
      default: 'value'
    },
    itemChildren: {
      type: String,
      default: 'children'
    },
    returnObject: {
      type: Boolean,
      default: false
    },
    noData: {
      type: String,
      default: 'No data available'
    }
  },
  data () {
    return {
      show: false,
      isUpdate: true,
      multipleChoose: [],
      valueType: null,
      modelValue: null, // 回显文字
      translateItems: [], // 转换后的数组
      dataItems: [] // 展示列表
    }
  },
  watch: {
    value: {
      handler (val) {
        if (this.multiple && JSON.stringify(val) === JSON.stringify(this.multipleChoose.map(e => e.value[e.value.length - 1]))) {
          return
        }
        this.valueReview()
      },
      deep: true,
      immediate: true
    },
    items: {
      handler (val) {
        if (!val.length) {
          return
        }
        // 转化label、value、children结构
        this.translateItems = this.translateItemsFn(JSON.parse(JSON.stringify(val)))
        this.valueReview()
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    handleDelete (index) {
      this.multipleChoose.splice(index, 1)
      this.handleReturn()
    },
    handleBlur (event) {
      console.log(event)
      event.preventDefault()
      event.stopPropagation()
    },
    translateItemsFn (items) {
      return items.map(e => {
        if (e[this.itemChildren]?.length) {
          const _items = this.translateItemsFn(e[this.itemChildren])
          e[this.itemChildren] = _items
        }
        return this.translateItem(e)
      })
    },
    translateItem (item) {
      const { data, ...obj } = item
      return {
        ...obj,
        data: data || item[this.itemChildren] || [],
        allNumber: this.checkNumber(item[this.itemChildren]),
        [this.itemLabel]: this.getNestedValue(item, this.itemLabel),
        [this.itemValue]: this.getNestedValue(item, this.itemValue)
      }
    },
    getNestedValue (obj, path) {
      const arr = path.split('.')
      if (arr.length === 1) {
        return obj[path]
      }
      return arr.reduce((currentObject, key) => {
        return (currentObject && currentObject[key] !== undefined) ? currentObject[key] : undefined
      }, obj)
    },
    // 查询止线数目
    checkNumber (arr) {
      if (!arr || !arr.length) {
        return 1
      }
      let num = 0
      const check = (_arr) => {
        _arr.forEach(e => {
          if (e[this.itemChildren]?.length) {
            check(e[this.itemChildren])
          } else {
            num++
          }
        })
      }
      check(arr)
      return num
    },
    // 查询是否有选中
    handleIndeterminate (_item, i) {
      const list = this.multipleChoose.filter(e => e.value[i] === _item[this.itemValue])
      if (!list.length) {
        return false
      }
      return _item.allNumber > list.length
    },
    // 回显 选中或者没选中
    handleInputValue (_item, i) {
      if (_item[this.itemChildren]?.length) {
        const list = this.multipleChoose.filter(e => e.value[i] === _item[this.itemValue])
        if (list.length === _item.allNumber) {
          return true
        }
        if (list.length) {
          return undefined
        }
        return false
      }
      return this.multipleChoose.some(e => e.value[i] === _item[this.itemValue])
    },
    valueReview () {
      if (!this.translateItems.length) {
        return
      }
      const initList = [{
        choose: null,
        data: this.translateItems
      }]
      if (!this.value || this.multiple) {
        this.dataItems = initList
      }
      // 多选单独渲染
      if (this.multiple) {
        this.multipleChoose = this.handleReviewMultiple(JSON.parse(JSON.stringify(this.value)), this.translateItems)
        this.modelValue = this.returnMultipleModelValue()
        return
      }
      const item = this.findItem(this.value, this.translateItems)
      if (!item) {
        return
      }
      const _label = []
      this.dataItems = item.map(e => {
        _label.push(e.choose[this.itemLabel])
        return {
          ...e
        }
      })
      this.modelValue = _label.join(' / ')
    },
    findItem (value, lists) {
      let level = 0
      const check = (val, items, arr = []) => {
        let i = 0
        while (i < items.length) {
          const { data, ...obj } = items[i]
          arr.splice(level, arr.length - level, {
            choose: obj,
            data: items
          })
          if (obj[this.itemValue] === val && !data?.length) {
            return arr
          }
          level++
          const oData = check(val, data, arr)
          if (oData) {
            return oData
          }
          level--
          i++
        }
        return false
      }
      return check(value, lists)
    },
    handleTo (item, level) {
      const { choose, ...obj } = item
      this.dataItems[level].choose = obj
      const _index = level + 1
      if (!item.data || item.data.length === 0) {
        this.dataItems.splice(_index, this.dataItems.length - _index)
        this.handleReturn()
        return
      }
      this.dataItems.splice(_index, this.dataItems.length - _index, {
        data: item.data,
        choose: null
      })
    },
    handleChooseMultiple (item, level) {
      const obj = this.dataItems.reduce((r, v, i) => {
        if (i >= level) {
          return r
        }
        r.label.push(v.choose[this.itemLabel])
        r.value.push(v.choose[this.itemValue])
        r.data.push(v.choose)
        return r
      }, { label: [], value: [], data: [] })
      obj.label.push(item[this.itemLabel])
      obj.value.push(item[this.itemValue])
      obj.data.push(item)
      if (!item[this.itemChildren]?.length) {
        const index = this.multipleChoose.findIndex(e => e.value[e.value.length - 1] === item[this.itemValue])
        if (index > -1) {
          this.multipleChoose.splice(index, 1)
          this.showPanel()
          return
        }
        this.multipleChoose.push(obj)
        this.showPanel()
        return
      }
      // 判断是否全选
      const checkItems = this.multipleChoose.filter(e => e.value[level] !== item[this.itemValue])
      // 如果是全选状态
      if (this.multipleChoose.length - checkItems.length === item.allNumber) {
        this.multipleChoose = checkItems
        this.showPanel()
        return
      }
      const arr = []
      this.handleRecursion(item, arr)
      const _array = []
      arr.forEach(e => {
        Object.assign(e, {
          label: [...obj.label, ...e.label],
          value: [...obj.value, ...e.value],
          data: [
            ...obj.data,
            ...e.data
          ]
        })
        // 过滤已有选择
        if (this.multipleChoose.find(_e => _e.value.join('-') === e.value.join('-'))) {
          return
        }
        _array.push(e)
      })
      this.multipleChoose.push(..._array)
      this.showPanel()
    },
    handleRecursion (v, arr = [], content = { label: [], value: [], data: [] }, level = 0) {
      const item = JSON.parse(JSON.stringify(v[this.itemChildren]))
      while (item.length) {
        const _item = item.shift()
        content.label.splice(level, content.label.length, _item[this.itemLabel])
        content.value.splice(level, content.label.length, _item[this.itemValue])
        content.data.splice(level, content.label.length, _item)
        if (_item[this.itemChildren]?.length) {
          this.handleRecursion(_item, arr, content, level + 1)
        } else {
          arr.push(JSON.parse(JSON.stringify(content)))
        }
      }
    },
    handleReviewMultiple (val, items, content = { label: [], value: [], data: [] }, level = 0, arr = []) {
      items.forEach(_item => {
        content.label.splice(level, content.label.length, _item[this.itemLabel])
        content.value.splice(level, content.label.length, _item[this.itemValue])
        content.data.splice(level, content.label.length, _item)
        if (_item[this.itemChildren]?.length) {
          this.handleReviewMultiple(val, _item[this.itemChildren], content, level + 1, arr)
        } else {
          const _index = val.indexOf(_item[this.itemValue])
          if (_index > -1) {
            val.splice(_index, 1)
            arr.push(JSON.parse(JSON.stringify(content)))
          }
        }
      })
      return arr
    },
    showPanel () {
      this.show = false
      this.modelValue = this.returnMultipleModelValue()
      this.isUpdate = false
      const ids = this.multipleChoose.map(e => e.value[e.value.length - 1])
      this.$emit('input', ids)
      this.$emit('change', !this.returnObject ? ids : this.multipleChoose.map(e => e.data[e.data.length - 1]))
      this.$nextTick(() => {
        this.show = true
      })
    },
    returnMultipleModelValue () {
      if (this.collapseTags) {
        const num = this.multipleChoose.length - this.maxCollapseTags
        const modelValue = this.multipleChoose.slice(0, this.maxCollapseTags).map(e => {
          return {
            [this.itemLabel]: e.label.join(' / '),
            [this.itemValue]: e.value.join('-')
          }
        })
        if (num <= 0) {
          return modelValue
        }
        return [
          ...modelValue,
          {
            [this.itemLabel]: `+${this.multipleChoose.length - this.maxCollapseTags}`,
            [this.itemValue]: `+${this.multipleChoose.length - this.maxCollapseTags}`,
            $isActive: true
          }
        ]
      }
      return this.multipleChoose.map(e => ({
        [this.itemLabel]: e.label.join(' / '),
        [this.itemValue]: e.value.join('-')
      }))
    },
    handleReturn () {
      if (this.multiple) {
        this.showPanel()
        return
      }
      this.show = false
      const label = []
      const value = []
      const chooseValue = this.dataItems.map(({ choose }) => {
        const { [this.itemLabel]: _label, [this.itemValue]: _value, ...obj } = choose
        label.push(_label)
        value.push(_value)
        if (this.returnObject) {
          return obj
        }
        return _value
      })
      this.modelValue = label.join(' / ')
      this.$emit('input', value.pop())
      this.$emit('change', chooseValue)
    },
    handleClear () {
      this.$emit('input', this.multiple ? [] : null)
    }
  }
}
</script>

<style lang="scss" scoped>
.cursor-pointer {
  cursor: pointer;
}
.noShadow {
  box-shadow: none !important;
  contain: unset !important;
  overflow: visible !important;
}
.shadow {
  box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
  0px 8px 10px 1px rgba(0, 0, 0, 0.14),
  0px 3px 14px 2px rgba(0, 0, 0, 0.12);
  // border-radius: 5px;
}
.content {
  max-height: 400px;
  min-height: 56px;
  min-width: 250px;
  max-width: 400px;
  overflow-y: auto;
  &-item {
    height: 45px;
    line-height: 45px;
    padding: 0 10px;
    cursor: pointer;
    font-size: 14px;
    &.active {
      color: #1976d2;
    }
    &-hide {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      width: 0;
      flex: 1;
    }
    &:hover {
      background-color: #e7f2fb;
    }
  }
}
.noData {
  height: 56px;
  line-height: 56px;
  padding: 0 16px;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 56px;
}
</style>
