<template>
    <form-item
        v-if="!usedOption.hidden"
        :form="form"
        :key="key"
        :controlKey="controlKey"
        :value="value"
        :tag-name="tagName"
        :option="usedOption"
        @input="valueChange"
    ></form-item>
</template>

<script>
import { isPromise, deepMerge, deepMergeWithoutNone } from '../utils/tools';
import ajax from '../utils/ajax';
import bus from '../utils/bus';
import { registedComponentMapping } from '../utils/registComponents.js';
import FormItem from './form-item.vue';
const defaultFetchPath = ['component.attrs.data', 'data'];
const arrToObj = 'arrToObj';
export default {
  props: {
    form: {
      type: Object,
      default: () => {
        return {};
      }
    },
    controlKey: String,
    option: Object,
    pattern: String,
    layout: Object,
    formItem: Object,
    component: Object
  },
  data() {
    return {
      key: Math.random(), // 随机数keu，用于强制item-control更新
      type: '', // 组件类型
      tagName: '', // 组件名称
      value: this.form[this.controlKey], // 值
      updateOptionTimer: undefined, // 合并配置防抖定时器
      valueChangeTimer: undefined, // 组件值改变时的防抖定时器
      usedOption: {}, // 实际使用的配置
      stageOption: {}, // 暂存的配置
      /**
             * eg:  dependency: [
             *          {
             *              path: 'component.attrs.data', // 映射字段路径
             *              triggerFields: [], // 关键字段值变化时触发（对应配置项的key）
             *              getValue: () => {}, // 计算值的方法
             *              transferModel: 'fixed' // 结果的转换方式，fixed或者underfined，不需要改变，arrToObj, 将数组中的每一项作为新对象的key，并赋值为true
             *          }
             *     ],
             */
      dependency: [], // 表单依赖关系
      /**
             * fetch: [
             *     {
             *         id: '', // 数据源唯一键，
             *         path: [], // fetch.data映射的字段
             *         resetValue: '', // 重设值
             *         resetFlag: 是否需要重设值， // 默认为false
             *         triggerFields: [], // fetch.data依赖的属性
             *         getValue: undefined, // fetch方法
             *     },
             * ],
             */
      fetch: [], // 数据源管理
      fetchData: [], // 数据源请求的结果,对应fetch中的数据源

      patternOption: {}, // 不同模式的配置，数据格式同stageOption
      patternDependency: [], // 指定模式配置项，数据格式同dependency
      patternFetch: [], // 指定模式数据源，数据格式同fetch
      patternFetchData: [] // 指定模式数据源请求结果,对应patternFetch中的数据源，数据格式同fetchData
    };
  },
  components: {
    FormItem
  },
  watch: {
    pattern() {
      // 清除指定模式数据
      this.patternOption = {};
      this.patternDependency = [];
      this.patternFetch = [];
      this.patternFetchData = [];
      const hasPattern = this.testPatternKey(this.pattern);

      // 当pattern为空或者错误时，代表切换为基础模式，
      if (!hasPattern) {
        // 因基础配置和指定模式配置完全分离，切换回基础模式，只需要清除指定模式配置即可
        this.doUpdateOptions({}, true, false);
      } else {
        // 处理配置项，暂忽略有依赖关系的配置，并记录依赖关系
        // 记录的依赖关系需要和普通的依赖做区分
        this.handleOption(this.option[this.pattern], true);

        // 处理fetch
        this.handleFetch(this.option[this.pattern], true);
        this.toUpdateOption(this.form, '', true, true);
        this.doUpdateOptions({}, true, false);
      }
    }
  },
  created() {
    try {
      const type = this.option.type || this.option.component.type;
      this.type = type;
      this.tagName = registedComponentMapping[type]
        ? registedComponentMapping[type].tagName
        : type;
      // const value = this.form[this.controlKey];
      // this.value =
      //           value !== undefined && value !== null
      //             ? value
      //             : registedComponentMapping[type]
      //               ? registedComponentMapping[type].value
      //               : undefined;
      // 表单域控件初始化
      this.init();
    } catch (error) {
      console.error(error);
    }
  },
  mounted() {
    bus.$on('valueChange', (form, changedKey) => {
      if (this.valueChangeTimer) {
        clearTimeout(this.valueChangeTimer);
      }
      // 增加防抖机制，防止触发多次
      this.valueChangeTimer = setTimeout(() => {
        const pattern = this.testPatternKey(this.pattern);
        // 清除需要更新的数据源结果缓存
        // 因为部分属性需要依赖数据源结果，清除了数据源结果就可以避免属性的冗余更新
        // 也可以避免属性使用历史数据更新
        this.clearFetchData(changedKey, pattern);
        this.toUpdateOption(form, changedKey, false, pattern);
      }, 50);
    });
  },
  methods: {
    valueChange(value) {
      // 会因为value只已经改变，而表单的form值未及时更新，导致验证出错
      // 如果this.value放在定时器中执行，会导致延迟，已经连续输入的无效
      this.value = value;
      this.$emit('valueChange', value);
    },
    /**
         * 配置项初始化
         */
    init() {
      // 清除之前的数据
      this.usedOption = {};
      this.dependency = [];
      this.fetch = [];
      this.fetchData = [];

      // 清除指定模式数据
      this.patternOption = {};
      this.patternDependency = [];
      this.patternFetch = [];
      this.patternFetchData = [];

      // 处理配置项，暂忽略有依赖关系的配置，并记录依赖关系
      this.handleOption(this.option);

      // 处理fetch
      this.handleFetch(this.option);

      const hasPattern = this.testPatternKey(this.pattern);
      if (hasPattern) {
        // 处理配置项，暂忽略有依赖关系的配置，并记录依赖关系
        this.handleOption(this.option[this.pattern], true);
        // 处理fetch
        this.handleFetch(this.option[this.pattern], true);
      }

      // 这里强制更新一次
      this.toUpdateOption(this.form, '', true, hasPattern);
      this.doUpdateOptions({}, true, hasPattern);
    },
    /**
         * 处理fetch
         * fromPattern: 来源，是否为指定模式配置
         */
    handleFetch(option, fromPattern = false) {
      if (!Object.prototype.hasOwnProperty.call(option, 'fetch')) return;

      const fetch = Array.isArray(option.fetch) ? option.fetch : [option.fetch];

      for (const item of fetch) {
        // 如果不存在mappingFileds或者mappingFileds为空，使用默认值
        const mappingFileds =
                    item.mappingFileds &&
                    Array.isArray(item.mappingFileds) &&
                    item.mappingFileds.length > 0
                      ? item.mappingFileds
                      : defaultFetchPath;
        if (!item.data) continue;
        let resetFlag = false;
        if (Object.prototype.hasOwnProperty.call(item, 'resetValue')) {
          resetFlag = true;
        }
        this.addFetchRules(
          item.id,
          item.data,
          mappingFileds,
          item.dependency,
          item.resetValue,
          resetFlag,
          fromPattern
        );
      }
    },
    /**
         * 新增数据源请求规则
         */
    addFetchRules(
      id,
      value,
      path = [],
      triggerFields = [],
      resetValue,
      resetFlag = false,
      fromPattern
    ) {
      let getValue;
      if (
        value.constructor !== Function &&
                Object.prototype.constructor.call(value, '_xhr') &&
                value._xhr
      ) {
        if (value.type.toUpperCase() === 'POST') {
          getValue = ajax.post(value.url, value.data);
        } else {
          getValue = ajax.get(value.url, value.data);
        }
      } else {
        getValue = value;
      }
      if (fromPattern) {
        this.patternFetch.push({
          id,
          path: Array.isArray(path) ? path : path.split(','), // fetch.data映射的字段
          resetValue, // 重设值
          resetFlag, // 是否需要重设
          triggerFields, // fetch.data依赖的属性
          getValue // fetch方法
        });
      } else {
        this.fetch.push({
          id,
          path: Array.isArray(path) ? path : path.split(','), // fetch.data映射的字段
          resetValue, // 重设值
          resetFlag, // 是否需要重设
          triggerFields, // fetch.data依赖的属性
          getValue // fetch方法
        });
      }
    },
    /**
         * 根据变化的key，清除相关数据源结果
         */
    clearFetchData(changedKey, fromPattern) {
      const patternFetchIds = []; // 指定模式数据源的id
      if (fromPattern) {
        this.patternFetch.forEach((item, index) => {
          if (item.triggerFields.includes(changedKey)) {
            if (item.id) patternFetchIds.push(item.id);
            this.patternFetchData[index] = undefined;
          }
        });
      }
      this.fetch.forEach((item, index) => {
        if (item.triggerFields.includes(changedKey) && !patternFetchIds.includes(item.id)) {
          this.fetchData[index] = undefined;
        }
      });
    },
    /**
         * 更新数据源结果缓存
         */
    updateFetchData(data, index, fromPattern) {
      if (fromPattern) {
        this.patternFetchData[index] = data;
      } else {
        this.fetchData[index] = data;
      }
    },
    /**
         * 处理配置项，记录依赖关系
         * fromPattern: 来源，是否为指定模式配置
         */
    handleOption(option, fromPattern = false) {
      let control = deepMergeWithoutNone(true, {
        key: option.key
      }, {
        component: {
          attrs: registedComponentMapping[this.type].attrs,
          style: registedComponentMapping[this.type].style
        }
      }, {
        layout: this.layout,
        formItem: this.formItem,
        component: this.component
      });

      // 需要将填写的配置项转为jsx可识别的配置

      // 根据dom划分的大类
      const domCategoryList = ['layout', 'formItem', 'component'];

      // 基础属性，对应dom相应的属性，值不需要转换
      const basicFieldList = ['attrs', 'props', 'style'];

      // 需要被排除的属性名
      const excludedFieldList = ['value', 'rules'];

      // 需要转换最终结果的属性，直接计算该属性即可，采用arrToObj规则
      const arrToObjFieldList = ['class'];

      // 直接属性，直接挂载在配置项上，attr：属性名, path：属性路径,多级使用/分割
      const directFieldList = [
        { attr: 'label', path: ['formItem', 'attrs', 'label']},
        { attr: 'data', path: ['component', 'attrs', 'data']},
        { attr: 'disabled', path: ['component', 'attrs', 'disabled']},
        { attr: 'hidden', path: ['hidden']},
        { attr: 'rules', path: ['rules']},
        { attr: 'span', path: ['layout', 'attrs', 'span']},
        { attr: 'placeholder', path: ['component', 'attrs', 'placeholder']}
      ];

      for (let category of domCategoryList) {
        control[category] = control[category] || {};
        for (let field of basicFieldList) {
          control[category][field] = control[category][field] || {};
          if (option[category] && option[category][field]) {
            for (let attr in option[category][field]) {
              if (excludedFieldList.includes(attr)) continue;
              // 如果有_function属性，说明需要根据表单值计算属性
              if (option[category][field][attr]._function) {
                if (typeof option[category][field][attr].value === 'string') {
                  // 函数字符串
                  // 添加规则
                  this.addRules(
                    new Function('form', option[category][field][attr].value),
                    [category, field, attr],
                    option[category][field][attr].dependency,
                    fromPattern
                  );
                } else {
                  // 添加规则
                  this.addRules(
                    option[category][field][attr].value,
                    [category, field, attr],
                    option[category][field][attr].dependency,
                    fromPattern
                  );
                }
                control[category][field][attr] = option[category][field][attr].defaultValue;
              } else if (option[category][field][attr].constructor === Function) {
                // 添加规则
                this.addRules(
                  option[category][field][attr],
                  [category, field, attr],
                  [],
                  fromPattern
                );
              } else {
                control[category][field][attr] = option[category][field][attr];
              }
            }
          }
        }

        // 需要转换最终结果的属性
        for (let field of arrToObjFieldList) {
          if (option[category] && option[category][field]) {
            // 如果有_function属性，说明需要根据表单值计算属性
            if (option[category][field]._function) {
              if (option[category][field].value === 'string') {
                // 函数字符串
                // 添加规则
                this.addRules(
                  new Function('form', option[category][field].value),
                  [category, field],
                  option[category][field].dependency,
                  fromPattern,
                  arrToObj
                );
              } else {
                // 添加规则
                this.addRules(
                  option[category][field].value,
                  [category, field],
                  option[category][field].dependency,
                  fromPattern,
                  arrToObj
                );
              }
              control[category][field] = option[category][field].defaultValue;
            } else if (option[category][field].constructor === Function) {
              // 添加规则
              this.addRules(
                option[category][field],
                [category, field],
                [],
                fromPattern,
                arrToObj
              );
            } else {
              const usedObj = {};
              for (let responseItem of option[category][field]) {
                usedObj[responseItem] = true;
              }
              control[category][field] = usedObj;
            }
          }
        }
      }

      // 直接属性
      for (let { attr, path } of directFieldList) {
        if (Object.prototype.hasOwnProperty.call(option, attr)) {
          // 如果有_function属性，说明需要根据表单值计算属性
          if (option[attr]._function) {
            if (typeof option[attr].value === 'string') {
              // 函数字符串
              // 添加规则
              this.addRules(
                new Function('form', option[attr].value),
                path,
                option[attr].dependency,
                fromPattern
              );
            } else {
              // 添加规则
              this.addRules(
                option[attr].value,
                path,
                option[attr].dependency,
                fromPattern
              );
            }
            control = deepMerge(true, control, this.transferPathToObject(path.join('.'), option[attr].defaultValue));
          } else if (option[attr].constructor === Function) {
            // 添加规则
            this.addRules(option[attr], path, [], fromPattern);
          } else {
            control = deepMerge(true, control, this.transferPathToObject(path.join('.'), option[attr]));
          }
        }
      }
      // 执行更新配置
      this.doUpdateOptions(control, false, fromPattern);
    },
    /**
         * 添加依赖关系
         */
    addRules(
      value,
      path = [],
      triggerFields = [],
      fromPattern = false,
      transferModel = 'fixed'
    ) {
      path = Array.isArray(path) ? path : path.split(',');
      // 需要根据配置项来源（是否指定了模式），分别存储
      if (fromPattern) {
        this.patternDependency.push({
          path: path.join('.'), // 映射字段路径
          triggerFields, // 关键字段值变化时触发（对应配置项的key）
          getValue: value, // 计算值的方法
          transferModel // 结果的转换方式，fixed或者underfined，不需要改变，arrToObj, 将数组中的每一项作为新对象的key，并赋值为true
        });
      } else {
        this.dependency.push({
          path: path.join('.'), // 映射字段路径
          triggerFields, // 关键字段值变化时触发（对应配置项的key）
          getValue: value, // 计算值的方法
          transferModel // 结果的转换方式，fixed或者underfined，不需要改变，arrToObj, 将数组中的每一项作为新对象的key，并赋值为true
        });
      }
    },
    /**
         * 更新配置项
         */
    toUpdateOption(form, triggerFields, isInit = false, fromPattern = false) {
      // 根据依赖计算普通属性
      this.computedOptionByDependency(form, triggerFields, isInit, fromPattern);
      // 根据依赖计算fetch结果
      this.computedOptionByFetch(form, triggerFields, isInit, fromPattern);
    },
    /**
         * 根据声明的依赖计算配置项
         * triggerFields只能是字符串或者数组
         */
    computedOptionByDependency(form, triggerFields, isInit = false, fromPattern = false) {
      // 建立索引
      const map = {};
      if (typeof triggerFields === 'string') {
        map[triggerFields] = true;
      } else {
        for (const item of triggerFields) {
          map[item] = true;
        }
      }
      if (fromPattern) {
        // 处理基础配置项
        let mappingField = [];
        this.patternFetch.forEach(item => {
          // 找到依赖当前变化字段的数据源
          // 初始化视为所有字段变化
          if (isInit || item.triggerFields.some(sItem => map[sItem])) {
            mappingField = mappingField.concat(item.path);
          }
        });
        // 处理指定模式配置项
        for (const item of this.patternDependency) {
          // 属性计算触发机制
          // 1.初始化时 2.属性未声明依赖 3.属性声明的依赖变化。  以上条件任一为真
          // 如果有任一fetch映射的该属性，需要等fetch结束在计算
          if (
            (isInit ||
                            item.triggerFields.length === 0 ||
                            item.triggerFields.some(field => {
                              return map[field];
                            })) &&
                        !mappingField.includes(item.path)
          ) {
            this.handleDependencyToOption(form, item, true);
          }
        }
      } else {
        let mappingField = [];
        this.fetch.forEach(item => {
          // 找到依赖当前变化字段的数据源
          // 初始化视为所有字段变化
          if (isInit || item.triggerFields.some(sItem => map[sItem])) {
            mappingField = mappingField.concat(item.path);
          }
        });
        // 处理基础配置项
        for (const item of this.dependency) {
          // 属性计算触发机制
          // 1.初始化时 2.属性未声明依赖 3.属性声明的依赖变化。  以上条件任一为真
          // 如果有任一fetch映射的该属性，需要等fetch结束在计算
          if (
            (isInit ||
                            item.triggerFields.length === 0 ||
                            item.triggerFields.some(field => {
                              return map[field];
                            })) &&
                        !mappingField.includes(item.path)
          ) {
            // 第一次初始化只需要将配置项更新到缓存中，后续会强制更新
            this.handleDependencyToOption(form, item, !isInit);
          }
        }
      }
    },
    /**
         * 根据改变的字段触发fetch
         * isInit: 是否为初始化，初始化会忽略triggerFields匹配
         */
    computedOptionByFetch(form, triggerFields, isInit = false, fromPattern = false) {
      // 建立索引
      const map = {};
      if (typeof triggerFields === 'string') {
        map[triggerFields] = true;
      } else {
        for (const item of triggerFields) {
          map[item] = true;
        }
      }
      if (fromPattern) {
        this.patternFetch.forEach((item, index) => {
          if (isInit || item.triggerFields.some(field => map[field])) {
            this.handleFetchToOption(form, item, index, fromPattern);
            // 将控件值置为设定值
            // 初始化不需要重设
            if (item.resetFlag && this.value !== item.resetValue && !isInit) {
              this.valueChange(item.resetValue);
            }
          }
        });
      }
      // 初始化时，一定触发
      // 遍历fetch，匹配触发字段
      if (isInit || !fromPattern) {
        this.fetch.forEach((item, index) => {
          if (isInit || item.triggerFields.some(field => map[field])) {
            this.handleFetchToOption(form, item, index);
            // 将控件值置为设定值
            // 初始化不需要重设
            if (item.resetFlag && this.value !== item.resetValue && !isInit) {
              this.valueChange(item.resetValue);
            }
          }
        });
      }
    },
    /**
         * 处理数据源请求，缓存数据源请求结果，
         * 更新相关配置项
         */
    handleFetchToOption(form, fetch, index, fromPattern = false) {
      if (fetch.getValue.constructor !== Function) {
        this.doHandleFetchToOption(form, fetch.getValue, fetch, index, false, fromPattern);
      } else {
        const p = fetch.getValue(form);
        if (isPromise(p)) {
          p.then(response => {
            this.doHandleFetchToOption(form, response, fetch, index, true, fromPattern);
          });
        } else {
          this.doHandleFetchToOption(form, p, fetch, index, false, fromPattern);
        }
      }
    },
    doHandleFetchToOption(form, data, fetch, index, canUpdate = false, fromPattern = false) {
      // const dependency = fromPattern ? this.patternDependency : this.dependency
      this.updateFetchData(data, index, fromPattern);
      // 更新配置
      if (fromPattern) {
        for (const item of this.patternDependency) {
          if (fetch.path.includes(item.path)) {
            this.handleDependencyToOption(form, item, canUpdate, fromPattern);
          }
        }
      }
      // 因指定模式的fetch可能会映射到基础配置上，所以这里不管是否为指定模式，都需要更新基础配置
      for (const item of this.dependency) {
        if (fetch.path.includes(item.path)) {
          this.handleDependencyToOption(form, item, canUpdate, fromPattern);
        }
      }
    },
    /**
         * 根据依赖关系计算配置项
         */
    handleDependencyToOption(form, dependency, canUpdate = false, fromPattern = false) {
      const path = dependency.path;

      const patternFetchData = [];
      const patternFetchIds = []; // 指定模式数据源的id

      // 所有映射到该属性的数据源是否已请求完毕
      let patternFetchReady = true;
      let fetchReady = true;
      if (fromPattern) {
        this.patternFetch.forEach((item, index) => {
          if (
            item.path.includes(path) &&
                        typeof this.patternFetchData[index] !== 'undefined'
          ) {
            if (item.id) patternFetchIds.push(item.id);
            if (this.patternFetchData[index] !== undefined) {
              patternFetchData.push(this.patternFetchData[index]);
            } else {
              patternFetchReady = false;
            }
          }
        });
      }
      // 找到所有映射了该字段的fetchData
      const fetchData = [];
      this.fetch.forEach((item, index) => {
        // 如果当前fetch的id已经在指定模式中出现，则忽略该fetch
        if (
          item.path.includes(path) &&
                    typeof this.fetchData[index] !== 'undefined' &&
                    !patternFetchIds.includes(item.id)
        ) {
          if (this.fetchData[index] !== undefined) {
            fetchData.push(this.fetchData[index]);
          } else {
            fetchReady = false;
          }
        }
      });

      // 任意一个依赖的数据源未请求完毕，不需要更新配置
      if (!patternFetchReady || !fetchReady) return;

      // 如果该属性指定模式有数据源，则认为该配置是指定了模式的
      const p = dependency.getValue(form, ...fetchData, ...patternFetchData);
      if (isPromise(p)) {
        p.then(response => {
          const value = this.transferPathToObject(
            path,
            response,
            dependency.transferModel
          );
          // 对于异步的属性计算，直接更新
          this.doUpdateOptions(value, true, fromPattern);
        });
      } else if (p && p.constructor === Function) {
        const value = this.transferPathToObject(
          path,
          p(form, ...fetchData),
          dependency.transferModel
        );
        this.doUpdateOptions(value, canUpdate, fromPattern);
      } else {
        // 根据转换规则处理数据
        const value = this.transferPathToObject(path, p, dependency.transferModel);
        this.doUpdateOptions(value, canUpdate, fromPattern);
      }
    },
    /**
     * 将属性路径转为对象
    */
    transferPathToObject(path, value, transferModel, separator = '.') {
      // 分割key
      const key = path.split(separator);
      // 如果value值为引用类型，需要转为字符串
      const obj = {};
      // obj[key[key.length - 1]] = value
      // 根据属性路径path，拼接出属性字符串
      let leftStr = '{';
      let rightStr = '}';
      for (let i = 0; i < key.length; i++) {
        if (i === key.length - 1) {
          if (transferModel === arrToObj) {
            obj[key[i]] = {};
            for (let item of value) {
              obj[key[i]][item] = true;
            }
          } else {
            obj[key[i]] = value;
          }
        } else if (i === key.length - 2) {
          leftStr += `"${key[i]}":`;
        } else {
          leftStr += `"${key[i]}":{`;
          rightStr += '}';
        }
      }
      if (key.length > 1) {
        // 拼接值,并提交
        return JSON.parse(leftStr + JSON.stringify(obj) + rightStr);
      } else {
        return obj;
      }
    },
    /**
         * 合并配置项,
         * 等待所有规则计算完毕后（异步除外），批量更新
         * 规则计算完成标识，canUpdate
         */
    doUpdateOptions(obj = {}, canUpdate = false, fromPattern = false) {
      // 合并配置项
      if (fromPattern) {
        this.patternOption = deepMerge(true, this.patternOption, obj);
      } else {
        this.stageOption = deepMerge(true, this.stageOption, obj);
      }
      // 需要对多个同步计算结果一起处理,如果canUpdate为true，则直接更新
      if (canUpdate) {
        this.$set(this, 'usedOption', deepMerge(true, this.stageOption, this.patternOption));
        this.key = Math.random();
      } else {
        // // 增加防抖机制，防止触发多次
        // if (this.updateOptionTimer) {
        //   clearTimeout(this.updateOptionTimer);
        // }
        // this.updateOptionTimer = setTimeout(() => {
        //   this.$set(this, 'usedOption', deepMerge(true, this.stageOption, this.patternOption));
        //   this.key = Math.random();
        // }, 50);
      }
    },
    /**
         * 校验模式关键字是否正确
         */
    testPatternKey(key) {
      // 关键字，禁用以下字段名作为模式名称
      const primaryWords = [
        'key',
        'label',
        'type',
        'data',
        'rules',
        'fetch',
        'disabled',
        'hidden',
        'attrs',
        'props',
        'style',
        'class',
        'domProps',
        'on',
        'nativeOn',
        'directives',
        'scopedSlots',
        'slot',
        'ref',
        'refInFor'
      ];
      // 保留字，配置属性上的关键字，不可以作为模式关键字
      // 关键字必须为字符串，且不能为空串
      if (
        primaryWords.includes(key) ||
                typeof key !== 'string' ||
                key === '' ||
                !this.option[key]
      ) {
        return false;
      }
      return true;
    }
  }
};
</script>
