<template>
  <ElForm ref="elForm"
          class="x-form"
          v-bind="$attrs"
          :model="model"
          :label-width="fitLabelWidth"
          v-on="$listeners">
    <ElRow :gutter="10">
      <ElCol v-for="(item,index) in showFields"
             :key="`${item.prop}_${index}`"
             :span="getColSpan(item.column)">
        <XFormItem v-bind="item">
          <slot :name="item.prop||'default'" :item="item" :index="index">
            <component v-if="getFieldComponent(item.type)"
                       :is="getFieldComponent(item.type)"
                       :options="item.options"
                       v-bind="getComponentProps(item)"
                       v-on="item.events"
                       v-model="model[item.prop]"></component>
          </slot>
        </XFormItem>
      </ElCol>
    </ElRow>
    <div v-if="showFold" class="x-form__fold" @click="toggleFold">
      <XIcon :class="foldIcon" size="mini"></XIcon>
    </div>
  </ElForm>
</template>

<script>
/**
 * XForm 表单组件
 * @module XForm
 * @description 继承 el-form的全部属性、方法和事件
 */
import {
  Form as ElForm,
  Input as ElInput,
  Row as ElRow,
  Col as ElCol
} from 'element-ui';
import { XFormItem, XIcon } from '@newpearl/ui';
import Keyboard from '@newpearl/utils/lib/Keyboard';
import XSelect from './fields/Select';
import XDatePicker from './fields/DatePicker';
import XTimePicker from './fields/TimePicker';
import XCascader from './fields/Cascader';
import { getCharLength, debounce } from '@newpearl/utils/lib/util';
import { proxyMethods } from '../../utils';
import * as DefaultProps from './defaultProps';

const typeMap = {
  input: ElInput,
  select: XSelect,
  date: XDatePicker,
  time: XTimePicker,
  cascader: XCascader
};

const defaultKeyboardProps = {
  focus: -1,
  loop: false
};

export default {
  name: 'XForm',
  inheritAttrs: false,
  provide() {
    return {
      xForm: this
    };
  },
  components: {
    ElForm,
    XFormItem,
    ElRow,
    ElCol,
    XIcon
  },
  props: {
    /**
     * 属性参数
     * @member props
     * @property {object[]} [fields] 字段域配置, 支持全部的 el-form-item 配置项，并且支持以下扩展设置
     * @property {string} [fields.type] 输入框类型，可选 input select date time cascader ,可选值不支持的输入框可以用插槽自定义
     * @property {string} fields.prop 字段名称，输入框插槽根据该名称定义
     * @property {array} [fields.options] 输入框选项数据
     * @property {number} [fields.column=1] 字段域占空间的列数
     * @property {object} [fields.events] 输入框绑定事件
     * @property {function} [fields.beforeFocusNext] 键盘FocusNext时回调函数，return false 可阻止FocusNext
     * @property {object} [model] 表单实体对象，与表单域双向绑定
     * @property {number} [column=4] 没行显示几个表单域
     * @property {string} [labelWidth=auto] 表单域label宽度，默认自动计算
     * @property {number} [labelWidthAdjust=24] 表单域宽度偏移值
     * @property {number} [fontSize=14] 字体大小，用来计算label宽度
     * @property {Boolean|object} [keyboard] 键盘操作配置，false标识不开启键盘操作功能
     * @property {number} [keyboard.focus=-1] 初始化焦点激活的输入框索引
     * @property {boolean} [keyboard.loop=false] 焦点循环切换
     * @property {function} [beforeFocusNext] 键盘FocusNext时回调函数，return false 可阻止FocusNext, 如果定义全局，组件自身的beforeFocusNext会失效
     * @property {boolean} [foldable=true] 开启折叠功能
     */
    fields: {
      type: Array,
      default() {
        return [];
      }
    },
    model: {
      type: Object,
      default() {
        return Object.create(null);
      }
    },
    componentMap: {
      type: Object,
      default() {
        return typeMap;
      }
    },
    column: {
      type: Number,
      default: 4
    },
    labelWidth: {
      type: String,
      default: 'auto'
    },
    labelWidthAdjust: {
      type: Number,
      default: 20
    },
    fontSize: {
      type: Number,
      default: 14
    },
    keyboard: {
      type: [Boolean, Object],
      default: false
    },
    beforeFocusNext: {
      type: Function
    },
    foldable: {
      type: Boolean,
      default: true
    }
  },
  data() {
    // 该属性，在FormItem组件中赋值
    this.fieldInstances = Object.create(null);
    // 该属性，在FormItem组件中赋值
    this.focusComponent = null;
    return {
      unfold: true
    };
  },
  computed: {
    keyboardProps() {
      return this.keyboard ? {
        ...defaultKeyboardProps,
        ...this.keyboard
      } : null;
    },
    colSpan() {
      return Math.floor(24 / this.column);
    },
    fitLabelWidth() {
      if (this.labelWidth !== 'auto') return this.labelWidth;
      const chartsArray = this.fields.map(n => getCharLength(n.label)).sort((a, b) => b - a);
      return chartsArray[0] * (this.fontSize / 2) + this.labelWidthAdjust + 'px';
    },
    showFold() {
      return this.foldable && this.fields.length > this.column;
    },
    showFields() {
      return this.unfold ? this.fields : this.fields.slice(0, this.column);
    },
    foldIcon() {
      return this.unfold ? 'el-icon-caret-top' : 'el-icon-caret-bottom';
    }
  },
  methods: {
    ...proxyMethods(['validate', 'validateField', 'resetFields', 'clearValidate'], 'elForm'),
    /**
     * 提交表单
     * @method submit
     */
    submit() {
      return this.$refs.elForm.validate((r, err) => {
        if (r) {
          /**
           * 表单提交时触发
           * @event submit
           * @param {object} model 表单数据实体
           */
          this.$emit('submit', { ...this.model }, this);
        } else {
          /**
           * 表单校验不通过时触发
           * @event error
           * @param {object} 未通过校验的字段
           */
          this.$emit('error', err);
        }
      });
    },
    getColSpan(column = 1) {
      return column * this.colSpan;
    },
    getFieldComponent(type = 'input') {
      const defaultComponent = this.componentMap.input;
      return this.componentMap[type] || defaultComponent;
    },
    getComponent(nameOrIndex) {
      const name = typeof nameOrIndex === 'string' ? nameOrIndex : this.fields[nameOrIndex]?.prop;
      if (!name) return;
      return name ? this.fieldInstances[name] : null;
    },
    /**
     * 获得焦点
     * @method focus
     * @param {string|number} nameOrIndex
     */
    focus(nameOrIndex) {
      const component = this.getComponent(nameOrIndex);
      if (component) {
        component.focus && component.focus();
      }
    },
    /**
     * 取消焦点
     * @method blur
     * @param {string|number} nameOrIndex
     */
    blur(nameOrIndex) {
      const component = this.getComponent(nameOrIndex);
      if (component) {
        component.blur && component.blur();
      }
    },
    getFocusIndex() {
      if (!this.focusComponent) return -1;
      return this.fields.findIndex(n => {
        const component = this.getComponent(n.prop);
        return component === this.focusComponent;
      });
    },
    /**
     * 下一个输入框获得焦点
     * @method focusNext
     */
    focusNext() {
      let index = this.getFocusIndex();
      const component = this.getComponent(index);
      const total = this.fields.length;
      const { loop } = this.keyboardProps;
      const currentField = this.fields[index];
      let nextComponent;
      if (index > -1) {
        do {
          ++index;
          if (loop && index >= total) {
            index = 0;
          }
          console.log('index', index, loop);
          nextComponent = this.getComponent(index);
        } while (this.isFocusDisabled(nextComponent));
        const beforeFocusNext = this.beforeFocusNext || currentField.beforeFocusNext || component.beforeFocusNext;
        if (beforeFocusNext) {
          const canNext = beforeFocusNext(component, nextComponent);
          canNext && this.focus(index);
        } else {
          this.focus(index);
        }
      }
    },
    isFocusDisabled(component) {
      return component?.disabled || component?.$attrs.disabled || component?.readonly || component?.$attrs.readonly;
    },
    createKeyboardInstance() {
      this.keyboardInstance = new Keyboard({
        el: this.$el,
        stop: false
      });
      this.keyboardInstance.on('Enter', this.proxyFocusNext);
    },
    toggleFold() {
      this.unfold = !this.unfold;
    },
    getComponentProps(field) {
      const { type, label, props } = field;
      const defaultProps = DefaultProps[type] || {};
      const placeholder = `请输入${label}`;
      let valueFormat;
      if (type === 'date') {
        valueFormat = props?.type === 'datetime' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd';
      }
      return {
        placeholder,
        valueFormat,
        ...defaultProps,
        ...props
      };
    }
  },
  mounted() {
    this.proxyFocusNext = debounce(this.focusNext, 200, {
      // 延时开始前调用
      leading: true,
      trailing: false
    });
    if (this.keyboardProps) {
      const { focus } = this.keyboardProps;
      this.focus(focus);
      this.createKeyboardInstance();
    }
  },
  beforeDestroy() {
    this.keyboardInstance && this.keyboardInstance.destroy();
    this.fieldInstances = Object.create(null);
  }
};
</script>
