<template>
  <el-form
    ref="FormRef"
    :model="modelData"
    class="sel-form"
    v-bind="props"
  >
    <s-grid v-bind="defaultOptions.grid">
      <s-grid-item
        v-for="(item, index) in fields"
        :key="index"
        v-bind="item.props?.gridItem || defaultOptions?.gridItem"
        v-show="itemVisible(modelData[item.code], item, index)"
        :i="item.code"
      >
        <s-form-item
          v-if="itemVisible(modelData[item.code], item, index)"
          :ref="setItemRefs"
          :code="item.code"
          :index="index"
          :prefix="item.prefix"
          :keys="item.key"
          :label="item.label"
          :type="item.type"
          :props="item.props"
          :children="item.children"
          :style="item.style"
          :input-style="item.style"
          :options="item.options"
          :show="item.show"
          :component="item.component"
          :model-value="modelData[item.code]"
        />
      </s-grid-item>
    </s-grid> 
  </el-form>
  <template v-if="slots.length>0">
    <teleport
      v-for="item in slots"
      :key="item.code"
      :to="`#${item.code}`"
    >
      <slot
        :slot-item="item"
        :slot-data="modelData"
        :slot-config="item"
        :name="item.slotName||item.key"
        :data="modelData"
        :validator="validator(item)"
      ></slot>
    </teleport>
  </template>
</template>
<script lang="ts" setup>
import {
  computed,
  provide,
  ref,
  getCurrentInstance, watch, nextTick
} from 'vue'
import SFormItem from './FormItem.vue'
import type { FI } from '../types'
import { isFunction } from 'utils'
import { useFormData, useFormItemRefs, useItemHooks, initValueHooks, useBuildForm } from '../hooks'
import { GridItemOption, GridOption } from '../../GroupForm/types'
import { get } from 'lodash-es'

defineOptions({
  name: 'SForm'
})

interface GlobalOptions {
  grid: GridOption
  gridItem: GridItemOption
}

interface Props {
  fields: Array<FI>
  modelValue: any
  props?: any
  options: GlobalOptions
}

const cProps = withDefaults(defineProps<Props>(), {
  fields: () => ([]),
  modelValue: () => ({}),
  options: () => ({
    grid: {
      cols: 24
    },
    gridItem: {
      span: 6
    }
  })
})

const emits = defineEmits<{
  (e: 'update:modelValue', value: any): void
  (e: 'change', key: string | [], value: any): void
  (e: 'blur', key: string | [], value: any): void
  (e: 'focus', key: string | [], value: any): void
}>()
const instance: any = getCurrentInstance()
const prefix = computed(() => instance?.appContext.config.globalProperties.prefix)
const { formData, setFormData } = useFormData(cProps, emits)
const FormRef = ref(null)
const {
  modelData,
  getItem,
  getItemValue
} = useItemHooks(cProps)
/*
 * 获取所有slot插槽
 * */
const { slots, buildUseForm } = useBuildForm(cProps.fields)

const renderSlot = ref(true)
watch(formData, value => {
  buildUseForm(cProps.fields)
}, {
  deep: true
})

/*
 * 验证器
 * */
function validator (item: any) {
  return async function (value, trigger, callback?: any) {
    const sValue = value?.target ? (value?.target?.valueAsNumber || value?.target?.value) : value
    setFormData(sValue, item.key)
    modelData.value[item.code] = sValue
    if (formItemRefs.value[item.code] && item.rules && item.rules?.length > 0) {
      const validate = await formItemRefs.value[item.code].validateValue(sValue, trigger)
      if (callback) callback(sValue, validate)
    }
  }
}

/*
 * 获取循环的ref
 * */
const { formItemRefs, setItemRefs } = useFormItemRefs()

/*
 * 控制显示隐藏
 * */
function itemVisible (val, item, index) {
  let visible = true
  if (isFunction(item.show)) {
    visible = item.show({ getItem, getItemValue, val, item, index })
  }
  return visible
}

const defaultOptions = computed(() => {
  return Object.assign({}, {
    grid: {
      cols: 24
    },
    gridItem: {
      span: 6
    }
  }, cProps.options)
})
provide('globalPrefix', prefix)
provide('setFormData', setFormData)
provide('modelData', modelData)
provide('changeEvent', changeEvent)
provide('options', defaultOptions.value)
provide('globalProps', computed(() => cProps.props))
provide('getItem', getItem)
provide('getItemValue', getItemValue)

defineExpose({
  slots,
  getItem,
  resetValue,
  getItemValue,
  validate,
  validateFields
})

async function resetValue () {
  cProps.fields.forEach(item => {
    if (item.code) {
      delete modelData.value[item.code]
    }
  })
  formData.value = {}
}

async function validate () {
  let access: any = true
  for (let code in formItemRefs.value) {
    const instance = formItemRefs.value[code]
    if (instance.visible()) {
      const result = await instance.validateValue(modelData.value[code], 'all')
      if (!result) {
        access = result
      }
    }
  }
  return new Promise((resolve, reject) => {
    if (access) {
      resolve(access)
    } else {
      reject(access)
    }
  })
}

async function validateFields (options) {
  let code = ''
  const target = getItem(options)
  code = target.code
  let access: any = true
  const instance = formItemRefs.value[code]
  const result = await instance.validateValue(modelData.value[code], 'all')
  if (!result) {
    access = result
  }
  return new Promise((resolve, reject) => {
    if (access) {
      resolve(access)
    } else {
      reject(access)
    }
  })
}

function changeEvent (key: string, value: any) {
  emits('change', key, value)
}

initValueHooks(formData, cProps.fields, cProps, modelData)

watch(() => cProps.modelValue, () => {
  cProps.fields.forEach((field: FI) => {
    if (Array.isArray(field.key)) {
      const value = []
      field.key.forEach((key: string, index) => {
        value[index] = get(cProps.modelValue, key)
      })
      modelData.value[field.code] = value
    } else {
      modelData.value[field.code] = get(cProps.modelValue, field.key)
    }
  })
}, {
  deep: true
})
</script>
<style lang="scss">
.sel-form {
  .sel-grid {
    .el-form-item {
      width: 100%;
    }
  }
}
</style>
