<template>
  <div v-loading="initLoading">
    <!-- form表单 -->
    <el-form ref="EasyDataForm" :style="{height: height}" class="ed-form--box" v-bind="getBindValue()">
      <slot name="formHeader" :model="formModel" :isSearchForm="isSearchForm" :controlInfos="controlInfos"></slot>
      <template v-for="(control, index) in controlInfos">
        <slot :control="control" :control-prop="getControlProp(control)" :isSearchForm="isSearchForm"
              :model="formModel" :name="control.controlName+'Wrapper'">
          <el-form-item v-if="showFormItem(control)" :prop="control.controlName">
            <!-- 表单项标签文本的内容 -->
            <template #label>
              <span :class="{'el-form-item-required': control.isRequired && control.controlType ==='upload'}">
                {{ control.label }}
                <el-tooltip v-if="showTooltip(control)" :content="getControlProp(control).tips" effect="dark" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
              </span>
            </template>

            <slot :control="control" :control-prop="getControlProp(control)"
                  :data="formModel[control.controlName]"
                  :isSearchForm="isSearchForm"
                  :model="formModel" :name="control.controlName">
              <FormItem :key="control.id" :control="control" :control-prop="getControlProp(control)"
                        :form-model="formModel" :isSearchForm="isSearchForm" @change="triggerControl">
                <!-- 计算公式 -->
                <template v-slot:append="{prop}">
                  <span v-if="showAppendSlots(control, prop)">
                    <!-- 选择弹窗 -->
                    <el-button v-if="control.controlType === 'popup'" @click="chosePopup(control)">选择</el-button>

                    <!-- 计算公式 -->
                    <el-button v-if="control.controlType === 'expression'" @click="clickExpression(control, prop)">计算公式</el-button>

                    <!-- 搜索项 -->
                    <el-button v-if="control.controlType === 'input' && prop?.isCallback" id="search">搜索项</el-button>
                  </span>
                </template>
              </FormItem>
            </slot>
          </el-form-item>
        </slot>

        <slot :control="control"
              :control-prop="getControlProp(control)"
              :model="formModel"
              :isSearchForm="isSearchForm"
              :name="'formItem'+index"></slot>
      </template>
      <slot name="formFooter" :model="formModel" :isSearchForm="isSearchForm" :controlInfos="controlInfos"></slot>
    </el-form>

    <!-- 弹窗组件 -->
    <ed-dialog ref="BasicDialog" :fullscreen="currentPopupComponent.controlProp?.isFullScreen" :visible="visible"
               :width="currentPopupComponent.controlProp?.width" title="选择弹窗"
               @close="handleClose"
               @ok="handleOk">
      <component v-bind:is="currentPopupComponent.component"
                 :ref="currentPopupComponent.ref" :controlProp="currentPopupComponent.controlProp"
                 :isSearchForm="isSearchForm">
      </component>
    </ed-dialog>
  </div>
</template>

<script>
import EasyDataMixins                                    from '../mixins/EasyDataMixins'
import Props, {FormProps}                                from './Props'
import useEmits                                          from "./hooks/useEmits";
import useForm                                           from "./hooks/useForm";
import useChosePopup                                     from "./hooks/useChosePopup";
import {getControlProp, setDefaultFormData, setFormData} from "../../utils/useBusiness";
import FormItem                                          from "./components/FormItem";
import {getVarType}                                      from "../../utils/utils";
import {TempType}                                        from "../../api/easyData";

/**
 * EasyData表单组件
 * - 示例
 *
 * ```vue
 * <easy-data-form ref="EasyDataForm" :dataSourceName="dataSourceName" :formData="formData" :label-position="labelPosition">
 * </easy-data-form>
 * ```
 * 详细用法参考下方链接
 *
 * [EasyDataForm组件使用示例](./web-comp/ExampleEasyDataFormCode.md)
 *
 * [](./EasyDataFormProps.md ':include')
 *
 * [](./ElFormItemProps.md ':include')
 *
 * [](./ElFormProps.md ':include')
 *
 */
export default {
  components: {FormItem},
  name: "EasyDataForm",
  props: Props,
  mixins: [EasyDataMixins, useEmits, useForm, useChosePopup],
  data() {
    return {
      // 转换this对象
      self: this,
      // 表单验证规则: object
      formRules: {},
      // 表单数据对象
      formModel: {},
      //popup弹窗 存储真实值
      popupFormModel: {},
      //文件列表 回显会用
      fileList: [],
      // 下拉树控件属性
      selectTree: {
        url: '/admin/base/tree/getData',
        treeData: [],
      },
      currentPopupComponent: {},
      // 选择弹窗緩存數據
      popupCache: [],
      // 显示弹窗
      visible: false,
      // loading
      initLoading: false
    }
  },
  watch: {
    controlConfig: {
      handler(newVal, oldVal) {
        if (newVal && Object.keys(newVal).length > 0) {
          const {controlInfos, controlProps, dataSource} = newVal
          // 解除js对象指向同一内存地址 JSON.parse(JSON.stringify(Object | Array))
          this.controlInfos = JSON.parse(JSON.stringify(controlInfos))
          this.controlProps = JSON.parse(JSON.stringify(controlProps))
          this.dataSource = JSON.parse(JSON.stringify(dataSource))
          this.$nextTick(() => {
            this.initFormControl()
          })
        }
      },
      deep: true,
      immediate: true
    },
    // 有数据源名称且未传入controlConfig时，自动发起请求
    dataSourceName: {
      deep: true,
      immediate: true,
      handler(newVal, oldVal) {
        if (newVal && newVal !== oldVal && (!this.controlConfig || Object.keys(this.controlConfig).length === 0)) {
          this.initControls().then(config => {
            this.initFormControl()
          })
        }
      }
    },
    formData: {
      deep: true,
      immediate: true,
      handler(newVal, oldVal) {
        if (newVal && newVal !== oldVal && this.controlInfos && Object.keys(this.controlInfos).length > 0) {
          this.watchFormData()
        }
      }
    },
    //没有传入formData 且传入查询参数时，自动查询数据
    loadQuery: {
      deep: true,
      immediate: true,
      handler(newVal, oldVal) {
        if (newVal && newVal !== oldVal && newVal.param && (this.formData === undefined || this.formData == null)) {
          this.initControls().then(config => {
            this.initFormControl()
          })
        }
      }
    }
  },
  computed: {
    showFormItem() {
      return (control) => {
        return Object.keys(this.formModel).length > 0 && control.isShow
      }
    },
    showTooltip() {
      return (control) => {
        return !this.isSearchForm && this.getControlProp(control).tips
      }
    }
  },
  methods: {
    /**
     * @name initFormControl
     * @description 初始化Form表单配置信息
     */
    async initFormControl() {
      await this.initRules()
      await this.watchFormData()
      this.initOk()
    },

    /**
     * @name watchFormData
     */
    watchFormData() {
      return new Promise(async (resolve, reject) => {
        // 默认值
        const defaultFormData = setDefaultFormData({
          controlInfos: this.controlInfos,
          controlProps: this.controlProps,
          dataSource: this.dataSource,
          isSearchForm: this.isSearchForm
        })

        this.popupFormModel = []
        // 传输值
        const controlFormData = setFormData(this.formData, this.controlInfos, this)

        //请求值
        let qFormData = {}
        if (this.hasObject(this.$props, 'loadQuery') && this.loadQuery && this.loadQuery.param) {
          const temp = await this.queryFormData()
          qFormData = setFormData(temp, this.controlInfos, this)
        }

        // 合并值
        this.formModel = Object.assign({}, defaultFormData, qFormData, controlFormData);

        let setTimeOut = setTimeout(() => {
          this.clearValidate()
          clearTimeout(setTimeOut)
        }, 100);

        this.initTriggerControl()
        resolve(true)
      })
    },

    /**
     * initFormDataAndControl
     */
    async initFormDataAndControl() {
      await this.initRules()
      await this.watchFormData()
      this.initOk()
    },

    /**
     * @private
     * @name getControlProp
     * @param control
     * @return {*}
     */
    getControlProp(control) {
      return getControlProp(control, this.controlProps)
    },

    /**
     * @private
     * @name initTriggerControl
     * @description
     */
    initTriggerControl() {
      Object.keys(this.formModel).map(f => {
        f !== 'id' && this.formModel[f] && this.triggerControl(this.formModel[f], this.controlInfos.find(i => i.controlName === f))
      })
    },

    /**
     * @private
     * @name showTriggerControl
     * @param t
     * @param isShow true: 显示控件    false: 隐藏控件
     */
    showTriggerControl(t, isShow) {
      this.controlInfos.map((control, index) => {
        if (control.controlName === t) {
          control.isShow = isShow
          this.$set(this.controlInfos, index, control)
        }
      })
    },

    /**
     * @private
     * @name triggerControl
     * @param value
     * @param control
     * @description
     */
    triggerControl(value, control) {
      if (control && this.triggerControls[control.controlName]) {
        const {dataSource} = this.getControlProp(control);
        if (dataSource && !this.isSearchForm) {
          let t_1 = {}
          dataSource.forEach((record) => {
            let {value: v, triggerControl: trigger} = record
            if (value.toString() === v.toString()) {
              if (trigger) {
                if (trigger.includes(',')) {
                  trigger.split(',').forEach((t) => {
                    this.showTriggerControl(t, true)
                    t_1[t] = []
                    t_1[t].push(t)
                  })
                } else {
                  this.showTriggerControl(trigger, true)
                  t_1[trigger] = []
                  t_1[trigger].push(trigger)
                }
              }
            } else {
              trigger && trigger.includes(',') ? trigger.split(',').forEach((t) => this.showTriggerControl(t, !!t_1[t])) : this.showTriggerControl(trigger, !!t_1[trigger])
            }
          })
        }
      }
      this.customizeChangeHandler(value, control)
    },

    /**
     * @name getBindValue
     * @returns {{}}
     * @description 获取Form组件属性
     */
    getBindValue() {
      let props = {}
      props = Object.assign({}, this.$attrs, this.$props)
      for (const key in props) {
        if (!this.hasObject(FormProps, key)) delete props[key]
      }
      props.model = this.formModel
      props.rules = this.formRules
      return Object.assign(props, this.$props.moduleProp)
    },

    /**
     * @name getFormActionSlot
     * @return {{form: {}}}
     * @description 按钮区slot传参
     */
    getFormActionSlot(data) {
      return {
        ...data,
        form: this.formModel,
        self: this.self
      }
    },

    /**
     * 是否显示插槽
     * @param control
     * @param prop
     * @return {boolean}
     */
    showAppendSlots(control, prop) {
      let arr = ['popup', 'upload', 'expression']
      if (control.controlType === 'input' && prop?.isCallback) {
        return true
      }
      return arr.includes(control.controlType)
    },
    /**
     * 获取表单的数据
     * @param form
     * @returns {any}
     */
    getFormModel(form) {
      let data = Object.assign({}, form)
      this.controlInfos.map(control => {
        let controlProp = this.getControlProp(control)
        if (controlProp && this.hasObject(controlProp, 'dataSource') && form[control.controlName]) {

          if (Array.isArray(form[control.controlName])) {
            data[control.controlName] = []
            form[control.controlName].map(item => {
              data[control.controlName].push(controlProp.dataSource.find(i => i.value === item))
            })
          } else {
            data[control.controlName] = controlProp.dataSource.find(i => i.value === form[control.controlName])
          }
        }
      })

      return data
    },

    /**
     * @param isValidate 是否验证后再返回数据
     * @param labelValue 只返回value形式数据 （反：返回labelValue形式数据） 默认只返回值
     * @param isOnlyControl
     * @returns {Promise<unknown>}
     * @description 获取当前表单数据
     */
    getFormData(isValidate = false, labelValue = false, isOnlyControl = false) {
      return new Promise(async (resolve, reject) => {
        try {
          isValidate ? await this.validateForm() : null;
          //查询参数
          let param = {}
          if (this.formModel.id) {
            param.id = this.formModel.id
          }
          param.keyInfo = null
          param.dataInfo = null
          let key = Object.assign(param.key ? param.key : {}, this.formModel, this.popupFormModel)
          let controlName = {}
          this.controlInfos.map(control => {
            controlName[control.controlName] = control
          })
          for (const item in key) {
            if (isOnlyControl) {
              if (controlName[item] === undefined) {
                delete key[item]
                continue
              }
            }
            //空数组 空对象则忽略
            if (key[item] === false) {
              // TO
            } else if (key[item] === undefined || key[item] === null || key[item] === "") {
              delete key[item]
            } else if (getVarType(key[item]) === 'array' && key[item].length === 0) {
              delete key[item]
            } else if (getVarType(key[item]) === 'object' && Object.keys(key[item]).length === 0) {
              delete key[item]
            }
          }
          param.key = this._convertData(key, labelValue);
          param.dataSourceName = this.dataSource.dataSourceName
          resolve(param)
        } catch (e) {
          reject(e)
        }
      })
    },
    /** 数据转换
     * labelValue 代码传入则肯定要转
     *
     */
    _convertData(form, labelValue) {
      let data = Object.assign({}, form)
      this.controlInfos.map(control => {
        let currentValue = data[control.controlName]
        let controlProp = this.getControlProp(control)
        //只操作有值且有视图属性的字段
        if (currentValue && controlProp && Object.keys(controlProp).length > 0) {
          let isLabelValue = controlProp.isLabelValue ? controlProp.isLabelValue : false;
          // 返回labelValue对象
          if (!this.isSearchForm && (labelValue === true || isLabelValue === true)) {
            if (control.controlType === 'cascader') {
              if (controlProp.multiple) {
                data[control.controlName] = currentValue.map(temp => this._addCascaderLabel(temp, controlProp.dataSource))
              } else {
                data[control.controlName] = this._addCascaderLabel(currentValue, controlProp.dataSource)
              }
            } else if (this.hasObject(controlProp, 'dataSource') && controlProp.dataSource.length > 0) {
              if (Array.isArray(currentValue)) {
                data[control.controlName] = currentValue.map(item => {
                  let {label, value} = controlProp.dataSource.find(i => i.value === item) || {}
                  return {label: label ? label : value, value}
                })
              } else {
                let {label, value} = controlProp.dataSource.find(i => i.value === currentValue) || {}
                data[control.controlName] = {label: label ? label : value, value}
              }
            }
          } else {
            //只存储值
            if (['select', 'checkbox'].includes(control.controlType) && getVarType(currentValue) === 'array') {
              data[control.controlName] = currentValue.join(",")
            } else if (control.controlType === 'popup' && this.isSearchForm) {
              if (currentValue instanceof Array) {
                let arr = []
                currentValue.forEach(item => {
                  arr.push(item.value)
                })
                data[control.controlName] = arr.join(",")
              } else {
                data[control.controlName] = currentValue.value
              }
            } else if (control.controlType === 'cascader') {
              if (controlProp.multiple) {
                data[control.controlName] = currentValue.map(i => {
                  if (getVarType(i) === 'array') {
                    return i.join("~")
                  }
                }).join(",")
              } else {
                data[control.controlName] = currentValue.join("~")
              }
            }
          }
        }
      })
      return data
    },
    _addCascaderLabel(data, dataSource) {
      if (data && dataSource) {
        let result = {value: data}
        let item = data.shift()
        let {label, value, children} = dataSource.find(i => i.value === item) || {}
        result.value = [item]
        if (label && value) {
          result.label = label
        } else {
          result.label = item
        }
        if (children) {
          let childrenObj = this._addCascaderLabel(data, children)
          result.label = result.label + "/" + childrenObj.label
          result.value = result.value.concat(childrenObj.value)
        }
        return result;
      }
    },
    /**
     * @name getType
     * @returns {string}
     * @description 设置当前模版类型
     */
    getType() {
      return TempType.save
    },

    /**
     * @name bindValuesChange
     * @param value
     * @param id
     * @param key
     * @returns {*}
     * @description 动态修改this对象属性值
     */
    bindValuesChange(value, id, key) {
      this.$set(this[key], id, value)
      return this[key]
    },

    /**
     * @name getControlValue
     * @param controlName
     * @returns {*}
     * @description 获取单项控件当前值
     */
    getControlValue(controlName) {
      return this.formModel[controlName]
    },

    /**
     * @name getPassValue
     * @param passDsName
     * @param controlName
     * @returns {*|string}
     */
    getPassValue(passDsName, controlName) {
      return this.passData[passDsName] ? this.passData[passDsName].keyInfoObject[controlName] : ''
    },

    // form-item项是否必填
    getFormItemRequired(control) {
      if (this.isSearchForm) {
        return false;
      }
      return control.isRequired
    },

    //计算是否展示 form-item
    isShowItem(control) {
      const notQuery = ['link', 'upload', 'imgPreview', 'expression', 'label', 'radio', 'checkbox', 'textarea']
      //搜索框且是查询项
      if (this.isSearchForm && control.isQuery && !notQuery.includes(control.controlType)) {
        return true;
      }
      //普通表单且是录入项
      else if (!this.isSearchForm && control.isInput) {
        return true;
      }
      return false;
    },

    /**清空数据及校验结果 */
    resetFields() {
      if (this.$refs.EasyDataForm)
        this.$refs.EasyDataForm.resetFields()
    },

    /** 清除校验结果 */
    clearValidate() {
      if (this.$refs.EasyDataForm)
        this.$refs.EasyDataForm.clearValidate()
    },
    clickFunc(e) {
      this.$refs.FormAction.clickFunc(e)
    },
  },
}
</script>

