<template>
  <view class="ml-form" :class="[className || '', {'is-border': border}]">
    <slot>
      <view v-for="(item, index) in items" :key="index">
        <ml-form-group v-if="item.children && item.children.length" :title="item.title">
          <ml-form-item
            v-for="(cItem, cIndex) in item.children"
            :key="cIndex"
            :title="cItem.title"
            :field="cItem.field"
            :vertical="cItem.vertical"
            :align="cItem.align"
            :titleAlign="cItem.titleAlign"
            :itemRender="cItem.itemRender"
            @modelvalue="updateModel">
          </ml-form-item>
        </ml-form-group>
        <ml-form-item v-else :title="item.title" :field="item.field" :labelWidth="item.labelWidth" :itemRender="item.itemRender" @modelvalue="updateModel"></ml-form-item>
      </view>
    </slot>
  </view>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'
import XEUtils from 'xe-utils'

export default defineComponent({
  name: 'MlForm',
  props: {
    modelValue: Object,
    items: Array as PropType<any[]>,
    rules: Object,
    align: String,
    titleAlign: String,
    border: Boolean,
    ruleMsg: {
      type: Boolean,
      default: true
    },
    disabled: {
      type: Boolean,
      default: false
    },
    readonly: Boolean,
    vertical: Boolean,
    labelWidth: String,
    customVaild: Function,
    className: String
  },
  inject: {
    currVM: {
      from: '$pageVM',
      default: null
    }
  },
  provide () {
    return {
      mlForm: this
    }
  },
  data () {
    return {
      itemList: []
    }
  },
  methods: {
    validate () {
      let errMaps: any = null
      for (let i = 0; i < this.itemList.length; i++) {
        const item: any = this.itemList[i]
        const errRule = this.validRule(item)
        if (errRule) {
          if (!errMaps) {
            errMaps = {}
          }
          errMaps[item.field] = errRule
          item.errRule = { field: item.field, message: errRule.message }
        } else {
          item.errRule = null
        }
      }
      return new Promise<any>(resolve => {
        if (errMaps) {
          if (this.ruleMsg) {
            uni.showToast({
              title: '请填写必填字段',
              icon: 'error'
            })
          }
          resolve(errMaps)
        } else {
          resolve(null)
        }
      })
    },
    validateField (field: any) {
      let errMaps = null
      if (field) {
        const item: any = this.itemList.find((item: any) => item.field === field)
        if (item) {
          const errRule = this.validRule(item)
          if (errRule) {
            errMaps = { [field]: errRule }
            item.errRule = { field, message: errRule.message }
          } else {
            item.errRule = null
          }
        }
      }
      return errMaps ? Promise.resolve(errMaps) : Promise.resolve()
    },
    validRule (item: any) {
      const rules = item && this.rules ? this.rules[item.field] : null
      const itemValue = item && this.modelValue ? XEUtils.get(this.modelValue, item.field) : null
      let errRule = null
      if (rules) {
        for (let i = 0; i < rules.length; i++) {
          const rule = rules[i]
          if (this.customVaild) {
            const customStatus = this.customVaild.call(this.currVM, { item, rule, rules, data: this.modelValue })
            // 如果 true 则通过
            if (customStatus === true) {
              break
            }
            // 如果 true 则不通过
            if (customStatus === false) {
              errRule = rule
              break
            }
            // 如果不通过并自定义提示语
            if (XEUtils.isString(customStatus)) {
              errRule = { field: item.field, message: customStatus }
              break
            }
          }
          if (rule.required) {
            if (XEUtils.eqNull(itemValue) || itemValue === '') {
              errRule = rule
              break
            }
            if (XEUtils.isString(itemValue) && !itemValue.trim()) {
              errRule = rule
              break
            }
            if (rule.type === 'integer' || rule.type === 'number') {
              if (rule.type === 'integer') {
                if (!/^\d+$/.test(itemValue)) {
                  errRule = rule
                  break
                }
              } else if (rule.type === 'number') {
                if (!/^[-+]?\d+(.\d+)?$/.test(itemValue)) {
                  errRule = rule
                  break
                }
              }
              const numValue = XEUtils.toNumber(itemValue)
              if (XEUtils.isNumber(rule.min) && numValue < rule.min) {
                errRule = rule
                break
              }
              if (XEUtils.isNumber(rule.max) && numValue > rule.max) {
                errRule = rule
                break
              }
            }
            if (rule.type === 'array') {
              if (!itemValue.length) {
                errRule = rule
                break
              }
            }
            if (rule.pattern && !new RegExp(rule.pattern).test(itemValue)) {
              errRule = rule
              break
            }
          }
        }
      }
      return errRule
    },
    updateModel ({ field, value }: any) {
      this.$emit('update:modelValue', { ...this.modelValue, [field]: value })
    }
  }
})
</script>

<style lang="scss">
.ml-form {
  display: block;
}
</style>
