<script lang="ts" setup>
import type { Ref } from 'vue'
import { computed, nextTick, onMounted, reactive, ref, unref, useAttrs, watch } from 'vue'
import { type FormProps as AntFormProps, Form, Row } from 'ant-design-vue'
import { useDebounceFn } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'

import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue'

import { dateItemType } from './helper'

import { useFormValues } from './hooks/useFormValues'
import useAdvanced from './hooks/useAdvanced'
import { useFormEvents } from './hooks/useFormEvents'
import { createFormContext } from './hooks/useFormContext'
import { useAutoFocus } from './hooks/useAutoFocus'
import { basicProps } from './props'
import { useModalContext } from '@/components/Modal'

import { deepMerge } from '@/utils'
import { dateUtil } from '@/utils/dateUtil'
import { useDesign } from '@/hooks/web/useDesign'

defineOptions({ name: 'BasicForm' })

const props = defineProps(basicProps)
const emit = defineEmits(['advanced-change', 'reset', 'submit', 'register', 'field-value-change'])
const attrs = useAttrs()
const formModel = reactive({})
const modalFn = useModalContext()

const advanceState = reactive<AdvanceState>({
  isAdvanced: true,
  hideAdvanceBtn: false,
  isLoad: false,
  actionSpan: 6,
})

const defaultValueRef = ref({})
const isInitedDefaultRef = ref(false)
const propsRef = ref<Partial<FormProps>>()
const schemaRef = ref<FormSchema[] | null>(null)
const formElRef = ref<FormActionType | null>(null)

const { prefixCls } = useDesign('basic-form')

// Get the basic configuration of the form
const getProps = computed(() => {
  return { ...props, ...unref(propsRef) } as FormProps
})

const getFormClass = computed(() => {
  return [
    prefixCls,
    {
      [`${prefixCls}--compact`]: unref(getProps).compact,
    },
  ]
})

// Get uniform row style and Row configuration for the entire form
const getRow = computed(() => {
  const { baseRowStyle = {}, rowProps } = unref(getProps)
  return {
    style: baseRowStyle,
    ...rowProps,
  }
})

const getBindValue = computed(
  () => ({ ...attrs, ...props, ...unref(getProps) }) as AntFormProps,
)

const getSchema = computed((): FormSchema[] => {
  const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
  for (const schema of schemas) {
    const {
      defaultValue,
      component,
      componentProps,
      isHandleDateDefaultValue = true,
    } = schema

    const valueFormat = componentProps ? componentProps.valueFormat : null
    // handle date type
    if (
      isHandleDateDefaultValue
      && defaultValue
      && component
      && dateItemType.includes(component)
    ) {
      if (!Array.isArray(defaultValue)) {
        schema.defaultValue = valueFormat
          ? dateUtil(defaultValue).format(valueFormat)
          : dateUtil(defaultValue)
      }
      else {
        const def: any[] = []
        defaultValue.forEach((item) => {
          def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item))
        })
        schema.defaultValue = def
      }
    }
  }
  if (unref(getProps).showAdvancedButton) {
    return cloneDeep(
      schemas.filter(schema => schema.component !== 'Divider') as FormSchema[],
    )
  }
  else {
    return cloneDeep(schemas as FormSchema[])
  }
})

const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
  advanceState,
  emit,
  getProps,
  getSchema,
  formModel,
  defaultValueRef,
})

const { handleFormValues, initDefault } = useFormValues({
  getProps,
  defaultValueRef,
  getSchema,
  formModel,
})

useAutoFocus({
  getSchema,
  getProps,
  isInitedDefault: isInitedDefaultRef,
  formElRef: formElRef as Ref<FormActionType>,
})

const {
  handleSubmit,
  setFieldsValue,
  clearValidate,
  validate,
  validateFields,
  getFieldsValue,
  updateSchema,
  resetSchema,
  appendSchemaByField,
  removeSchemaByField,
  resetFields,
  scrollToField,
} = useFormEvents({
  emit,
  getProps,
  formModel,
  getSchema,
  defaultValueRef,
  formElRef: formElRef as Ref<FormActionType>,
  schemaRef: schemaRef as Ref<FormSchema[]>,
  handleFormValues,
})

createFormContext({
  resetAction: resetFields,
  submitAction: handleSubmit,
})

watch(
  () => unref(getProps).model,
  () => {
    const { model } = unref(getProps)
    if (!model)
      return
    setFieldsValue(model)
  },
  {
    immediate: true,
  },
)

watch(
  () => unref(getProps).schemas,
  (schemas) => {
    resetSchema(schemas ?? [])
  },
)

watch(
  () => getSchema.value,
  (schema) => {
    nextTick(() => {
      //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
      modalFn?.redoModalHeight?.()
    })
    if (unref(isInitedDefaultRef))
      return

    if (schema?.length) {
      initDefault()
      isInitedDefaultRef.value = true
    }
  },
)

watch(
  () => formModel,
  useDebounceFn(() => {
    unref(getProps).submitOnChange && handleSubmit()
  }, 300),
  { deep: true },
)

async function setProps(formProps: Partial<FormProps>): Promise<void> {
  propsRef.value = deepMerge(unref(propsRef) || {}, formProps)
}

function setFormModel(key: string, value: any, schema: FormSchema) {
  formModel[key] = value
  emit('field-value-change', key, value)
  // TODO 优化验证，这里如果是autoLink=false手动关联的情况下才会再次触发此函数
  if (schema && schema.itemProps && !schema.itemProps.autoLink)
    validateFields([key]).catch((_) => {})
}

function handleEnterPress(e: KeyboardEvent) {
  const { autoSubmitOnEnter } = unref(getProps)
  if (!autoSubmitOnEnter)
    return
  if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
    const target: HTMLElement = e.target as HTMLElement
    if (target && target.tagName && target.tagName.toUpperCase() === 'INPUT')
      handleSubmit()
  }
}

const formActionType: FormActionType = {
  getFieldsValue,
  setFieldsValue,
  resetFields,
  updateSchema,
  resetSchema,
  setProps,
  removeSchemaByField,
  appendSchemaByField,
  clearValidate,
  validateFields,
  validate,
  submit: handleSubmit,
  scrollToField,
}

onMounted(() => {
  initDefault()
  if (props.register) {
    props.register(formActionType)
  }
  else {
    emit('register', formActionType)
  }
})

const getFormActionBindProps = computed(() => ({ ...getProps.value, ...advanceState }) as InstanceType<typeof FormAction>['$props'])
</script>

<template>
  <Form
    v-bind="getBindValue"
    ref="formElRef"
    :class="getFormClass"
    :model="formModel"
    @keypress.enter="handleEnterPress"
  >
    <Row v-bind="getRow">
      <slot name="formHeader" />
      <template v-for="schema in getSchema" :key="schema.field">
        <FormItem
          :is-advanced="fieldsIsAdvancedMap[schema.field]"
          :table-action="tableAction"
          :form-action-type="formActionType"
          :schema="schema"
          :form-props="getProps"
          :all-default-values="defaultValueRef"
          :form-model="formModel"
          :set-form-model="setFormModel"
        >
          <template v-for="item in Object.keys($slots)" #[item]="data">
            <slot :name="item" v-bind="data || {}" />
          </template>
        </FormItem>
      </template>

      <FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
        <template
          v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
          #[item]="data"
        >
          <slot :name="item" v-bind="data || {}" />
        </template>
      </FormAction>
      <slot name="formFooter" />
    </Row>
  </Form>
</template>

<style lang="less">
@prefix-cls: ~'@{namespace}-basic-form';

.@{prefix-cls} {
  .ant-form-item {
    &-label label::after {
      margin: 0 6px 0 2px;
    }

    &.suffix-item {
      .ant-form-item-children {
        display: flex;
      }

      .ant-form-item-control {
        margin-top: 4px;
      }

      .suffix {
        display: inline-flex;
        align-items: center;
        padding-left: 6px;
        margin-top: 1px;
        line-height: 1;
      }
    }
  }

  .ant-form-explain {
    font-size: 14px;
  }

  &--compact {
    .ant-form-item {
      margin-bottom: 8px !important;
    }
  }
}
</style>
