<template>
  <div class="vm-form" :class="[`is-label-${labelPosition}`]">
    <div
      v-for="item in visibleItems"
      :key="item.prop"
      class="vm-form__field"
      :class="{ 'is-error': fieldErrors[item.prop] }"
      :style="{ gridColumn: `span ${itemSpan(item)}` }"
    >
      <label v-if="item.label" class="vm-form__label" :style="labelStyle">
        {{ item.label }}
        <span
          v-if="resolveItemRequired(item, form)"
          class="vm-form__req"
        >*</span>
      </label>
      <component
        :is="resolveField(item)"
        v-if="resolveField(item)"
        v-bind="fieldProps(item)"
        :model-value="form[item.prop]"
        :disabled="item.disabled"
        :aria-invalid="fieldErrors[item.prop] ? true : undefined"
        @update:model-value="(v: unknown) => set(item.prop, v)"
      />
      <Input
        v-else-if="!item.type || item.type === 'input'"
        :ref="(el) => setFieldRef(item.prop, el)"
        class="vm-form__control"
        :placeholder="item.placeholder"
        :disabled="item.disabled"
        :aria-invalid="fieldErrors[item.prop] ? true : undefined"
        :model-value="String(form[item.prop] ?? '')"
        @update:model-value="(v) => set(item.prop, v)"
      />
      <textarea
        v-else-if="item.type === 'textarea'"
        class="vm-form__textarea"
        :placeholder="item.placeholder"
        :disabled="item.disabled"
        :aria-invalid="fieldErrors[item.prop] ? true : undefined"
        :value="String(form[item.prop] ?? '')"
        @input="set(item.prop, ($event.target as HTMLTextAreaElement).value)"
      />
      <p v-if="fieldErrors[item.prop]" class="vm-form__error">
        {{ fieldErrors[item.prop] }}
      </p>
    </div>
    <slot :form="form" />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, reactive } from 'vue'
import { resolveCrudComponent } from './registry'
import { getCrudStyle } from './style'
import { resolveFormSpan, useFormCols } from './span'
import { applySetRules, resolveItemRequired, validateFormFields } from './validate'
import type { CrudFormItem } from '../../../typings/admin/comm/crud'
defineOptions({ name: 'vm-form' })

const props = withDefaults(
  defineProps<{
    items?: CrudFormItem[]
    modelValue?: Record<string, unknown>
    labelPosition?: 'top' | 'left' | 'right'
    labelWidth?: string
  }>(),
  {
    items: () => [],
    modelValue: () => ({}),
  },
)

const emit = defineEmits<{
  'update:modelValue': [v: Record<string, unknown>]
}>()

const style = getCrudStyle()
const formCols = useFormCols()
const form = reactive<Record<string, unknown>>({ ...props.modelValue })
const fieldErrors = ref<Record<string, string>>({})
const refs: Record<string, { focus?: () => void } | null> = {}

const labelPosition = computed(
  () => props.labelPosition || style.form.labelPosition,
)
const labelStyle = computed(() =>
  labelPosition.value !== 'top'
    ? { width: props.labelWidth || style.form.labelWidth }
    : undefined,
)

/** required 默认补 rules；校验引擎本身也会读 required */
const visibleItems = computed(() => {
  const raw = applySetRules(props.items || [])
  return raw.filter((it) => {
    if (typeof it.hidden === 'function') return !it.hidden(form)
    return !it.hidden
  })
})

watch(
  () => props.modelValue,
  (v) => Object.assign(form, v || {}),
  { deep: true },
)

function clearFieldError(prop: string) {
  if (!fieldErrors.value[prop]) return
  const next = { ...fieldErrors.value }
  delete next[prop]
  fieldErrors.value = next
}

function set(prop: string, value: unknown) {
  form[prop] = value
  clearFieldError(prop)
  emit('update:modelValue', { ...form })
}

function itemSpan(item: CrudFormItem) {
  return resolveFormSpan(item.span, formCols.value)
}

function resolveField(item: CrudFormItem) {
  const name = item.component?.name
  if (typeof name === 'string' && name === 'upload') {
    return resolveCrudComponent('vm-upload')
  }
  return resolveCrudComponent(name)
}

function fieldProps(item: CrudFormItem) {
  return {
    options: resolveOptions(item.options),
    placeholder: item.placeholder,
    refreshOnChange: false,
    ...(item.component?.props || {}),
  }
}

function resolveOptions(options: CrudFormItem['options']) {
  const list = options == null ? [] : toValue(options)
  return Array.isArray(list) ? list : []
}

function setFieldRef(prop: string, el: unknown) {
  refs[prop] = el as { focus?: () => void } | null
}

function focus(prop?: string) {
  const name = prop || visibleItems.value[0]?.prop
  if (!name) return
  const el = refs[name] as { focus?: () => void; $el?: HTMLElement } | null
  el?.focus?.()
  const node = (el as { $el?: HTMLElement })?.$el?.querySelector?.('input')
  node?.focus?.()
}

function clearValidate() {
  fieldErrors.value = {}
}

/** 校验表单；必填等规则在字段下展示，不弹 toast。通过返回 true */
async function validate(): Promise<boolean> {
  const errors = await validateFormFields(visibleItems.value, form)
  fieldErrors.value = errors
  const first = Object.keys(errors)[0]
  if (first) focus(first)
  return Object.keys(errors).length === 0
}

defineExpose({ form, focus, set, validate, clearValidate, fieldErrors })
</script>

<style lang="scss" scoped>
.vm-form {
  display: grid;
  grid-template-columns: repeat(24, minmax(0, 1fr));
  gap: 12px;

  @media (max-width: 768px) {
    grid-template-columns: repeat(12, minmax(0, 1fr));
  }
}

.vm-form__field {
  display: grid;
  gap: 6px;

  &.is-error :deep(.vm-form__control),
  &.is-error .vm-form__textarea {
    border-color: var(--destructive, var(--danger, #c45b5e)) !important;
  }
}

.vm-form.is-label-left .vm-form__field,
.vm-form.is-label-right .vm-form__field {
  grid-template-columns: auto 1fr;
  align-items: start;
}

.vm-form__label {
  font-size: 12px;
  color: var(--muted-foreground);
}

.vm-form__req {
  color: var(--destructive, var(--danger, #c45b5e));
}

.vm-form__error {
  margin: 0;
  grid-column: 1 / -1;
  font-size: 12px;
  line-height: 1.4;
  color: var(--destructive, var(--danger, #c45b5e));
}

.vm-form.is-label-left .vm-form__error,
.vm-form.is-label-right .vm-form__error {
  grid-column: 2;
}

.vm-form__control,
.vm-form__textarea {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  font-size: 13px;
}

.vm-form__control {
  height: var(--vm-control-height);
  padding: 0 var(--vm-control-padding-x);
}

.vm-form__textarea {
  min-height: 72px;
  padding: 8px var(--vm-control-padding-x);
}
</style>
