<script setup lang="ts">
import { ref, reactive, watch, nextTick, computed } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'

/**
 * 基于 Element Plus 的表单弹窗组件
 * 支持新增/编辑模式，通过 fields 配置动态渲染表单项
 * 支持 input、select、number、date、textarea、radio、switch 等字段类型
 */
defineOptions({
  name: 'ElFormDialog',
  inheritAttrs: false, // 不自动将 $attrs 绑定到根元素，避免属性透传到 el-dialog
})

/** 表单字段类型 */
type FieldType = 'input' | 'select' | 'number' | 'date' | 'textarea' | 'radio' | 'switch'

/** 表单字段配置项 */
type FormField = {
  /** 字段属性名，对应表单数据中的 key */
  prop: string
  /** 字段标签文本 */
  label: string
  /** 字段类型 */
  type: FieldType
  /** 占位提示文本，不传则根据类型自动生成 */
  placeholder?: string
  /** 是否必填，为 true 时自动生成必填校验规则 */
  required?: boolean
  /** 自定义校验规则，优先级低于 props.rules 中同名字段的规则 */
  rules?: FormRules[string]
  /** 下拉选项列表，适用于 select 和 radio 类型 */
  options?: Array<{ label: string; value: any }>
  /** 数字输入框最小值，适用于 number 类型 */
  min?: number
  /** 数字输入框最大值，适用于 number 类型 */
  max?: number
  /** 文本域行数，适用于 textarea 类型，默认 3 */
  rows?: number
  /** switch 打开时的文字描述 */
  activeText?: string
  /** switch 关闭时的文字描述 */
  inactiveText?: string
}

const props = withDefaults(
  defineProps<{
    /** 控制弹窗显示/隐藏，支持 v-model 双向绑定 */
    modelValue: boolean
    /** 表单字段配置列表 */
    fields: FormField[]
    /** 表单数据对象，编辑时需传入包含 id 的对象，新增时传入空对象 */
    formData: Record<string, any>
    /** 弹窗标题，不传则根据是否编辑自动显示"新增"或"编辑" */
    title?: string
    /** 弹窗宽度 */
    width?: string
    /** 自定义校验规则，与 fields 中的 required/rules 合并，同名字段优先使用此处的规则 */
    rules?: FormRules
  }>(),
  {
    width: '600px',
  },
)

const emit = defineEmits<{
  /** 弹窗显示状态变化时触发，用于 v-model 双向绑定 */
  'update:modelValue': [value: boolean]
  /** 点击确认按钮且表单校验通过后触发，参数为表单数据（编辑时包含 id） */
  confirm: [data: Record<string, any>]
}>()

/** 表单实例引用，用于调用 validate、clearValidate 等方法 */
const formRef = ref<FormInstance>()

/**
 * 弹窗显示状态的双向绑定计算属性
 * 读取时返回 props.modelValue，设置时触发 update:modelValue 事件
 */
const dialogVisible = computed({
  get: () => props.modelValue,
  set: value => emit('update:modelValue', value),
})

/** 是否为编辑模式（根据 formData 中是否包含 id 判断） */
const isEdit = computed(() => !!props.formData?.id)

/** 弹窗标题，优先使用 props.title，否则编辑模式显示"编辑"、新增模式显示"新增" */
const dialogTitle = computed(() => {
  if (props.title) return props.title
  return isEdit.value ? '编辑' : '新增'
})

/** 响应式表单数据对象，根据 fields 配置动态初始化 */
const form = reactive<Record<string, any>>({})

/**
 * 合并后的表单校验规则
 * 合并逻辑：
 * 1. 以 props.rules 为基础
 * 2. 若字段配置了 required 且 props.rules 中无对应规则，则自动生成必填规则
 * 3. 若字段配置了自定义 rules 且 props.rules 中无对应规则，则使用字段的自定义规则
 * 注：props.rules 中的规则优先级最高，不会被字段级配置覆盖
 */
const mergedRules = computed(() => {
  const rules: FormRules = { ...props.rules }
  props.fields.forEach(field => {
    if (field.required && !rules[field.prop]) {
      rules[field.prop] = [
        {
          required: true,
          // 根据字段类型自动生成提示语：select/date 用"选择"，其他用"输入"
          message: `请${field.type === 'select' || field.type === 'date' ? '选择' : '输入'}${field.label}`,
          // 根据字段类型自动选择触发方式：switch/select/date 用 change，其他用 blur
          trigger: field.type === 'switch' ? 'change' : field.type === 'select' || field.type === 'date' ? 'change' : 'blur',
        },
      ]
    }
    if (field.rules && !rules[field.prop]) {
      rules[field.prop] = field.rules
    }
  })
  return rules
})

/**
 * 初始化表单数据
 * 1. 清空原有表单数据
 * 2. 根据 fields 配置逐字段初始化，使用 formData 中对应值或默认值
 *    - switch 类型默认 false
 *    - number 类型默认 undefined
 *    - 其他类型默认空字符串
 */
const initForm = () => {
  Object.keys(form).forEach(key => delete form[key])
  props.fields.forEach(field => {
    if (field.type === 'switch') {
      form[field.prop] = props.formData?.[field.prop] ?? false
    } else if (field.type === 'number') {
      form[field.prop] = props.formData?.[field.prop] ?? undefined
    } else {
      form[field.prop] = props.formData?.[field.prop] ?? ''
    }
  })
}

/**
 * 监听弹窗打开事件
 * 弹窗打开时：初始化表单数据，并在下一帧清除校验状态（避免残留的校验提示）
 */
watch(
  () => props.modelValue,
  val => {
    if (val) {
      initForm()
      nextTick(() => formRef.value?.clearValidate())
    }
  },
)

/**
 * 确认按钮点击处理
 * 1. 校验表单
 * 2. 校验通过后触发 confirm 事件，传递表单数据（编辑时附带 id）
 * 3. 关闭弹窗
 */
const handleConfirm = async () => {
  if (!formRef.value) return
  await formRef.value.validate(valid => {
    if (!valid) return
    emit('confirm', { ...form, id: props.formData?.id })
    dialogVisible.value = false
  })
}

/** 取消按钮点击处理，直接关闭弹窗 */
const handleCancel = () => {
  dialogVisible.value = false
}
</script>

<template>
  <!-- 弹窗容器：禁止点击遮罩关闭、关闭时销毁内容 -->
  <el-dialog
    v-model="dialogVisible"
    :title="dialogTitle"
    :width="width"
    :close-on-click-modal="false"
    destroy-on-close
  >
    <!-- 表单容器 -->
    <el-form
      ref="formRef"
      :model="form"
      :rules="mergedRules"
      label-width="100px"
    >
      <!-- 动态渲染表单项，根据 field.type 选择对应的输入组件 -->
      <el-form-item
        v-for="field in fields"
        :key="field.prop"
        :label="field.label"
        :prop="field.prop"
      >
        <!-- 文本输入框 -->
        <el-input
          v-if="field.type === 'input'"
          v-model="form[field.prop]"
          :placeholder="field.placeholder || `请输入${field.label}`"
        />

        <!-- 数字输入框 -->
        <el-input-number
          v-else-if="field.type === 'number'"
          v-model="form[field.prop]"
          :min="field.min"
          :max="field.max"
          :placeholder="field.placeholder || `请输入${field.label}`"
          style="width: 100%"
        />

        <!-- 下拉选择框 -->
        <el-select
          v-else-if="field.type === 'select'"
          v-model="form[field.prop]"
          :placeholder="field.placeholder || `请选择${field.label}`"
          style="width: 100%"
        >
          <el-option
            v-for="opt in field.options"
            :key="opt.value"
            :label="opt.label"
            :value="opt.value"
          />
        </el-select>

        <!-- 日期选择器 -->
        <el-date-picker
          v-else-if="field.type === 'date'"
          v-model="form[field.prop]"
          type="date"
          :placeholder="field.placeholder || `请选择${field.label}`"
          style="width: 100%"
        />

        <!-- 文本域 -->
        <el-input
          v-else-if="field.type === 'textarea'"
          v-model="form[field.prop]"
          type="textarea"
          :rows="field.rows || 3"
          :placeholder="field.placeholder || `请输入${field.label}`"
        />

        <!-- 开关 -->
        <el-switch
          v-else-if="field.type === 'switch'"
          v-model="form[field.prop]"
          :active-text="field.activeText"
          :inactive-text="field.inactiveText"
        />

        <!-- 单选框组 -->
        <el-radio-group
          v-else-if="field.type === 'radio'"
          v-model="form[field.prop]"
        >
          <el-radio
            v-for="opt in field.options"
            :key="opt.value"
            :value="opt.value"
          >
            {{ opt.label }}
          </el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>

    <!-- 底部按钮区域 -->
    <template #footer>
      <el-button @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleConfirm">确认</el-button>
    </template>
  </el-dialog>
</template>
