<template>
    <!--todo-->
    <div :class="{ 'ele-form--inline': inline }">
<!--        <div v-for="(item) of layoutData" :class="item[0].iscontainer?'container':''">-->
<!--            {{item[0]}}-->
<!--        </div>-->
<!--        <template v-for="(formItem, field) of orderedFormDesc">-->
<!--            <el-col-->
<!--                    :key="field+ '-only'"-->
<!--                    v-bind="formItem._colAttrs"-->
<!--                    v-if="formItem._vif">-->
<!--                <slot v-if="formItem._type=='el-headers'"-->
<!--                      :data="formData[field]"-->
<!--                      :desc="formItem"-->
<!--                      :props="$props"-->
<!--                      :field="field"-->
<!--                      :formData="formData"-->
<!--                      :name="field"-->
<!--                      :disabled="formItem._disabled"-->
<!--                      :type="formItem._type"-->
<!--                      :options="formItem._options"-->
<!--                >-->
<!--                    <component-->
<!--                            :disabled="formItem._disabled"-->
<!--                            :readonly="readonly"-->
<!--                            :desc="formItem"-->
<!--                            :is="formItem._type"-->
<!--                            :options="formItem._options"-->
<!--                            :ref="field"-->
<!--                            :field="field"-->
<!--                            :formData="formData"-->
<!--                            @input="setValue(field, $event)"-->
<!--                            :value="formData[field]"-->
<!--                    />-->
<!--                </slot>-->
<!--            </el-col>-->
<!--        </template>-->
<!--        <div class="container">-->
<!--            <template v-for="(formItem, field) of orderedFormDesc">-->
<!--                <el-col-->
<!--                        :key="field+ '-only1'"-->
<!--                        v-bind="formItem._colAttrs"-->
<!--                        v-if="formItem._vif">-->
<!--                    <slot v-if="formItem._type!='el-headers'&&formItem._type!='el-footers'"-->
<!--                          :data="formData[field]"-->
<!--                          :desc="formItem"-->
<!--                          :props="$props"-->
<!--                          :field="field"-->
<!--                          :formData="formData"-->
<!--                          :name="field"-->
<!--                          :disabled="formItem._disabled"-->
<!--                          :type="formItem._type"-->
<!--                          :options="formItem._options"-->
<!--                    >-->
<!--                        <component-->
<!--                                :disabled="formItem._disabled"-->
<!--                                :readonly="readonly"-->
<!--                                :desc="formItem"-->
<!--                                :is="formItem._type"-->
<!--                                :options="formItem._options"-->
<!--                                :ref="field"-->
<!--                                :field="field"-->
<!--                                :formData="formData"-->
<!--                                @input="setValue(field, $event)"-->
<!--                                :value="formData[field]"-->
<!--                        />-->
<!--                    </slot>-->
<!--                </el-col>-->
<!--            </template>-->
<!--        </div>-->
<!--        <template v-for="(formItem, field) of orderedFormDesc">-->
<!--            <el-col-->
<!--                    :key="field"-->
<!--                    v-bind="formItem._colAttrs"-->
<!--                    v-if="formItem._vif">-->
<!--                <slot v-if="formItem._type=='el-footers'"-->
<!--                      :data="formData[field]"-->
<!--                      :desc="formItem"-->
<!--                      :props="$props"-->
<!--                      :field="field"-->
<!--                      :formData="formData"-->
<!--                      :name="field"-->
<!--                      :disabled="formItem._disabled"-->
<!--                      :type="formItem._type"-->
<!--                      :options="formItem._options"-->
<!--                >-->
<!--                    <component-->
<!--                            :disabled="formItem._disabled"-->
<!--                            :readonly="readonly"-->
<!--                            :desc="formItem"-->
<!--                            :is="formItem._type"-->
<!--                            :options="formItem._options"-->
<!--                            :ref="field"-->
<!--                            :field="field"-->
<!--                            :formData="formData"-->
<!--                            @input="setValue(field, $event)"-->
<!--                            :value="formData[field]"-->
<!--                    />-->
<!--                </slot>-->
<!--            </el-col>-->
<!--        </template>-->
        <template v-for="(formItem, field) of orderedFormDesc">
            <el-col
                    :key="field"
                    v-bind="formItem._colAttrs"
                    v-if="formItem._vif">
                <slot :data="formData[field]"
                      :desc="formItem"
                      :props="$props"
                      :field="field"
                      :formData="formData"
                      :name="field"
                      :disabled="formItem._disabled"
                      :type="formItem._type"
                      :options="formItem._options"
                >
                    <component
                            :disabled="formItem._disabled"
                            :readonly="readonly"
                            :desc="formItem"
                            :is="formItem._type"
                            :options="formItem._options"
                            :ref="field"
                            :field="field"
                            :formData="formData"
                            @input="setValue(field, $event)"
                            :value="formData[field]"
                    />
                </slot>
            </el-col>
        </template>

    </div>
</template>

<script>
  import responsiveMixin from '../../../src/mixins/responsiveMixin';
  import {isUnDef, is, castArray, isEmpty} from '../../../src/tools/utils';
  import localeMixin from '../../../src/mixins/locale';
  import {throttle} from 'throttle-debounce';

  const isNumber = require('is-number');

  export default {
    name: 'EleBox',
    // 响应式单独抽离出来作为mixin, 具体实现请到 responsiveMixin 中查看
    mixins: [responsiveMixin, localeMixin],
    model: {
      prop: 'formData',
      event: 'input'
    },
    provide() {
      return {
        EleForm: this
      };
    },
    data() {
      return {
        oldFormData: {},
        // 是否正在请求中
        innerIsLoading: false,
        // 内部请求出错
        innerFormError: {},
        layoutData: [],
        parentLayout: [],
        rows: 0
      };
    },
    computed: {
      computedIsShowCancelBtn() {
        if (is(this.isShowCancelBtn, 'Boolean')) {
          // 如果指定了, 则使用指定的值
          return this.isShowCancelBtn;
        } else {
          // 如果未指定, 根据 isDialog
          return this.isDialog;
        }
      },
      // 是否显示返回按钮(inline和layout模式下不同)
      computedIsShowBackBtn() {
        if (is(this.isShowBackBtn, 'Boolean')) {
          return this.isShowBackBtn;
        } else {
          return !(this.inline || this.isDialog);
        }
      },
      // 提交按钮默认值(inline和layout模式下不同)
      computedSubmitBtnText() {
        if (is(this.submitBtnText, 'String')) {
          return this.submitBtnText;
        } else {
          return this.inline;
        }
      },
      // 标签宽度(数字和字符串两种处理)
      computedLabelWidth() {
        if (isNumber(this.labelWidth)) {
          return this.labelWidth + 'px';
        } else {
          return this.labelWidth;
        }
      },
      // 表单错误信息
      formErrorObj() {
        return Object.assign({}, this.innerFormError, this.formError);
      },
      // 校检规则 (支持局部定义和全局定义)
      // 此函数即将局部定义转为全局定义
      computedRules() {
        return this.formDescKeys.reduce((rules, field) => {
          // 合并 (全局 和 局部) 的rules
          const formRules = castArray(this.rules[field]);
          const formItemRules = castArray(this.formDesc[field].rules);
          rules[field] = [...formRules, ...formItemRules];

          // 如果采用required, 则判断已有的规则有无, 如果没有, 则添加
          if (
            this.formDesc[field].required &&
            !rules[field].some(rule => rule.required)
          ) {
            rules[field].push({
              required: true,
              message: this.formDesc[field]._label
            });
          }
          return rules;
        }, {});
      },
      // formDesc的key
      formDescKeys() {
        return Object.keys(this.formDesc);
      },
      // 通过order数组排序后的formDesc
      orderedFormDesc() {
        console.log(this.layoutData);
        if (this.order && this.order.length > 0) {
          const orderedFormDesc = {};
          // 根据order遍历，先添加到orderedFormDesc的key在之后遍历的时候，会先遍历，从而实现排序的目的。
          this.order.forEach(field => {
            if (this.formDesc[field]) {
              orderedFormDesc[field] = this.formDesc[field];
            } else {
              throw new Error('order中定义的key在formDesc中不存在');
            }
          });
          // 如果key不在order数组的时候，按照原序添加到orderedFormDesc
          Object.keys(this.formDesc).forEach(field => {
            // 当key不在order数组的时候
            if (!orderedFormDesc[field]) {
              orderedFormDesc[field] = this.formDesc[field];
            }
          });
          return orderedFormDesc;
        } else {
          return this.formDesc;
        }
      }
    },
    props: {
      // 表单描述
      formDesc: {
        type: Object,
        required: true
      },
      // 表单数据
      formData: {
        type: Object,
        required: true
      },
      // 行内模式
      inline: {
        type: Boolean,
        default: false
      },
      // 表单自身属性
      formAttrs: Object,
      // 校检规则
      rules: {
        type: Object,
        default() {
          return {};
        }
      },
      // 模拟数据
      mock: {
        type: Boolean,
        default: false
      },
      // 提交状态
      isLoading: {
        type: Boolean,
        default: false
      },
      // 表单错误信息
      formError: Object,
      // 提交函数
      requestFn: Function,
      // 自定义表单按钮
      formBtns: Array,
      // 表单按钮大小
      formBtnSize: String,
      // 是否显示submit按钮
      isShowSubmitBtn: {
        type: Boolean,
        default: true
      },
      // 是否显示 cancel 取消按钮
      // 默认值: isDialog = true 时, 默认值为 true, 具体查看: computedIsShowCancelBtn
      isShowCancelBtn: {
        type: Boolean,
        default: null
      },
      // 是否显示back按钮
      // 默认值: 当 inline = true OR isDialog = true, 默认值为 false; 其它情况true. 具体请看计算属性: computedIsShowBackBtn
      isShowBackBtn: {
        type: Boolean,
        default: null
      },
      // 是否显示reset按钮
      isShowResetBtn: {
        type: Boolean,
        default: false
      },
      // 提交按钮文本
      // 默认值: 当 inline 为true时, 值为 '查询'; inline 为 false 时,  值为 '提交'. 具体请看计算属性: computedSubmitBtnText
      submitBtnText: {
        type: String,
        default: null
      },
      // 返回按钮
      backBtnText: String,
      // 重置按钮
      resetBtnText: String,
      // 取消按钮
      cancelBtnText: String,
      // 是否显示标签
      isShowLabel: {
        type: Boolean,
        default: true
      },
      // 标签宽度
      labelWidth: {
        type: [Number, String],
        default: 'auto'
      },
      // 全局禁用表单
      disabled: {
        type: Boolean,
        default: false
      },
      // 全局的readonly
      readonly: {
        type: Boolean,
        default: false
      },
      // 是否为弹窗
      isDialog: {
        type: Boolean,
        default: false
      },
      // 弹窗变量控制
      visible: {
        type: Boolean,
        default: false
      },
      // options 的请求方法
      optionsFn: Function,
      // 表单项顺序数组
      // 数组项为formDesc中的key
      order: {
        type: Array,
        default: () => []
      },
      // 是否显示错误后的 notify
      isShowErrorNotify: {
        type: Boolean,
        default: true
      }
    },
    watch: {
      disabled(val) {
        if (val) {
          this.$refs.form.clearValidate();
        }
      },
      // 处理options参数
      formDesc: {
        handler(desc) {
          if (desc) {
            Object.keys(desc).forEach(field => {
              // 当全局设置mock为true时, 所有子项都标记为true
              if (this.mock && isUnDef(desc[field].mock)) {
                desc[field].mock = true;
              }

              this.setDefaultvalue(desc[field], field);
              // layout值, 内部属性不显示
              if (desc[field].attrs.rows < 24 || this.rows < 24) {
                this.rows += desc[field].attrs.rows;
                this.parentLayout.push({
                  type: desc[field].type,
                  iscontainer: desc[field].iscontainer
                });
              } else if (desc[field].attrs.rows >= 24 || this.rows >= 24 || !desc[field].attrs.rows) {
                let typeValue = [];
                if (this.parentLayout.length) {
                  typeValue = this.parentLayout;
                } else {
                  typeValue.push({
                    type: desc[field].type,
                    iscontainer: desc[field].iscontainer
                  });
                }
                this.layoutData.push(typeValue);
                this.rows = 0;
                this.parentLayout = [];
              }
              desc[field]._colAttrs = this.getColAttrs(desc[field].attrs.rows);
              // 老数据, 用于options切换不同类型和type切换不懂类型时, 保留旧数据
              // 例如 原type为 switch, 后改为 input, 出现类型和值不兼容情况, 就需要保留原数据
              if (!desc[field]._oldValue) {
                desc[field]._oldValue = {};
              }

              this.setVif(desc[field], field);
              if (desc[field]._vif) {
                // 设置 options
                this.changeOptions(desc[field].options, field);
              }
            });
            // 检查联动
            this.checkLinkage();
          }
          this.$nextTick(() => {
            this.$refs.form && this.$refs.form.clearValidate();
          });
        },
        immediate: true
      },
      formErrorObj(obj) {
        // 后端异常的弹窗警告
        if (obj) {
          this.processError(obj);
        }
      },
      formData() {
        this.checkLinkage();
      }
    },
    methods: {
      checkLinkage() {
        if (this.checkVifFn) {
          this.checkLinkageFn();
        } else {
          this.checkLinkageFn = throttle(300, () => {
            const formDesc = this.formDesc;
            const formData = this.formData;
            Object.keys(formDesc).forEach(field => {
              const formItem = formDesc[field];
              // 1.设置 type
              let type = formItem.type;
              if (typeof formItem.type === 'function') {
                type = this.getFunctionAttr(formItem.type, field);
                if (formItem._type && formItem._type !== type) {
                  // 获取此类型的以前值
                  const newVal = formItem._oldValue['type-' + type] || null;
                  // 保存现在的数据作为老数据
                  this.formDesc[field]._oldValue['type-' + formItem._type] = formData[field];

                  // 类型改变, 则删除原数据
                  this.handleChange(field, newVal);
                  this.setDefaultvalue(this.formDesc[field], field);
                }
              } else {
                type = formItem.type;
              }

              // 2.触发 v-if 显示 / 隐藏
              this.setVif(formItem, field);
              // 3.触发 disabled 禁用 / 启用
              let disabled = null;
              if (typeof formItem.disabled === 'function') {
                disabled = this.getFunctionAttr(formItem.disabled, field);
              } else if (typeof formItem.disabled === 'boolean') {
                disabled = formItem.disabled;
              }

              // 4.动态属性
              const attrs = this.getDynamicAttr(formItem.attrs, field);

              // 5.动态 label
              const label = this.getDynamicAttr(formItem.label, field);

              // 6.动态 prop
              const prop = this.getDynamicAttr(formItem.prop, field);

              // 7.动态 optionsLinkageFields
              const optionsLinkageFields = castArray(
                this.getDynamicAttr(formItem.optionsLinkageFields, field)
              );

              this.$set(formItem, '_type', type);
              this.$set(formItem, '_disabled', disabled);
              this.$set(formItem, '_attrs', attrs);
              this.$set(formItem, '_label', label);
              this.$set(formItem, '_prop', prop);
              this.$set(formItem, '_optionsLinkageFields', optionsLinkageFields);

              // 4.重新获取 options
              if (formItem._vif) {
                this.changeOptions(formItem.options || formItem._options, field);
              }

              this.hidePrivateAttr(formItem);
            });
          });
          this.checkLinkageFn();
        }
      },
      setDefaultvalue(formItem, field) {
        let defaultValue =
          typeof formItem.default === 'function'
            ? formItem.default(this.formData)
            : formItem.default;
        // 默认值不为空  & (值为空 || 老值和当前值)
        if (!isEmpty(defaultValue) && isEmpty(this.formData[field])) {
          // 判断是否有格式化函数
          if (formItem.displayFormatter) {
            defaultValue = formItem.displayFormatter(defaultValue, this.formData);
          }

          this.handleChange(field, defaultValue);
        }
        this.$set(formItem, '_defaultValue', defaultValue);
      },
      // 获取col的属性(是否为inline模式)
      getColAttrs(layout) {
        return this.inline ? {span: layout || 6} : {md: layout || 24, xs: 24};
      },
      setVif(formItem, field) {
        let vif = true;
        if (typeof formItem.vif === 'function') {
          vif = Boolean(this.getFunctionAttr(formItem.vif, field));

          if (!vif) {
            // 如果隐藏, 则使用其默认值
            this.handleChange(field, formItem._defaultValue);
          }
        } else if (typeof formItem.vif === 'boolean') {
          vif = formItem.vif;
        }
        this.$set(formItem, '_vif', vif);
      },
      changeOptions(options, field) {
        if (options) {
          if (options instanceof Array) {
            // 当options为数组时: 直接获取
            this.setOptions(options, field);
          } else if (options instanceof Function) {
            // 当options为Promise时: 等待Promise结束, 并获取值
            if (
              (this.formDesc[field]._optionsIsPromise &&
                !this.isReloadOptions(field)) ||
              this.formDesc[field]._isLoadingOptions
            ) {
              return;
            }
            const res = this.getFunctionAttr(options, field);
            /*global Promise*/
            if (res instanceof Promise) {
              this.formDesc[field]._isLoadingOptions = true;
              this.formDesc[field]._optionsIsPromise = true;
            }
            // 当options为函数: 执行函数并递归
            this.changeOptions(res, field);
          } else if (options instanceof Promise) {
            options.then(options => {
              this.formDesc[field]._isLoadingOptions = false;
              this.changeOptions(options, field);
            });
          } else if (typeof options === 'string' && this.optionsFn) {
            // options为url地址
            this.changeOptions(this.optionsFn(options), field);
          } else {
            if (typeof options === 'string') {
              throw new TypeError(
                `options值为: ${options}`
              );
            } else {
              // 其他报错
              throw new TypeError(
                'options的类型不正确, options及options请求结果类型可为: 字符串数组, 对象数组, 函数和Promise或者URL地址, 当前值为: ' +
                options +
                ', 不属于以上四种类型'
              );
            }
          }
        } else {
          if (this.formDesc[field]._options) {
            // 如果new options为null / undefined, 且 old Options 存在, 则设置
            this.setOptions([], field);
          }
        }
      },
      // 获取动态属性
      getDynamicAttr(attr, field) {
        return typeof attr === 'function'
          ? this.getFunctionAttr(attr, field)
          : attr;
      },
      // 隐藏私有属性
      hidePrivateAttr(formItem) {
        if (process.env.NODE_ENV !== 'production') return;
        Object.keys(formItem).forEach(key => {
          if (key.startsWith('_')) {
            Object.defineProperty(formItem, key, {
              enumerable: false,
              configurable: true,
              writable: true,
              value: formItem[key]
            });
          }
        });
      }
      // 选择布局
    },
    mounted() {
    }
  };
</script>
