<template>
  <view :class="`wd-form ${customClass}`" :style="customStyle">
    <slot></slot>
    <wd-toast v-if="props.errorType === 'toast'" selector="wd-form-toast" />
  </view>
</template>

<script lang="ts">
export default {
  name: 'wd-form',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import wdToast from '../wd-toast/wd-toast.vue'
import { reactive, watch } from 'vue'
import { deepClone, getPropByPath, isArray, isDef, isPromise, isString } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { useToast } from '../wd-toast'
import { type FormRules, FORM_KEY, type ErrorMessage, formProps, type FormExpose } from './types'

const { show: showToast } = useToast('wd-form-toast')
const props = defineProps(formProps)

const { children, linkChildren } = useChildren(FORM_KEY)
let errorMessages = reactive<Record<string, string>>({})

linkChildren({ props, errorMessages })

watch(
  () => props.model,
  () => {
    if (props.resetOnChange) {
      clearMessage()
    }
  },
  { immediate: true, deep: true }
)

/**
 * 表单校验
 * @param prop 指定校验字段或字段数组
 */
async function validate(prop?: string | string[]): Promise<{ valid: boolean; errors: ErrorMessage[] }> {
  const errors: ErrorMessage[] = []
  let valid: boolean = true
  const promises: Promise<void>[] = []
  const formRules: FormRules = getMergeRules()
  const propsToValidate = isArray(prop) ? prop : isDef(prop) ? [prop] : []
  const rulesToValidate: FormRules =
    propsToValidate.length > 0
      ? propsToValidate.reduce((acc, key) => {
          if (formRules[key]) {
            acc[key] = formRules[key]
          }
          return acc
        }, {} as FormRules)
      : formRules

  for (const propName in rulesToValidate) {
    const rules = rulesToValidate[propName]
    const value = getPropByPath(props.model, propName)

    if (rules && rules.length > 0) {
      for (const rule of rules) {
        if (rule.required && (!isDef(value) || value === '')) {
          errors.push({
            prop: propName,
            message: rule.message
          })
          valid = false
          break
        }
        if (rule.pattern && !rule.pattern.test(value)) {
          errors.push({
            prop: propName,
            message: rule.message
          })
          valid = false
          break
        }
        const { validator, ...ruleWithoutValidator } = rule
        if (validator) {
          const result = validator(value, ruleWithoutValidator)
          if (isPromise(result)) {
            promises.push(
              result
                .then((res) => {
                  if (typeof res === 'string') {
                    errors.push({
                      prop: propName,
                      message: res
                    })
                    valid = false
                  } else if (typeof res === 'boolean' && !res) {
                    errors.push({
                      prop: propName,
                      message: rule.message
                    })
                    valid = false
                  }
                })
                .catch((error?: string | Error) => {
                  const message = isDef(error) ? (isString(error) ? error : error.message || rule.message) : rule.message
                  errors.push({ prop: propName, message })
                  valid = false
                })
            )
          } else {
            if (!result) {
              errors.push({
                prop: propName,
                message: rule.message
              })
              valid = false
            }
          }
        }
      }
    }
  }

  await Promise.all(promises)

  showMessage(errors)

  if (valid) {
    if (propsToValidate.length) {
      propsToValidate.forEach(clearMessage)
    } else {
      clearMessage()
    }
  }

  return {
    valid,
    errors
  }
}

// 合并子组件的rules到父组件的rules
function getMergeRules() {
  const mergedRules: FormRules = deepClone(props.rules)
  const childrenProps = children.map((child) => child.prop)

  // 过滤掉在 children 中不存在对应子组件的规则
  Object.keys(mergedRules).forEach((key) => {
    if (!childrenProps.includes(key)) {
      delete mergedRules[key]
    }
  })

  children.forEach((item) => {
    if (isDef(item.prop) && isDef(item.rules) && item.rules.length) {
      if (mergedRules[item.prop]) {
        mergedRules[item.prop] = [...mergedRules[item.prop], ...item.rules]
      } else {
        mergedRules[item.prop] = item.rules
      }
    }
  })
  return mergedRules
}

function showMessage(errors: ErrorMessage[]) {
  const childrenProps = children.map((e) => e.prop).filter(Boolean)
  const messages = errors.filter((error) => error.message && childrenProps.includes(error.prop))
  if (messages.length) {
    messages.sort((a, b) => {
      return childrenProps.indexOf(a.prop) - childrenProps.indexOf(b.prop)
    })
    if (props.errorType === 'toast') {
      showToast(messages[0].message)
    } else if (props.errorType === 'message') {
      messages.forEach((error) => {
        errorMessages[error.prop] = error.message
      })
    }
  }
}

function clearMessage(prop?: string) {
  if (prop) {
    errorMessages[prop] = ''
  } else {
    Object.keys(errorMessages).forEach((key) => {
      errorMessages[key] = ''
    })
  }
}

/**
 * 重置表单项的验证提示
 */
function reset() {
  clearMessage()
}

defineExpose<FormExpose>({ validate, reset })
</script>

<style lang="scss" scoped></style>
