/*
NOTE:	思路：
	使用场景
	1	筛选条件在页面内，一般解决方案就能解决
	2	筛选条件在弹层内，弹层未展示之前或者收起时，视图可能会被销毁
	3	表单填写

	应对情况2，把原mixins文件进行拆分，分为数据部分与视图部分，
		数据部分负责持久化逻辑	，可与视图mixins合并使用，也可依附于父级视图独立工作
		视图部分负责渲染与UI交互，并将接收到值反应到数据部分

		如果同时包含数据与视图，称为联合模式，UI方法会内部消化，而条件方法则会向外抛出事件，
		如果分开，称为独立模式，则UI方法会向外抛出事件，以通知mixins-data更改，而条件方法则会内部消化。

		下划线'_'开头的为内部使用, 非下划线开头为公开API

		原则上只要有在2场景中使用的情况 mixins-data mixins-view就最好分开引用
		但也可以引用在一起通过mixinsDataEnabled mixinsViewEnabled控制
		注意：这两个开关千万别乱用！！！

	应对情况3，将mixins-data, mixins-view同时引用即可
		此情况下，视图可以用v-model将UI输入映射致model, 也可通过setModelValue触发

	组件逻辑的核心在于解析元素实体依赖关系，并不一定局限于搜索功能！！！
	即任何需要体现依赖关系的地方都可以使用此组件
	TODO:	稳定后去除console.log
 */
// 22.2.22 hht 改进context动态引入
// noinspection JSUnresolvedFunction
const conditionModules = require.context('./condition-object', false, /\.js$/)
const conditions = conditionModules.keys().map(key => conditionModules(key).default)

export default {
  props: {
    localData: {
      // 22.2.22 hht 有的选择项并不需要从API获取，定义些属性，用于传入一些本地数据
      type: Object,
      default: _ => ({})
    },
    queryParams: {
      type: Object,
      default: _ => null
    },
    queryRules: {
      type: Object,
      default: _ => null
    },
    requireFields: {
      type: Array,
      default: _ => []
    }
  },
  data() {
    return {
      mixinsDataEnabled: true, // [联合/独立]模式识别标记 如非必要，不要改动这个值
      ignoreUnmatched: true, // 如果model中有未匹配到的，乎略与否。乎略的话仅打印提示
      fillRequiresWhenInit: false, // 如果model中有属性要求必填，初始化时是否自动填充

      dataCache: {}, // getList数据容器，可加快请求速度，如果想增加缓存影响，可从外部引入作用域更广的对象
      model: {}, // 条件模型，按需要按顺序传入，属性名对应condition-object.key
      requires: [], // [key1,key2]必选项，非必选项会添加'所有'; 另外会影响校验方法和事件触发
      rules: null, // 22.3.3 hht 可以尝试用标准的rules把requires替换掉
      formRef: 'form',
      conditionsOutput: [], // 用于展示的条件合集 [{key:'',value:'',title:'',list:[],error:''}]
      conditionsOutputTemporary: {}, // 每次需要重新构建条件时，存储条件数据的临时容器
      // 如果图方便，可以把所有支持的条件都加进来, 但要确保condition-key不冲突
      imports: conditions,
      useCache: true, // 缓存开关
      initChangedNotified: false, // 已通知标记
      modelTemporary: {}, // 临时存储属性，用于独立手动模式临时存储与还原

      // 注意 _开头的变量会被VUE忽略掉？！ 比如this._modelSnapshot -> undefined
      // TODO: _dependencyTree 其实不需要每次都全量组建，有优化空间
      _modelSnapshot: null, // {} model传入时的快照，用于重置功能
      _modelKeys: null, //[] 读取model属性名，对应imports中的condition-key，仅保留参与条件运算的key，允许model挂载一些其它属性
      _dependencyTree: null, // {key1:[childKeys],key2:[childKeys]} // 因为条件影响向下传递，只需要两级，递归算法会读取所有依赖
      _restoreProcessing: false // 正处于还原过程
    }
  },
  computed: {
    // 22.3.2 hht 将校验收拢到el-form，为尽量兼容历史写法，先保留requires属性
    finalRules() {
      const rules = this.rules || {}
      this.requires.forEach(key => {
        const condition = this.imports.find(c => c.key == key)
        if (condition) {
          rules[key] = [{
            required: true,
            message: `${condition.title}必选`
          }]
        }
      })
      return rules
    }
  },
  watch: {
    model(val) {
      console.log('_initialization from watch', val)
      this._initialization(val)
    },
    queryParams: {
      immediate: true,
      handler: function(val) {
        console.log('model assign from watch', val)
        this.model = val
      }
    },
    queryRules: {
      immediate: true,
      handler: function(val) {
        console.log('rule assign from watch', val)
        this.rules = val
      }
    },
    requireFields: {
      immediate: true,
      handler: function(val) {
        console.log('requires assign from watch', val)
        this.requires = val
      }
    }
  },
  methods: {
    // 联合模式：对外抛出事件(这些事件可能会重合, 请按需监听) == 独立模式：按需重写对应Action
    // singleChanged==singleChangedAction单项变化, 不考虑校验，改值就触发
    // changed==changedAction有效变化,非初始化的有效变化
    // invalidChanged==invalidChangedAction无效变化，非初始化的无效变化
    // initChanged==initChangedAction初始化时的有效变化, 因为有必填或必选项, 借这个方法可以把触发外部查询的时机内聚到该组件内
    // initInvalidChanged==initInvalidChangedAction初始化时的未通过校验的事件
    // 公共API
    isCombineMode() {
      // 此方法请与mixins-view保持一致
      return this.mixinsViewEnabled && this.mixinsDataEnabled
    },
    setModelValue(key, newVal) {
      // reset
      this.conditionsOutputTemporary = {}
      // trigger
      this.model[key] = newVal
      this._rebuildConditionFrom(key, false).then(() => this._triggerEventIfNeed())
    },
    valid() {
      return this._validAndHandleErrors()
    },
    reset() {
      const mSnapshot = this._modelSnapshot
      this.model = mSnapshot
      this._modelSnapshot = null
      this.modelTemporary = null
    },
    resetInitNotify() {
      // init方法会重新触发initChanged事件
      this.initChangedNotified = false
    },
    resetAll(clearCache = true) {
      // 同页面场景切换可以先调用此方法，再给model赋值，触发初始化
      if (clearCache) this.clearCache()
      this.initChangedNotified = false
      this._modelSnapshot = null
      this.modelTemporary = null
      this.conditionsOutput = []
    },
    clearCache(key = null) {
      if (!key) {
        this.dataCache = {}
      } else {
        // 清除某个分类的缓存
        Object.keys(this.dataCache).forEach(cacheKey => {
          if (cacheKey.endsWith(key)) {
            delete this.dataCache[cacheKey]
          }
        })
      }
    },
    backup() {
      // 独立模式弹层手动搜索，建立快照
      if (this.model) {
        this.modelTemporary = this.deepClone(this.model)
        console.log('建立数据备份', this.modelTemporary)
      }
    },
    restore() {
      // 独立模式弹层手动搜索，从快照还原
      if (this.modelTemporary) {
        console.log('将还原数据备份FROM-TO', this.model, this.modelTemporary)
        this._restoreProcessing = true
        this.model = this.modelTemporary
      }
    },
    anyConditionFired() {
      return this._modelKeys?.some(k => this.model[k])
    },
    getConditionLabel(key, code) {
      let condition = this.conditionsOutput.find(c => c.key == key)
      return condition?.list.find(i => i.code == code)?.label
    },
    isRequired(key) {
      return this.finalRules.hasOwnProperty(key)
    },
    // 公共API 重写部分 供独立模式挂载主体重写
    singleChangedAction(model, key) {
      // for override
    },
    changedAction(model) {
      // for override
    },
    invalidChangedAction(errors, model) {
      // for override
    },
    initChangedAction(model) {
      // for override
    },
    initInvalidChangedAction(errors, model) {
      // for override
    },

    // 内部方法
    _initialization(model) {
      if (!model) return
      console.log('_initialization switcher mixinsDataEnabled', this.mixinsDataEnabled)
      if (!this.mixinsDataEnabled) return
      this._modelKeys = this._readEffectiveModelKeys(model)
      if (!this._modelKeys.length) return // 无效传值
      // 重置相关变量
      console.log('_initialization begin', this._modelKeys)
      // {...obj}不使用浅表拷贝是因为可能存在数组属性
      if (!this._modelSnapshot) {
        this._modelSnapshot = this.deepClone(model)
        console.log('_initialization _modelSnapshot', this._modelSnapshot)
      }
      this._dependencyTree = {}
      // 读取属性名 建立关系树
      const buildDependencySuccess = this._rebuildDependencyTree()
      if (!buildDependencySuccess) return
      console.log('build dependency tree success:', this._dependencyTree)
      this._rebuildAllConditions().then(() => this._triggerEventIfNeed())
    },
    _readEffectiveModelKeys(model) {
      let allKeys = Object.keys(model)
      if (!allKeys.length) return allKeys
      if (!this.ignoreUnmatched) return allKeys

      let unmatched = []
      allKeys.forEach(key => {
        if (!this.imports.some(i => i.key == key)) {
          unmatched.push(key)
        }
      })
      if (unmatched.length) {
        console.log(`警告：因为配置为乎略未匹配条件，[${unmatched}]将不做处理`)
        console.log(`乎略前所有条件：${allKeys}`)
        unmatched.forEach(k => allKeys.remove(k))
        console.log(`乎略后有效条件：${allKeys}`)
      }
      return allKeys
    },
    _rebuildDependencyTree() {
      const unmatchedKeys = []
      const conflictKeys = []
      this._modelKeys.forEach(key => {
        const condition = this.imports.find(c => c.key == key)
        if (condition) {
          // 递归解析key相关的依赖项
          const dependencyCheckLink = []
          const buildNodeSuccess = this._buildDependencyNode(condition.key, dependencyCheckLink)
          if (!buildNodeSuccess) {
            conflictKeys.push(`{${key}:${dependencyCheckLink}}`)
          }
          return
        }
        unmatchedKeys.push(key)
      })
      if (unmatchedKeys.length) {
        console.log(`发现条件实现缺失${unmatchedKeys}，请检查condition-object实现`)
        this.msgError(`imports中未找到[${unmatchedKeys}]`)
      } else if (conflictKeys.length) {
        this.msgError(`[${conflictKeys}]发现依赖冲突`)
      }
      return !unmatchedKeys.length && !conflictKeys.length
    },
    _buildDependencyNode(key, dependencyCheckLink, fromKey) {
      if (dependencyCheckLink.includes(key)) {
        console.log(`发现循环依赖，请检查condition-object.${key}.independentKeys：`, key, dependencyCheckLink, fromKey)
        return false
      } // 循环依赖
      dependencyCheckLink.push(key)
      if (!this._modelKeys.includes(key)) {
        console.log(`发现依赖缺省，请检查model.${key}是否存在：`, key, dependencyCheckLink, fromKey)
        return false
      }
      let selfChildren = this._dependencyTree[key] || []
      if (fromKey && !selfChildren.includes(fromKey)) selfChildren.push(fromKey)
      this._dependencyTree[key] = selfChildren
      const matchCondition = this.imports.find(c => c.key == key)
      if (!matchCondition) {
        console.log('发现依赖中断，请检查condition-object：', key, dependencyCheckLink, fromKey)
        return false
      } // 依赖中断
      let ancestorChecked = true
      matchCondition.dependentKeys.forEach(ancestorKey => {
        const result = this._buildDependencyNode(ancestorKey, dependencyCheckLink, key)
        ancestorChecked = ancestorChecked && result
      })
      return ancestorChecked
    },
    async _rebuildAllConditions() {
      // 从依赖树的根结点开始创建
      this.conditionsOutputTemporary = {}
      const rootKeys = Object.keys(this._dependencyTree)
      for (const key of rootKeys) {
        await this._rebuildConditionFrom(key, true)
      }
    },
    async _rebuildConditionFrom(key, includesSelf) {
      // 从某个属性开始构建条件
      if (includesSelf) {
        const condition = this.imports.find(c => c.key == key)
        const withoutAll = this.isRequired(key) || condition.disableAllByForce
        const hasData = this.conditionsOutputTemporary[key]?.list.length > (withoutAll ? 0 : 1)
        if (hasData) return console.log('_rebuildConditionFrom ignored', key)
        console.log('_rebuildConditionFrom previous', key)
        await this._loadConditionData(condition)
        console.log('_rebuildConditionFrom suffix', key)
      }
      const dependencyKeys = this._dependencyTree[key]
      for (const dependencyKey of dependencyKeys) {
        await this._rebuildConditionFrom(dependencyKey, true)
      }
    },
    async _loadConditionData(condition) {
      const { cacheKey, param } = this._getDependentParamAndCacheKey(condition)
      if (!condition.isDependencyReady(param)) return this._handleConditionData(condition, [], cacheKey)
      let list = this.dataCache[cacheKey]
      if (!list) list = await condition.getList(param, this)
      this._handleConditionData(condition, list, cacheKey)
    },
    _handleConditionData(condition, list, cacheKey) {
      console.log('_handleConditionData', condition, list, cacheKey)
      // 只缓存有数据的情况
      if (this.useCache && list?.length && !condition.neverCache) this.dataCache[cacheKey] = list
      // 处理显示对象
      /* TODO:  现在还不确定一个条件同时依赖多个条件的情况是否能正常工作，
                理论上一个依赖多个可以强制转换成单依赖，因为交互时用户也无法同时点两个条件 */
      let displayList = list.map(item => ({
        code: condition.getCode(item),
        label: condition.getLabel(item),
        _raw: item // 原数据引用提供一份
      }))
      // 追加'所有'
      const isConditionRequired = this.isRequired(condition.key)
      if (!isConditionRequired && !condition.disableAllByForce) {
        displayList.unshift({ code: condition.allCode, label: condition.allLabel })
      }
      // 校验当前值还是否有效
      const currentVal = this.model[condition.key]
      let displayValue = condition.transferToEffectiveDisplayValue(currentVal, displayList)
      if (!condition.codeEquals(currentVal, displayValue)) {
        // 初始化过程和还原过程不改值
        if (isConditionRequired) {
          if (this.initChangedNotified || this.fillRequiresWhenInit) {
            if (!this._restoreProcessing) {
              this.model[condition.key] = displayValue
            }
          }
        } else {
          this.model[condition.key] = displayValue
        }
      }
      // 包装并保存
      const wrappedList = {
        key: condition.key,
        value: this.model[condition.key],
        title: condition.title,
        list: displayList,
        template: condition.template
      }
      this.conditionsOutputTemporary[condition.key] = wrappedList
    },
    _getDependentParamAndCacheKey(condition) {
      let cacheKey = '',
        param = {}
      condition.dependentKeys.forEach(key => {
        const currentVal = this.model[key]
        cacheKey += `${key}_${currentVal}$`
        param[key] = currentVal
      })
      cacheKey += condition.key
      if (condition.modelAsParam) {
        param = this.model
      }
      return {
        cacheKey,
        param
      }
    },
    _mergeConditionsWithTemporary() {
      const finalConditions = []
      for (const key of this._modelKeys) {
        const list = this.conditionsOutputTemporary[key] ||
          this.conditionsOutput.find(c => c.key == key)
        if (!list) {
          if (this.model[key]) this.model[key] = '' // clear invalid value
        } else {
          const condition = this.imports.find(c => c.key == key)
          const withoutAll = this.isRequired(key) || condition.disableAllByForce
          const hasData = list.list.length > (withoutAll ? 0 : 1)
          if (hasData || this.isRequired(key)) {
            finalConditions.push(list)
          }
        }
      }
      this.conditionsOutput = finalConditions
    },
    _triggerEventIfNeed() {
      // 整理最终的conditionOutput
      this._mergeConditionsWithTemporary()
      // 校验&外抛事件
      this._restoreProcessing = false
      this.valid().then(() => {
        if (this.initChangedNotified) {
          this._triggerChangedEvent(this.model)
        } else {
          this._triggerInitChangedEvent(this.model)
          this.initChangedNotified = true
        }
      }).catch(errors => {
        if (this.initChangedNotified) {
          this._triggerInvalidChangedEvent(errors, this.model)
        } else {
          this._triggerInitInvalidChangedEvent(errors, this.model)
          this.initChangedNotified = true
        }
      })
    },
    _triggerSingleChangedEvent(model, key) {
      console.log('_triggerSingleChangedEvent', model, key, this._initKeys)
      if (this.isCombineMode()) {
        this.$emit('singleChanged', model, key)
      } else {
        this.singleChangedAction(model, key)
      }
    },
    _triggerChangedEvent(model) {
      console.log('_triggerChangedEvent validChanged', model)
      if (this.isCombineMode()) {
        this.$emit('changed', model)
      } else {
        this.changedAction(model)
      }
    },
    _triggerInvalidChangedEvent(errors, model) {
      console.log('_triggerInvalidChangedEvent invalidChanged', model)
      if (this.isCombineMode()) {
        this.$emit('invalidChanged', errors, model)
      } else {
        this.invalidChangedAction(model)
      }
    },
    _triggerInitChangedEvent(model) {
      console.log('_triggerInitChangedEvent initValidChanged', model)
      if (this.isCombineMode()) {
        this.$emit('initChanged', model)
      } else {
        this.initChangedAction(model)
      }
    },
    _triggerInitInvalidChangedEvent(errors, model) {
      console.log('_triggerInitInvalidEvent initInvalidChanged', model)
      if (this.isCombineMode()) {
        this.$emit('initInvalidChanged', errors, model)
      } else {
        this.initInvalidChangedAction(errors, model)
      }
    },
    _validAndHandleErrors() {
      const formRef = this.$refs[this.formRef]
      if (!formRef || !formRef.validate) {
        console.log('警告：您未正确配置表单引用，组件校验工作中断。参见`this.formRef`')
        return Promise.resolve()
      }
      // return formRef.validate() 这种方式没有外抛异常明细
      return new Promise((resolve, reject) => {
        formRef.validate((valid, errors) => {
          if (valid) resolve(valid)
          else reject(errors) // 经这一步转化，可以外抛errors
        })
      })
    }
  }
}
