<template>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="formRules"
    :label-width="labelWidth"
    v-bind="$attrs"
  >
    <el-form-item
      v-for="(field, key) in schemaProperties"
      :key="key"
      :label="field.title || key"
      :prop="key"
    >
      <template #label>
        <span>{{ field.title || key }}</span>
        <el-tooltip v-if="field.description" :content="field.description" placement="top">
          <el-icon class="ml-1 cursor-help"><QuestionFilled /></el-icon>
        </el-tooltip>
      </template>
      <el-input
        v-if="field.type === 'string'"
        v-model="formData[key]"
        :placeholder="`请输入${field.title || key}`"
        clearable
        :show-password="isPasswordField(key)"
      />
      <el-input-number
        v-else-if="field.type === 'integer' || field.type === 'number'"
        v-model="formData[key]"
        :placeholder="`请输入${field.title || key}`"
        :min="field.minimum"
        :max="field.maximum"
        :step="field.type === 'number' ? 0.1 : 1"
        :precision="field.type === 'number' ? 2 : 0"
        controls-position="right"
        class="w-full"
      />
      <el-switch
        v-else-if="field.type === 'boolean'"
        v-model="formData[key]"
      />
      <el-select
        v-else-if="field.enum && field.enum.length > 0"
        v-model="formData[key]"
        :placeholder="`请选择${field.title || key}`"
        clearable
        class="w-full"
      >
        <el-option
          v-for="option in field.enum"
          :key="option"
          :label="option"
          :value="option"
        />
      </el-select>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { QuestionFilled } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'

interface SchemaProperty {
  type: string
  title?: string
  description?: string
  default?: any
  enum?: string[]
  minimum?: number
  maximum?: number
  required?: boolean
}

interface ConfigSchema {
  type?: string
  properties?: Record<string, SchemaProperty>
  required?: string[]
}

const props = defineProps<{
  schema: ConfigSchema
  modelValue: Record<string, any>
  labelWidth?: string
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', value: Record<string, any>): void
}>()

const formRef = ref<FormInstance>()
const formData = ref<Record<string, any>>({})
const isUpdating = ref(false)

const schemaProperties = computed(() => {
  return props.schema?.properties || {}
})

const requiredFields = computed(() => {
  const required = props.schema?.required || []
  const propsRequired = Object.entries(schemaProperties.value)
    .filter(([, field]) => field.required)
    .map(([key]) => key)
  return [...new Set([...required, ...propsRequired])]
})

const formRules = computed<FormRules>(() => {
  const rules: FormRules = {}
  Object.entries(schemaProperties.value).forEach(([key, field]) => {
    const fieldRules: any[] = []
    if (requiredFields.value.includes(key)) {
      fieldRules.push({
        required: true,
        message: `${field.title || key}不能为空`,
        trigger: ['blur', 'change']
      })
    }
    if (field.type === 'string' && field.minimum !== undefined) {
      fieldRules.push({
        min: field.minimum,
        message: `${field.title || key}长度不能小于${field.minimum}`,
        trigger: ['blur']
      })
    }
    if (field.type === 'string' && field.maximum !== undefined) {
      fieldRules.push({
        max: field.maximum,
        message: `${field.title || key}长度不能大于${field.maximum}`,
        trigger: ['blur']
      })
    }
    if (rules[key]) {
      rules[key] = fieldRules
    } else if (fieldRules.length > 0) {
      rules[key] = fieldRules
    }
  })
  return rules
})

const isPasswordField = (key: string): boolean => {
  const lowerKey = key.toLowerCase()
  return lowerKey.includes('password') || lowerKey.includes('secret') || lowerKey.includes('key') || lowerKey.includes('token')
}

const initFormData = () => {
  const data: Record<string, any> = {}
  Object.entries(schemaProperties.value).forEach(([key, field]) => {
    if (props.modelValue && props.modelValue[key] !== undefined) {
      data[key] = props.modelValue[key]
    } else if (field.default !== undefined) {
      data[key] = field.default
    } else {
      switch (field.type) {
        case 'string':
          data[key] = ''
          break
        case 'integer':
        case 'number':
          data[key] = field.minimum ?? 0
          break
        case 'boolean':
          data[key] = false
          break
        default:
          data[key] = null
      }
    }
  })
  formData.value = data
}

watch(
  () => props.modelValue,
  (newVal) => {
    if (isUpdating.value) return
    if (JSON.stringify(newVal) !== JSON.stringify(formData.value)) {
      initFormData()
    }
  },
  { deep: true }
)

watch(
  () => props.schema,
  () => {
    initFormData()
  },
  { deep: true }
)

watch(
  formData,
  (val) => {
    if (isUpdating.value) return
    if (JSON.stringify(val) !== JSON.stringify(props.modelValue)) {
      isUpdating.value = true
      emit('update:modelValue', val)
      Promise.resolve().then(() => {
        isUpdating.value = false
      })
    }
  },
  { deep: true }
)

onMounted(() => {
  initFormData()
})

defineExpose({
  validate: () => formRef.value?.validate(),
  resetFields: () => formRef.value?.resetFields(),
  clearValidate: () => formRef.value?.clearValidate()
})
</script>

<style scoped>
.w-full {
  width: 100%;
}
.ml-1 {
  margin-left: 4px;
}
.cursor-help {
  cursor: help;
}
</style>
