<template>
  <section class="AiAreaGet">
    <el-cascader v-if="refresh" ref="areaCascader" :value="value" size="small" :props="props" :show-all-levels="showAll"
                 :options="options" @visible-change="editing=true" clearable
                 filterable :before-filter="handleFindArea" @change="handleAfterFilter"
                 v-bind="$attrs" v-on="$listeners" popper-class="popperSelectors"/>
  </section>
</template>

<script>
/**
 * 智能地区选择器
 * @displayName AiAreaGet
 */
export default {
  name: "AiAreaGet",
  inject: {
    elFormItem: {default: ""},
  },
  model: {
    prop: 'value',
    event: 'change',
  },
  props: {
    /**
     * 接口方法类:必填
     */
    instance: {default: () => null},
    /**
     * 绑定地区编码
     * @model
     */
    value: {default: ""},
    /**
     * 是否多选
     */
    multiple: Boolean,
    /**
     * 获取地区信息接口地址,默认为:/admin/area/queryAreaByParentId
     */
    action: {default: "/admin/area/queryAreaByParentId"},
    /**
     * 限制获取地区编码的范围,1:省 2:地级市 3:县/区 4:镇/街道 5:村/社区
     * @values 1,2,3,4,5
     */
    valueLevel: {default: 5},
    /**
     * 指定根级地区范围
     */
    root: {default: ""},
    /**
     * 获取地区名称,支持.sync 双向获取绑定
     */
    name: {default: ""},
    /**
     * 显示完整地区名称
     */
    showAll: Boolean
  },
  data() {
    return {
      rules: [10, 8, 6, 3, 0],
      cacheOptions: [],
      editing: false,
      filterData: [],
      caches: [],
      roots: [],
      refresh: true,
      rootLoad: ""
    }
  },
  watch: {
    value(v) {
      !this.editing && !this.caches.includes(this.value) && this.getCacheOptions()
      this.dispatch('ElFormItem', 'el.form.change', [v]);
      setTimeout(() => this.$emit("update:name", this.$refs.areaCascader?.inputValue))
    },
    root() {
      if (this.value) {
        this.getCacheOptions()
      } else {
        setTimeout(() => {
          this.refresh = false
          this.$nextTick(() => this.refresh = true)
        }, 200)
      }
    },
    options: {
      handler() {
        this.$nextTick(() => this.$forceUpdate())
      }, deep: true, immediate: true
    }
  },
  computed: {
    fullArea() {
      const length = 12,
          getFull = v => this.rules.map(e => {
            let reg = new RegExp(`(\\d{${length-e}})\\d{${e}}`, 'g')
            return v?.replace(reg, '$1' + Array(e).fill(0).join(''))
          }).filter((e, i) => i <= this.getLevel(v))
      return this.multiple ? [this.value].flat()?.map(e => getFull(e)) : getFull(this.value)
    },
    props() {
      return {
        label: 'name',
        value: 'id',
        lazy: true,
        multiple: this.multiple,
        checkStrictly: true,
        emitPath: false,
        lazyLoad: (node, resolve) => {
          if (!(this.caches.includes(node.value) && this.fullArea.includes(node.value)) || node.loading) {
            if (node?.level == 0) {
              this.getRoots(resolve, "lazyLoad")
            } else if (node?.level > 0 && node.children?.length == 0) {
              let {id, leaf} = node.data
              leaf ? resolve([]) : this.getAreasByParent(id, resolve)
            } else resolve([])
          } else resolve([])
        }
      }
    },
    options() {
      return [...this.cacheOptions, ...this.filterData]
    },
    filtering() {
      let v = this.$refs?.areaCascader?.filtering
      if (!v) this.filterData = []
      return v
    }
  },
  methods: {
    getLevel(code) {
      let lv = -1
      this.rules.some((e, index) => {
        let reg = new RegExp(`0{${e}}$`, "g")
        if (reg.test(code)) {
          lv = index
          return true
        }
      })
      return lv
    },
    getRoots(resolve, from) {
      let url = '/admin/area/queryProvinceList'
      if (this.root) {
        url = "/admin/area/queryAreaByAreaid"
        if (this.rootLoad == this.root) {
          let waitRoots = (count = 0) => setTimeout(() => {
            if (this.roots.length > 0 || count == 5) {
              resolve(this.roots)
            } else waitRoots(++count)
          }, 500)
          return from == "lazyLoad" ? '' : waitRoots()
        }
      }
      this.rootLoad = JSON.parse(JSON.stringify(this.root))
      if (this.roots.some(e => e.id == this.root)) {
        resolve(this.roots)
      } else this.instance.post(url, null, {
        params: {id: this.root, from}, withoutToken: true
      }).then(res => {
        if (res?.data) {
          this.roots = [res.data].flat().map(e => ({...e, leaf: e.type == this.valueLevel}))
          resolve(this.roots)
        }
      })
    },
    getAreasByParent(id, resolve) {
      id && this.instance.post(this.action, null, {
        params: {id}, withoutToken: true,
      }).then(res => {
        if (res?.data) {
          resolve(res.data.map(e => ({...e, leaf: e.type == this.valueLevel})))
        }
      })
    },
    async getCacheOptions() {
      let finished = 0
      const hasChild = ids => ids?.some(e => this.fullArea?.flat()?.includes(e)),
          appendChildren = (area, resolve) => {
            let values = [this.value].flat()
            if (values.includes(area.id)) {
              finished++
              if (finished == values.length) {
                this.$emit("update:name", area.name)
                resolve()
              }
            } else this.getAreasByParent(area.id, data => {
              this.$set(area, "children", data)
              data.map(d => {
                this.caches.push(d.id)
                hasChild([d.id]) && appendChildren(d, resolve)
              })
            })
          }
      if (!!this.value?.toString()) {
        this.cacheOptions = []
        this.caches = []
        await this.getRoots(data => {
          this.caches = data?.map(e => e.id) || []
          new Promise(resolve => {
            if (hasChild(data.map(e => e.id))) {
              data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
            } else resolve()
          }).then(() => {
            this.cacheOptions = data
          })
        }, "initWithValue")
      } else if (!!this.root) {
        this.caches = []
        await this.getRoots(data => {
          this.caches = data?.map(e => e.id) || []
          new Promise(resolve => {
            if (hasChild(data.map(e => e.id))) {
              data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
            } else resolve()
          }).then(() => {
            this.cacheOptions = data
          })
        }, "initWithRoot")
      }
    },
    /**
     * 表单验证
     * @param componentName
     * @param eventName
     * @param params
     */
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    handleFindArea(areaName) {
      return new Promise(resolve => {
        this.instance.post("/admin/area/queryAreaByAreaName", null, {
          params: {areaName}
        }).then(res => {
          if (res?.data) {
            let range = new RegExp(`^${this.root.replace(/0+$/g, '')||'\d'}`)
            this.filterData = res.data.filter(e => !this.caches.includes(e.id) && range.test(e.id)).map(e => ({
              ...e,
              leaf: e.type == this.valueLevel
            }))
            resolve()
          }
        })
      })
    },
    handleAfterFilter(v) {
      this.$emit('select', this.$refs.areaCascader?.getCheckedNodes(true))
      this.$refs.areaCascader?.toggleDropDownVisible(false)
      if (!this.multiple) {
        if (this.filterData?.length > 0) {
          this.filterData = []
        }
        this.editing = this.caches.includes(v);
      }
    }
  },
  created() {
    setTimeout(() => {
      this.cacheOptions.length == 0 && this.getCacheOptions()
    })
  }
}
</script>

<style lang="scss" scoped>
.AiAreaGet {
  width: 100%;

  .el-cascader {
    width: 100%;
  }
}
</style>
<style lang="scss">
.popperSelectors {
  .el-cascader-menu__wrap {
    height: 300px;
  }
}
</style>
