<template>
  <div class="form-box">
    <el-form v-bind="getProps.formProps" ref="ecoFormRef" :model="model">
      <template #default>
        <template v-if="!getProps.onlyRenderComponent">
          <template v-if="slots.default">
            <slot
              name="default"
              :model="model"
              :get-component-width="getComponentWidth"
              :parse-label="parseLabel"
              :is-destroy="isDestroy"
              :is-show="isShow" />
          </template>
          <template v-else>
            <el-row class="!w-full" :gutter="getProps.formProps.gutter">
              <template v-for="item in itemsProps" :key="item.prop">
                <el-col :span="item.span" v-show="isShow(item)">
                  <el-form-item v-bind="item" :prop="item.prop">
                    <template #label>
                      <el-space :size="4">
                        <span> {{ item.label }}</span>
                        <el-tooltip v-if="item.tooltip" effect="dark" :content="item.tooltip" placement="top">
                          <EcoIcon icon="el-icon-Warning" />
                        </el-tooltip>
                      </el-space>
                      <span>&nbsp;:</span>
                    </template>
                    <template v-if="item.eTag == 'slot'">
                      <slot
                        :name="item.prop"
                        :model="model"
                        :get-component-width="getComponentWidth"
                        :parse-label="parseLabel"
                        :is-destroy="isDestroy"
                        :is-show="isShow" />
                    </template>
                    <EcoItem v-else :style="getComponentWidth(item)" :item="item" v-model="model" />
                  </el-form-item>
                </el-col>
              </template>
            </el-row>
            <el-form-item v-if="slots.operation && getProps.formProps.hasFooter">
              <slot name="operation" :model="model" :eco-form-ref="ecoFormRef">
                <el-button type="primary" v-if="getProps.formProps.submitButtonText" @click="onSubmit(ecoFormRef)">{{
                  getProps.formProps.submitButtonText
                }}</el-button>
                <el-button v-if="getProps.formProps.resetButtonText" type="info" @click="resetForm(ecoFormRef)">
                  {{ getProps.formProps.resetButtonText }}
                </el-button>
                <el-button v-if="getProps.formProps.cancelButtonText" @click="emits('cancel')">
                  {{ getProps.formProps.cancelButtonText }}
                </el-button>
              </slot>
            </el-form-item>
          </template>
        </template>
      </template>
    </el-form>
  </div>
</template>

<script lang="ts" setup name="EcoForm">
import { type FormInstance } from 'element-plus'
import { handleProp, handleFormProp, deleteObjProperty, getPx, formHandleProp } from '@/utils'
import { isString } from '@/utils/is'
export interface EcoFormProps {
  formProps?: EcoForm.Form | Record<string, any>
  itemsProps?: EcoForm.Item[]
  enumMapProps?: Map<string, Record<string, any>[]>
  onlyRenderComponent?: boolean
  dynamicModel?: boolean
  includeModelKeys?: string[]
  api?: (params: any) => Promise<any> // 表单提交api
}
const props = withDefaults(defineProps<EcoFormProps>(), {
  itemsProps: () => [],
  formProps: () => ({
    gutter: 20,
    labelWidth: undefined,
    labelPosition: 'right',
    submitButtonText: '提交',
    hasFooter: true,
    resetButtonText: '重置',
    cancelButtonText: '取消'
  }),
  onlyRenderComponent: false,
  dynamicModel: true,
  includeModelKeys: () => []
})
interface EmitEvent {
  (e: 'submit', params: any): void
  (e: 'reset'): void
  (e: 'cancel'): void
}
const emits = defineEmits<EmitEvent>()
const model = defineModel<Record<string, any>>({ default: () => ({}) })
const mergeProps = ref<EcoFormProps>({})
const slots = useSlots()
const enumMap = ref(props.enumMapProps || new Map<string, Record<string, any>[]>())

const setEnumMap = async ({ enum: enumValue, prop, useCacheEnum = true }: EcoForm.Item) => {
  if (!enumValue) return
  const enumMapConst = unref(enumMap)
  if (useCacheEnum && enumMapConst.has(prop!) && (typeof enumValue === 'function' || enumMapConst.get(prop!) === enumValue)) return
  if (typeof enumValue !== 'function') return enumMapConst.set(prop, unref(enumValue)!)
  enumMapConst.set(prop!, [])
  let data = await enumValue(unref(model), enumMapConst)
  data = data?.data || data
  enumMapConst.set(prop, data)
}
provide('enumMap', enumMap)
const ecoFormRef = shallowRef<FormInstance>()
/**
 * 是否隐藏表单项
 */
const isShow = (item: EcoForm.Item) => {
  const { hidden } = item
  if (typeof hidden === 'function') return !hidden(unref(model))
  return !hidden
}

// 初始化默认值
const initDefaultValue = async ({ defaultValue, fieldNames, prop }: EcoForm.Item) => {
  const formConst = unref(model)
  const value = formHandleProp(formConst, prop)
  if (value || value === false || value === 0) return
  const defaultValueConst = unref(defaultValue)
  // 设置表单项的默认值，如果存在值，则不需要赋默认值
  if (defaultValueConst !== undefined && defaultValueConst !== null) {
    if (typeof defaultValueConst !== 'function') return handleFormProp(formConst, prop, defaultValueConst)
    return handleFormProp(formConst, prop, await defaultValueConst(formConst, unref(enumMap)))
  }

  // 如果没有设置默认值，则判断后台是否返回 isDefault 为 Y 的枚举
  const enumData = unref(enumMap).get(prop)
  if (enumData?.length) {
    // 找出 isDefault 为 Y 的 value
    const data = enumData.filter(item => item.isDefault === 'Y')
    return data.length && handleFormProp(formConst, prop, data[0][fieldNames?.value ?? 'value'])
  }
}
const cascadeEnum = ({ prop, eTag, subField, subEnum }: EcoForm.Item) => {
  if (eTag === 'select') {
    if (typeof subField !== 'string') return
    // 监听级联下拉变化
    watch(
      () => formHandleProp(unref(model), prop),
      async (newVal: string) => {
        const enumMapConst = unref(enumMap)
        // 选择时将级联的 subProp 置空
        if (unref(model)[subField!]) unref(model)[subField!] = ''

        if (!subEnum) return
        if (!newVal) return enumMapConst.set(subField!, [])

        if (enumMapConst.get(`${subField!}-${newVal}`)) {
          // 存在缓存字典数据，则取出来赋值
          enumMapConst.set(subField!, enumMapConst.get(`${subField!}-${newVal}`) || [])
        } else {
          if (typeof subEnum === 'function') {
            const subEnumData = await subEnum(newVal, enumMapConst.get(prop))
            // 缓存字典数据
            enumMapConst.set(`${subField!}-${newVal}`, subEnumData)
            enumMapConst.set(subField!, subEnumData)
          } else if (Array.isArray(subEnum)) {
            // 缓存字典数据
            enumMapConst.set(`${subField!}-${newVal}`, subEnum)
            enumMapConst.set(subField!, subEnum)
          }
        }

        const formEnum = enumMapConst.get(prop) || []
        const [enumValue] = formEnum.filter(item => item.value === newVal)
        // 如果选中的字典有 subValue，则直接赋值给 subField
        if (enumValue?.subValue) unref(model)[subField!] = enumValue.subValue
      },
      { immediate: true }
    )
  }
}
const getProps = computed(() => {
  const propsObj = { ...props }
  Object.assign(propsObj, unref(mergeProps))
  return propsObj
})
// 监听表单结构化数组，重新组装 itemsProps
watch(
  () => unref(getProps).itemsProps,
  (itemsProps = []) => {
    itemsProps.forEach((item, index) => {
      // 设置枚举
      setEnumMap(item)
      // 级联下拉监听
      cascadeEnum(item)
      // 设置表单排序默认值
      item && (item.order = item.order ?? index + 2)
      // 初始化值
      initDefaultValue(item)
    })
    // 排序表单项
    itemsProps.sort((a, b) => a.order! - b.order!)
    if (unref(getProps).dynamicModel) {
      // 如果 itemsProps 对应的 prop 不存在，则删除 model 中的对应的 prop
      Object.keys(unref(model)).forEach(key => {
        const isExist = itemsProps.some(
          item => item.prop === key || item.renderUseProp?.includes(key) || unref(getProps).includeModelKeys?.includes(key)
        )
        if (!isExist) delete unref(model)[key]
      })
    }
  },
  {
    immediate: true,
    deep: true
  }
)
const isDestroy = (item: EcoForm.Item) => {
  let destroy: boolean
  if (typeof item.destroy === 'function') destroy = item.destroy(unref(model))
  else destroy = item.destroy || false

  // 如果不销毁，则初始化表单默认值，反之则重置为空
  if (!destroy) initDefaultValue(item)
  else deleteObjProperty(unref(model), item.prop)
  return destroy
}
const parseLabel = (label: ValueType | ((model: Record<string, any>) => string) | ComputedRef<ValueType>) => {
  if (typeof label === 'function') return label(unref(model))
  return unref(label) + ''
}
const getComponentWidth = ({ width, props: componentProps }: EcoForm.Item) => {
  const { formProps = {} } = unref(getProps)
  const style = componentProps?.style || { width: '100%' } // 默认宽度 100%
  if (width) return { ...style, width: getPx(width) }
  if (formProps.fixWidth) return { ...style, width: getPx(formProps.width || formProps.inline ? 220 : '100%') }
  return style
}

// 设置 form 的值
const setValues = (data: Record<string, any> = {}) => {
  model.value = Object.assign(unref(model), data)
}

// 设置 EcoForm 组件的 props
const setProps = (props: Partial<EcoFormProps> = {}) => {
  mergeProps.value = Object.assign(unref(mergeProps), props)
}

// 设置 items
const setItems = (itemSet: FormSetProps[]) => {
  const { itemsProps } = unref(getProps)
  for (const v of itemsProps) {
    for (const item of itemSet) {
      if (v.prop === item.prop) {
        handleFormProp(v, item.fieldName, item.value)
      }
    }
  }
}

// 添加 items
const addItems = (formItem: EcoForm.Item, prop?: number | string, position: 'before' | 'after' = 'after') => {
  const { itemsProps } = unref(getProps)

  if (isString(prop)) {
    return itemsProps.forEach((s, i) => {
      if (s.prop === prop) position === 'after' ? formItem.splice(i + 1, 0, s) : formItem.splice(i, 0, s)
    })
  }
  if (prop !== undefined) return itemsProps.splice(prop, 0, formItem)
  return itemsProps.push(formItem)
}

// 删除 items
const delItems = (prop: string) => {
  const { itemsProps } = unref(getProps)

  const index = itemsProps.findIndex(item => item.prop === prop)
  if (index > -1) itemsProps.splice(index, 1)
}
// 提交按钮
const onSubmit = (formEl: FormInstance | undefined) => {
  if (!props.api) return ElMessage.warning('提交API不能为空。')
  if (!formEl) return
  formEl.validate(valid => {
    if (valid) {
      if (!props.api) emits('submit', model.value)
      props.api!({ ...model }).then(res => {
        if (res.code == 200) {
          resetForm(formEl)
          ElMessage.success('操作成功')
        } else {
          console.log('message', res.message)
        }
      })
    } else {
      console.log('校验失败')
    }
  })
}
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}
defineExpose({
  form: ecoFormRef,
  model,
  resetForm,
  onSubmit,
  setValues,
  setProps,
  setItems,
  addItems,
  delItems,
  getComponentWidth,
  parseLabel,
  isDestroy,
  isShow
})
</script>
<style lang="scss" scoped></style>
