<script lang="ts">
import { computed, defineComponent, onMounted, reactive, ref, toRaw, unref, watch } from 'vue';
import type { Ref } from 'vue';
import type { ButtonProps, FormItemRule } from 'naive-ui';
import { DownOutlined, QuestionCircleOutlined, UpOutlined } from '@vicons/antd';
import type { GridProps } from 'naive-ui/lib/grid';
import { areAllItemsAllFieldsFilled, deepMerge } from '@/utils/zt';
import { isBoolean, isFunction } from '@/utils/zt/is';
import { createPlaceholderMessage } from './helper';
import { useFormEvents } from './hooks/useFormEvents';
import { useFormValues } from './hooks/useFormValues';
import { basicFormProps } from './props';
import type { FormActionType, FormProps, FormSchema } from './types/form';
import { componentMap } from './types/form';

export default defineComponent({
  name: 'BasicForm',
  components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
  props: {
    ...basicFormProps
  },
  emits: ['reset', 'submit', 'registerForm'],
  setup(props, { emit, attrs }) {
    const defaultFormModel = ref<Recordable>({});
    const formModel = reactive<Recordable>({});
    const propsRef = ref<Partial<FormProps>>({});
    const schemaRef = ref<Nullable<FormSchema[]>>(null);
    const formElRef = ref<Nullable<FormActionType>>(null);
    const gridCollapsed = ref(true);
    const loadingSub = ref(false);
    const isUpdateDefaultRef = ref(false);
    const propsData = toRaw(props);

    const getSubmitBtnOptions = computed(() => {
      return {
        size: propsData.size,
        type: 'primary',
        ...propsData.submitButtonOptions
      } as ButtonProps;
    });

    const getResetBtnOptions = computed(() => {
      return {
        size: propsData.size,
        type: 'default',
        ...propsData.resetButtonOptions
      } as ButtonProps;
    });
    function getComponentValue(schema: FormSchema) {
      const timeArr = ['datetime', 'datetimerange', 'daterange', 'date'];
      if (schema.component == 'NDatePicker' && timeArr.includes(String(schema.componentProps?.type))) {
        return 'formatted-value';
      }
      return 'value';
    }

    function getComponentProps(schema: FormSchema) {
      const compProps = schema.componentProps ?? {};
      const component = schema.component;
      const isTime = component == 'NDatePicker';
      const newObj: Record<string, any> = {
        clearable: true,
        placeholder: createPlaceholderMessage(unref(component), String(schema.label)),
        ...compProps
      };
      if (isTime && !schema.componentProps?.valueFormat) {
        newObj.valueFormat = 'yyyy-MM-dd HH:mm:ss';
      }

      return newObj;
    }

    const getProps = computed((): FormProps => {
      // 分步处理属性，避免一次性合并导致的类型推断压力
      const baseProps = props as FormProps;
      const refProps = unref(propsRef) as Partial<FormProps>;

      const rawProps: FormProps = { ...baseProps, ...refProps };

      const rulesObj: { rules: Record<string, any> } = {
        rules: {}
      };

      const schemas: FormSchema[] = rawProps.schemas || [];
      schemas.forEach(item => {
        if (item.rules && Array.isArray(item.rules)) {
          rulesObj.rules[item.field] = item.rules;
        }
      });

      return { ...rawProps, ...rulesObj };
    });

    const isInline = computed(() => {
      const { layout } = unref(getProps);
      return layout === 'inline';
    });

    const getGrid = computed((): GridProps => {
      const { gridProps } = unref(getProps);
      return {
        ...gridProps,
        collapsed: isInline.value ? gridCollapsed.value : false,
        responsive: 'screen'
      };
    });

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

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

        if (defaultValue !== undefined && defaultValue !== null) {
          schema.defaultValue = defaultValue;
        }
      }

      return schemas as FormSchema[];
    });
    function getRule(schema: FormSchema): FormItemRule | FormItemRule[] | undefined {
      if (schema.required && !schema.rules) {
        return {
          required: true,
          message: `${schema.label}不能为空`,
          trigger: ['blur', 'input', 'change'],
          validator: () => {
            const value = formModel[schema.field];
            // 数组校验
            if (Array.isArray(value)) {
              if (value.length === 0) {
                return new Error(`${schema.label}不能为空`);
              }
              if (!areAllItemsAllFieldsFilled(value)) {
                return new Error(`${schema.label}中存在空项，请完善所有项目`);
              }

              return true;
            }

            // 非数组的原有校验逻辑
            if (value === undefined || value === null || value === '') {
              return new Error(`${schema.label}不能为空`);
            }
            return true;
          }
        };
      }
      return schema.rules;
    }
    function getShow(schema: FormSchema) {
      if (isBoolean(schema.show)) {
        return schema.show;
      }
      if (isFunction(schema.ifShow)) {
        return schema.ifShow({
          schema,
          model: formModel,
          field: schema.field,
          values: formModel[schema.field]
        });
      }
      return true;
    }
    const { handleFormValues, initDefault } = useFormValues({
      defaultFormModel,
      getSchema,
      formModel
    });
    const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue, updateSchema } =
      useFormEvents({
        emit,
        getProps,
        formModel,
        getSchema,
        formElRef: formElRef as Ref<FormActionType>,
        defaultFormModel,
        loadingSub,
        schemaRef: schemaRef as Ref<FormSchema[]>,
        handleFormValues
      });

    function unfoldToggle() {
      gridCollapsed.value = !gridCollapsed.value;
    }

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

    function render(schema: FormSchema) {
      // 如果 schema 中定义了 render 函数，则使用自定义 render 函数
      if (schema.render && isFunction(schema.render)) {
        return schema.render({
          schema,
          model: formModel,
          field: schema.field,
          values: formModel[schema.field]
        });
      }

      // 如果没有定义 render 函数，返回 null 或 undefined
      return null;
    }
    const formActionType: Partial<FormActionType> = {
      getFieldsValue,
      setFieldsValue,
      resetFields,
      validate,
      clearValidate,
      setFormProps,
      submit: handleSubmit,
      updateSchema
    };

    watch(
      () => getSchema.value,
      schema => {
        if (unref(isUpdateDefaultRef)) {
          return;
        }
        if (schema.length) {
          initDefault();
          isUpdateDefaultRef.value = true;
        }
      }
    );

    onMounted(() => {
      initDefault();
      emit('registerForm', formActionType);
    });

    return {
      formElRef,
      formModel,
      getGrid,
      getProps,
      getBindValue,
      getSchema,
      getSubmitBtnOptions,
      getResetBtnOptions,
      gridCollapsed,
      handleSubmit,
      resetFields,
      loadingSub,
      isInline,
      getComponentProps,
      unfoldToggle,
      componentMap,
      getRule,
      getComponentValue,
      getShow,
      render
    };
  }
});
</script>

<template>
  <NForm v-bind="getBindValue" ref="formElRef" :model="formModel">
    <NGrid v-bind="getGrid">
      <NGi v-for="schema in getSchema" v-bind="schema.giProps" :key="schema.field">
        <NFormItem
          v-if="getShow(schema)"
          :label="schema.label"
          :path="schema.field"
          :required="schema.required"
          :rule="getRule(schema)"
        >
          <!--标签名右侧温馨提示-->
          <template v-if="schema.labelMessage" #label>
            {{ schema.label }}
            <NTooltip trigger="hover" :style="schema.labelMessageStyle">
              <template #trigger>
                <NIcon size="18" class="cursor-pointer text-gray-400">
                  <QuestionCircleOutlined />
                </NIcon>
              </template>
              {{ schema.labelMessage }}
            </NTooltip>
          </template>
          <template v-if="render(schema)">
            <component :is="render(schema)" />
          </template>
          <template v-else>
            <!--判断插槽-->
            <template v-if="schema.slot">
              <slot
                :name="schema.slot"
                :model="formModel"
                :field="schema.field"
                :value="formModel[schema.field]"
              ></slot>
            </template>

            <!--NCheckbox-->
            <template v-else-if="schema.component === 'NCheckboxGroup'">
              <NCheckboxGroup v-model:value="formModel[schema.field]" v-bind="schema.componentProps">
                <NSpace>
                  <NCheckbox
                    v-for="item in schema.componentProps!.options"
                    :key="item.value"
                    :value="item.value"
                    :label="item.label"
                  />
                </NSpace>
              </NCheckboxGroup>
            </template>

            <!--NRadioGroup-->
            <template v-else-if="schema.component === 'NRadioGroup'">
              <NRadioGroup v-model:value="formModel[schema.field]" v-bind="schema.componentProps">
                <NSpace>
                  <NRadio v-for="item in schema.componentProps!.options" :key="item.value" :value="item.value">
                    {{ item.label }}
                  </NRadio>
                </NSpace>
              </NRadioGroup>
            </template>
            <!--动态渲染表单组件-->
            <template v-else>
              <component
                v-bind="getComponentProps(schema)"
                :is="componentMap[schema.component]"
                v-model:[`${getComponentValue(schema)}`]="formModel[schema.field]"
                :class="{ isFull: schema.isFull != false && getProps.isFull }"
              />
            </template>
          </template>

          <!--组件后面的内容-->
          <template v-if="schema.suffix">
            <slot
              :name="schema.suffix"
              :model="formModel"
              :field="schema.field"
              :value="formModel[schema.field]"
            ></slot>
          </template>
        </NFormItem>
      </NGi>
      <!--提交 重置 展开 收起 按钮-->
      <NGi
        v-if="getProps.showActionButtonGroup"
        :span="isInline ? '' : 24"
        :suffix="isInline ? true : false"
        #="{ gridCollapsed }"
      >
        <NSpace
          align="center"
          :justify="isInline ? 'end' : 'start'"
          :style="{ 'margin-left': `${isInline ? 12 : getProps.labelWidth}px` }"
        >
          <NButton
            v-if="getProps.showSubmitButton"
            v-bind="getSubmitBtnOptions"
            :loading="loadingSub"
            attr-type="submit"
            @click="handleSubmit"
          >
            {{ getProps.submitButtonText }}
          </NButton>

          <NButton v-if="getProps.showResetButton" v-bind="getResetBtnOptions" @click="resetFields">
            {{ getProps.resetButtonText }}
          </NButton>
          <NButton
            v-if="isInline && getProps.showAdvancedButton"
            type="primary"
            text
            icon-placement="right"
            @click="unfoldToggle"
          >
            <template #icon>
              <NIcon v-if="gridCollapsed" size="14" class="unfold-icon">
                <DownOutlined />
              </NIcon>
              <NIcon v-else size="14" class="unfold-icon">
                <UpOutlined />
              </NIcon>
            </template>
            {{ gridCollapsed ? '展开' : '收起' }}
          </NButton>
        </NSpace>
      </NGi>
    </NGrid>
  </NForm>
</template>

<style scoped>
.isFull {
  width: 100%;
  justify-content: flex-start;
}

.unfold-icon {
  display: flex;
  align-items: center;
  height: 100%;
  margin-left: -3px;
}
</style>
