<script setup>
import { ref, computed, useSlots, watch, inject } from 'vue'
import { COMP_NAME, COMPONENT_VALUES, BTN_TYPES, ACTION_ALIGN_VALUES } from './constant'
import {
  isString,
  isUndefined,
  isBoolean,
  isObject,
  isFunction,
  isNull,
  dealWidth,
  checkTypesCorrect,
  checkKeyHazyExist
} from '../utils/assist'
import { toFixed } from '../utils/number'
import grid from './grid'
import VRender from '../VRender/index.vue'
import VAutocomplete from '../Fields/VAutocomplete.vue'
import VCascader from '../Fields/VCascader.vue'
import VCheckbox from '../Fields/VCheckbox.vue'
import VCheckboxGroup from '../Fields/VCheckboxGroup.vue'
import VColorPicker from '../Fields/VColorPicker.vue'
import VDatePicker from '../Fields/VDatePicker.vue'
import VInput from '../Fields/VInput.vue'
import VInputNumber from '../Fields/VInputNumber.vue'
import VRadioGroup from '../Fields/VRadioGroup.vue'
import VRate from '../Fields/VRate.vue'
import VSelect from '../Fields/VSelect.vue'
import VSwitch from '../Fields/VSwitch.vue'
import VTimePicker from '../Fields/VTimePicker.vue'
import VTimeSelect from '../Fields/VTimeSelect.vue'
import VUpload from '../Fields/VUpload.vue'
import { vRipple } from '../directives/ripple'

const COMP_MAP = {
  Render: VRender,
  Autocomplete: VAutocomplete,
  Cascader: VCascader,
  Checkbox: VCheckbox,
  CheckboxGroup: VCheckboxGroup,
  ColorPicker: VColorPicker,
  DatePicker: VDatePicker,
  Input: VInput,
  InputNumber: VInputNumber,
  RadioGroup: VRadioGroup,
  Rate: VRate,
  Select: VSelect,
  Switch: VSwitch,
  TimePicker: VTimePicker,
  TimeSelect: VTimeSelect,
  Upload: VUpload
}

defineOptions({
  name: COMP_NAME
})

const emits = defineEmits(['validateError', 'validate', 'submit', 'reset'])

const props = defineProps({
  fields: {
    type: Array,
    required: true,
    validator(val) {
      let nameList = []
      val.forEach((item) => {
        let { name, component, render, slot } = item
        if (!name && !render && !slot) {
          throw new Error(
            `${COMP_NAME}: fields每项中需传入唯一标志符name字段或render函数或者指定slot`
          )
        }
        if (name && !component) {
          throw new Error(`${COMP_NAME}: fields每项中需传入component字段来指定表单项`)
        }
        if (!isUndefined(name) && !isString(name)) {
          throw new Error(`${COMP_NAME}: fields每项中的name字段需为String类型`)
        }
        if (!isUndefined(component) && !COMPONENT_VALUES.includes(component)) {
          throw new Error(`${COMP_NAME}: fields中component的值需为${COMPONENT_VALUES.join('|')}`)
        }
        if (name && nameList.includes(name)) {
          throw new Error(`${COMP_NAME}: fields每项中唯一标志符name字段值: ${name}重复`)
        }
        name && nameList.push(name)
      })
      return true
    }
  },
  config: {
    type: Object,
    default() {
      return {}
    },
    validator(value) {
      const { rules, actionAlign = 'left', submitBtn, resetBtn } = value
      if (!isUndefined(rules) && !isObject(rules)) {
        throw new Error(`${COMP_NAME}: config中的rule字段需为Object类型`)
      }
      if (!checkTypesCorrect(submitBtn, BTN_TYPES)) {
        throw new Error(`${COMP_NAME}: config中的submitBtn字段需为${BTN_TYPES.join('|')}类型`)
      }
      if (!checkTypesCorrect(resetBtn, BTN_TYPES)) {
        throw new Error(`${COMP_NAME}: config中的resetBtn字段需为${BTN_TYPES.join('|')}类型`)
      }
      if (!ACTION_ALIGN_VALUES.includes(actionAlign)) {
        throw new Error(
          `${COMP_NAME}: config中的actionAlign字段需为${ACTION_ALIGN_VALUES.join('|')}`
        )
      }
      return true
    }
  },
  // 是否作为搜索区域
  isSearch: {
    type: Boolean,
    default: false
  }
})
const CONFIG = inject('CONFIG_V_FORM')

const root = ref()

const model = defineModel({
  type: Object,
  required: true
})

const FORM_ID = new Date().getTime().toString().slice(-4) + Math.random().toString().slice(-4)

const slots = useSlots()

// slot map
const slotsMap = computed(() => {
  return props.fields
    .filter((item) => item.name && item.component && item.slot)
    .reduce((acc, cur) => {
      const { name } = cur
      const currentSlot = cur.slot
      if (isString(currentSlot)) {
        acc[name] = { default: currentSlot }
      } else if (isObject(currentSlot)) {
        acc[name] = { ...currentSlot }
      }
      return acc
    }, {})
})

// 表单宽度
const formWidth = computed(() => {
  const width = dealWidth(props.config.width) || 'auto'
  return width
})

const formConfig = computed(() => {
  const { submitBtn, resetBtn, ...rest } = props.config || {}
  return rest
})

// 表单提交按钮配置
let submitConfig = ref({
  text: '提交',
  width: 100,
  type: 'primary',
  plain: false,
  loading: false
})

watch(
  () => props.config.submitBtn,
  (value) => {
    if (!isObject(value)) return
    submitConfig.value = {
      ...submitConfig.value,
      ...value
    }
  },
  { deep: true, immediate: true }
)

// 表单提交按钮宽度
const submitWidth = computed(() => {
  const width = dealWidth(submitConfig.value.width) || 'auto'
  return width
})

// 表单重置按钮配置
let resetConfig = ref({
  text: '重置',
  width: 100,
  type: 'primary',
  plain: true,
  loading: false
})

watch(
  () => props.config.resetBtn,
  (value) => {
    if (!isObject(value)) return
    resetConfig.value = {
      ...resetConfig.value,
      ...value
    }
  },
  { deep: true, immediate: true }
)
// 表单重置按钮宽度
const resetWidth = computed(() => {
  const width = dealWidth(resetConfig.value.width) || 'auto'
  return width
})
// 表单项ref
const fieldRefs = computed(() => {
  let refs = {
    formItem: {},
    field: {}
  }
  props.fields.forEach((item) => {
    if (item.name && item.component) {
      refs.formItem[item.name] = ref()
      refs.field[item.name] = ref()
    }
  })
  return refs
})
// 操作区域在grid的规划的单元格中
const actionInGrid = computed(() => {
  const config = props.config
  return props.isSearch && config.columns && !config.actionNewLine
})

const displayFields = computed(() => {
  return props.fields.filter((item) => isUndefined(item.hidden) || item.hidden !== true)
})

const groupFields = computed(() => {
  let groupInfo = {}
  let groupArr = []
  displayFields.value.forEach((field, index) => {
    let group = field.group
    if (!group) {
      groupInfo[`${index}`] = [field]
      groupArr[index] = [field]
    } else {
      const existKey = checkKeyHazyExist(groupInfo, group)
      if (existKey) {
        const existKeyIndex = Number(existKey.split('-')[0])
        groupInfo[existKey].push(field)
        groupArr[existKeyIndex].push(field)
      } else {
        groupInfo[`${index}-${group}`] = [field]
        groupArr[index] = [field]
      }
    }
  })
  return groupArr.map((item) => {
    if (item.length === 1) {
      return item[0]
    }
    return item
  })
})

const gridFields = computed(() => {
  let columns = props.config.columns || 1
  if (actionInGrid.value) {
    // 生成网格布局的fields数据
    return grid.create(
      [
        ...groupFields.value,
        {
          type: 'action',
          name: 'action',
          colSpan: 1,
          labelWidth: 1,
          class: 'el-form-action-wrapper'
        }
      ],
      columns
    )
  } else {
    // 生成网格布局的fields数据
    return grid.create(groupFields.value, columns)
  }
})

const colStyleInfo = computed(() => {
  const columns = props.config.columns || 1
  // 同一group中的表单域，公用colSpan，此colSpan为一组表单域中最大的那个colSpan
  return gridFields.value.map((grid) => {
    return grid.map((col) => {
      let colSpanList = col.map((item) => {
        return parseFloat(item.colSpan) || 1
      })
      const multiple = colSpanList.length > 1
      let flex = Math.max(...colSpanList) / columns
      flex = flex > 1 ? 1 : toFixed(flex, 4)
      const percent = `${flex * 100}%`
      return {
        style: {
          maxWidth: percent,
          minWidth: percent,
          flex
        },
        multiple,
        class: multiple ? `form-item-col-${col[0]?.group}` : `form-item-col-${col[0]?.name}`
      }
    })
  })
})

// 去除不必要的属性
function formatField(field) {
  if (!field) return {}
  const { name, component, label, labelPosition, labelWidth, rules, ...rest } = field
  return { ...rest, class: field.fieldClass, style: field.fieldStyle }
}

// 表单项的规则
function getFieldRules(rules = [], name) {
  if (!Array.isArray(rules)) throw new Error(`${COMP_NAME}: fields每项中的rules需为Array类型`)
  const parentRules = props.config.rules || {}
  const r = parentRules[name] || []
  return r.concat(rules)
}

function trimEmpty() {
  if (props.config.trimEmpty === false) return
  Object.entries(model.value).forEach(([key, value]) => {
    if (isUndefined(value) || isNull(value)) {
      delete model.value[key]
    }
  })
}
// 表单提交
async function onSubmit(...rest) {
  try {
    emits('validate')
    await root.value.validate()
    submitConfig.value.loading = true
    trimEmpty()
    emits(
      'submit',
      () => {
        submitConfig.value.loading = false
      },
      ...rest
    )
  } catch (e) {
    emits('validateError', e)
  }
}
// 表单重置
function onReset(...rest) {
  resetConfig.value.loading = true
  root.value.resetFields()
  trimEmpty()
  if (props.isSearch) {
    emits(
      'reset',
      () => {
        resetConfig.value.loading = false
      },
      ...rest
    )
  } else {
    emits('reset', ...rest)
    resetConfig.value.loading = false
  }
}
defineExpose({
  root,
  get el() {
    return document.querySelector(`.v-form-${FORM_ID}`)
  },
  get refs() {
    // 表单项ref
    let result = { formItem: {}, field: {} }
    const { formItem = {}, field = {} } = fieldRefs.value
    for (let [key, value] of Object.entries(formItem)) {
      result.formItem[key] = value.value[0]
    }
    for (let [key, value] of Object.entries(field)) {
      result.field[key] = value.value[0]
    }
    result.root = root.value
    return result
  },
  async validate(callback) {
    try {
      let res = await root.value.validate(callback)
      return Promise.resolve(res)
    } catch (e) {
      return Promise.reject(e)
    }
  },
  async validateField(props, callback) {
    try {
      let res = await root.value.validateField(props, callback)
      return Promise.resolve(res)
    } catch (e) {
      return Promise.reject(e)
    }
  },
  resetFields(props) {
    root.value.resetFields(props)
  },
  scrollToField(prop) {
    root.value.scrollToField(prop)
  },
  clearValidate(props) {
    root.value.clearValidate(props)
  },
  submit: onSubmit,
  reset: onReset
})
</script>

<template>
  <el-form ref="root" :class="['v-form', `v-form-${FORM_ID}`]" :model="model" v-bind="formConfig">
    <!-- 行内表单 -->
    <template v-if="props.config.inline">
      <el-form-item
        v-for="(item, index) in props.fields"
        v-show="item.hidden !== true"
        :key="index"
        :class="[item.class || '', item.name ? `el-form-item-${item.name}` : '']"
        :ref="fieldRefs.formItem[item.name]"
        :prop="item.name || `field-${index}`"
        :rules="getFieldRules(item.rules, item.name)"
        :required="item.required"
        :label-width="item.labelWidth"
        :error="item.error || ''"
        :show-message="isBoolean(item.showMessage) ? item.showMessage : true"
        :inline-message="item.inlineMessage || false"
        :size="item.size"
        :style="item.style || {}"
      >
        <!-- FormItem 的label slot -->
        <template v-if="item.label" #label>
          <VRender
            v-if="isFunction(item.label)"
            :render="item.label"
            :data="{ labelSuffix: formConfig.labelSuffix }"
          ></VRender>
          <template v-else> {{ item.label }}{{ formConfig.labelSuffix }} </template>
        </template>
        <!-- error slot -->
        <template
          v-if="item.slot && isObject(item.slot) && item.slot.error && slots[item.slot.error]"
          #error
        >
          <slot :name="item.slot.error"></slot>
        </template>
        <!-- 内置组件 -->
        <component
          :ref="fieldRefs.field[item.name]"
          v-if="item.component && COMPONENT_VALUES.includes(item.component)"
          :is="COMP_MAP[item.component]"
          v-model="model[item.name]"
          :field="{ ...formatField(item) }"
        >
          <template
            v-for="slotValue in Object.values(slotsMap[item.name] || {})"
            #[slotValue]="data"
          >
            <slot :name="slotValue" v-bind="data || {}"></slot>
          </template>
        </component>
        <template v-else>
          <template v-if="item.slot && slots[item.slot]">
            <slot :name="item.slot"></slot>
          </template>
          <!-- 自定义render函数 -->
          <VRender v-else :render="item.render"></VRender>
        </template>
      </el-form-item>
      <el-form-item
        class="el-form-action-wrapper"
        :class="[`align-${props.config.actionAlign || 'left'}`, { flex: formConfig.actionNewLine }]"
        v-if="props.fields && props.fields.length"
      >
        <template v-if="slots.action">
          <slot name="action-prepend"></slot>
          <slot name="action"></slot>
          <slot name="action-append"></slot>
        </template>
        <template v-else>
          <slot name="action-prepend"></slot>
          <el-button
            v-if="props.config.submitBtn !== false"
            class="submit-btn"
            :type="submitConfig.type"
            :plain="submitConfig.plain"
            v-bind="submitConfig"
            :text="false"
            v-ripple
            @click="() => onSubmit()"
            >{{ submitConfig.text }}</el-button
          >
          <el-button
            v-if="props.config.resetBtn !== false"
            class="reset-btn"
            :type="resetConfig.type"
            :plain="resetConfig.plain"
            v-bind="resetConfig"
            :text="false"
            v-ripple
            @click="() => onReset()"
            >{{ resetConfig.text }}</el-button
          >
          <slot name="action-append"></slot>
        </template>
      </el-form-item>
    </template>
    <!-- flex布局 -->
    <template v-else>
      <div class="form-item-row" v-for="(row, rowIndex) in gridFields" :key="`row-${rowIndex}`">
        <div
          v-for="(col, colIndex) in row"
          :key="`col-${colIndex}`"
          class="form-item-col"
          :class="[{ multiple: colStyleInfo[rowIndex][colIndex].multiple }, colStyleInfo[rowIndex][colIndex].class]"
          :style="colStyleInfo[rowIndex][colIndex].style"
        >
          <div class="field" :class="[item.name ? `field-${item.name}` : '']" v-for="(item, fieldIndex) in col" :key="`field-${fieldIndex}`">
            <el-form-item
              :class="[item.class || '', `el-form-item-${item.name}`]"
              :ref="fieldRefs.formItem[item.name]"
              :prop="item.name || `field-${fieldIndex}`"
              :rules="getFieldRules(item.rules, item.name)"
              :required="item.required"
              :label-width="item.labelWidth"
              :error="item.error || ''"
              :show-message="isBoolean(item.showMessage) ? item.showMessage : true"
              :inline-message="item.inlineMessage || false"
              :size="item.size"
              :style="item.style || {}"
            >
              <!-- FormItem 的label slot -->
              <template v-if="item.label" #label>
                <VRender
                  v-if="isFunction(item.label)"
                  :render="item.label"
                  :data="{ labelSuffix: formConfig.labelSuffix }"
                ></VRender>
                <template v-else> {{ item.label }}{{ formConfig.labelSuffix }} </template>
              </template>
              <!-- error slot -->
              <template
                v-if="item.slot && isObject(item.slot) && item.slot.error && slots[item.slot.error]"
                #error
              >
                <slot :name="item.slot.error"></slot>
              </template>
              <!-- 内置组件 -->
              <template v-if="item.name === 'action' && item.type === 'action'">
                <template v-if="slots.action">
                  <slot name="action-prepend"></slot>
                  <slot name="action"></slot>
                  <slot name="action-append"></slot>
                </template>
                <template v-else>
                  <slot name="action-prepend"></slot>
                  <el-button
                    v-if="props.config.submitBtn !== false"
                    class="submit-btn"
                    :type="submitConfig.type"
                    :plain="submitConfig.plain"
                    v-bind="submitConfig"
                    v-ripple
                    :text="false"
                    @click="() => onSubmit()"
                    >{{ submitConfig.text }}</el-button
                  >
                  <el-button
                    v-if="props.config.resetBtn !== false"
                    class="reset-btn"
                    :type="resetConfig.type"
                    :plain="resetConfig.plain"
                    v-bind="resetConfig"
                    v-ripple
                    :text="false"
                    @click="() => onReset()"
                    >{{ resetConfig.text }}</el-button
                  >
                  <slot name="action-append"></slot>
                </template>
              </template>
              <template v-else>
                <component
                  :ref="fieldRefs.field[item.name]"
                  v-if="item.component && COMPONENT_VALUES.includes(item.component)"
                  :is="COMP_MAP[item.component]"
                  v-model="model[item.name]"
                  :field="{ ...formatField(item) }"
                >
                  <template
                    v-for="slotValue in Object.values(slotsMap[item.name] || {})"
                    #[slotValue]="data"
                  >
                    <slot :name="slotValue" v-bind="data || {}"></slot>
                  </template>
                </component>
                <template v-else>
                  <template v-if="item.slot && slots[item.slot]">
                    <slot :name="item.slot"></slot>
                  </template>
                  <!-- 自定义render函数 -->
                  <VRender v-else :render="item.render"></VRender>
                </template>
              </template>
            </el-form-item>
          </div>
        </div>
      </div>
      <!-- 表单action区 -->
      <el-form-item
        class="el-form-action-wrapper"
        :class="[`align-${props.config.actionAlign || 'left'}`]"
        v-if="props.fields && props.fields.length && !actionInGrid"
      >
        <template v-if="slots.action">
          <slot name="action-prepend"></slot>
          <slot name="action"></slot>
          <slot name="action-append"></slot>
        </template>
        <template v-else>
          <slot name="action-prepend"></slot>
          <el-button
            v-if="props.config.submitBtn !== false"
            class="submit-btn"
            :type="submitConfig.type"
            :plain="submitConfig.plain"
            v-bind="submitConfig"
            v-ripple
            :text="false"
            @click="() => onSubmit()"
            >{{ submitConfig.text }}</el-button
          >
          <el-button
            v-if="props.config.resetBtn !== false"
            class="reset-btn"
            :type="resetConfig.type"
            :plain="resetConfig.plain"
            v-bind="resetConfig"
            v-ripple
            :text="false"
            @click="() => onReset()"
            >{{ resetConfig.text }}</el-button
          >
          <slot name="action-append"></slot>
        </template>
      </el-form-item>
    </template>
  </el-form>
</template>

<style>
.v-form {
  --v-form-bg: var(--v-light-bg);
  background-color: var(--v-form-bg);
  width: v-bind(formWidth);
}
.v-form .submit-btn {
  width: v-bind(submitWidth);
}
.v-form .reset-btn {
  width: v-bind(resetWidth);
}
.v-form .el-form-action-wrapper {
  margin-right: 0 !important;
}
.v-form .el-form-action-wrapper.align-center .el-form-item__content {
  margin-left: 0 !important;
  justify-content: center;
}
.v-form .el-form-action-wrapper.align-right .el-form-item__content {
  justify-content: end;
}
.v-form .el-form-action-wrapper .el-form-item__label {
  display: none;
}
.v-form .el-form-action-wrapper.flex {
  display: flex;
}
.v-form .form-item-row {
  display: flex;
  flex-wrap: wrap;
}

.v-form .form-item-col.multiple {
  display: flex;
}
</style>
