<template>
  <a-form ref="schemaFormRef" v-bind="getFormProps" :model="formModel">
    <a-row v-bind="getRowConfig">
      <template
        v-for="schemaItem in formSchemaRef.schemas"
        :key="schemaItem.field"
      >
        <SchemaFormItem
          v-if="getFormItemIsShow(schemaItem, 'vIf')"
          v-show="getFormItemIsShow(schemaItem, 'vShow')"
          :schema-item="schemaItem"
          :schema="formSchemaRef"
          :set-form-model="setFormModel"
          :form-model="formModel"
          :setItemRef="setItemRef(schemaItem)"
          :view-mode="viewMode"
        >
          <template
            v-for="item in Object.keys($slots)"
            #[item]="data"
            :key="item"
          >
            <slot :name="item" v-bind="data || {}"></slot>
          </template>
        </SchemaFormItem>
      </template>
    </a-row>
    <a-row v-bind="getRowConfig">
      <a-col :span="24">
        <template v-if="$slots['operate-button']">
          <a-form-item
            :wrapper-col="formSchemaRef.actionColOptions"
            class="operate-button"
          >
            <slot name="operate-button"></slot>
          </a-form-item>
        </template>
      </a-col>
    </a-row>
  </a-form>
</template>

<script lang="ts" setup>
import {
  reactive,
  ref,
  PropType,
  unref,
  computed,
  watchEffect,
  useAttrs,
  watch,
} from "vue";
import { isNullOrUnDef, isObject, isArray, isFunction } from "./utils/is";
import SchemaFormItem from "./schema-form-item.vue";
import type { FormItemSchema, FormSchema, FormActionType } from "./types/form";
import { NamePath } from "ant-design-vue/lib/form/interface";
import { merge, uniqBy, cloneDeep, isBoolean, mergeWith } from "lodash";
import { dateItemType, handleInputNumberValue } from "./support";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
dayjs.locale("zh-cn");
const emit = defineEmits(["submit", "reset", "change"]);

const props = defineProps({
  formSchema: {
    // 动态验证表单
    type: Object as PropType<FormSchema>,
    default: () => ({}),
  },
  initialValues: {
    // 预置字段默认值
    type: Object as PropType<Recordable>,
    default: () => ({}),
  },
  // 只读模式
  viewMode: {
    type: Boolean,
    default: false,
  },
});

let oldFormSchema: FormSchema;
// TODO 将formSchema克隆一份，避免修改原有的formSchema
// TODO 类型为FormSchema 提示：类型实例化过深，且可能无限
const formSchemaRef = ref<any>({});
// 表单项数据
const formModel = reactive({ ...props.initialValues });
// 表单默认数据
const defaultFormValues = reactive({ ...props.initialValues });
// 表单实例
const schemaFormRef = ref<FormActionType>();
// 表单属性
const schemaFormPropsRef = ref<Partial<FormSchema>>({});
// 缓存的表单值，用于恢复form-item v-if为true后的值
const cacheFormModel = { ...props.initialValues };
// 将所有的表单组件实例保存起来
const compRefs = {} as any;
// const vMode = props.viewMode;
const attrs = useAttrs();
// 获取表单所有属性
const getFormProps = computed(() => {
  return {
    ...attrs,
    ...props,
    ...schemaFormPropsRef.value,
  } as FormSchema;
});

watchEffect(() => {
  if (Object.is(props.formSchema, oldFormSchema)) {
    // console.log("相同");
    merge(formSchemaRef.value, cloneDeep(props.formSchema));
  } else {
    // console.log("不相同");
    formSchemaRef.value = cloneDeep(props.formSchema);
  }
  oldFormSchema = props.formSchema;
});

// 初始化数据
unref(formSchemaRef).schemas?.forEach((item) => {
  const { defaultValue } = item;
  if (!isNullOrUnDef(defaultValue)) {
    formModel[item.field] = defaultValue;
    defaultFormValues[item.field] = defaultValue;
    cacheFormModel[item.field] = defaultValue;
  }
});
// 将所有的表单组件实例保存起来
const setItemRef = (formItem: FormItemSchema) => {
  return (el) => {
    if (el) {
      compRefs[formItem.field] = el;
    }
  };
};

const getFormItemIsShow = (formItem: FormItemSchema, key: "vIf" | "vShow") => {
  if (Reflect.has(formItem, key)) {
    const isShow: any = formItem[key];
    let show = true;
    if (isBoolean(isShow)) {
      show = isShow;
    }
    if (isFunction(isShow)) {
      show = isShow({
        schemaItem: formItem,
        formModel,
        field: formItem.field,
      });
    }
    if (Object.is(show, false) && key === "vIf") {
      formModel[formItem.field] = undefined;
    } else if (Object.is(show, true) && key === "vIf") {
      formModel[formItem.field] = cacheFormModel[formItem.field];
    }
    return show;
  } else {
    return true;
  }
};

// 设置表单属性
const setFormProps = (formProps: Partial<FormSchema>) => {
  Object.assign(schemaFormPropsRef.value, formProps);
};

// 设置某个字段的值
const setFormModel = (key: string, value: any) => {
  formModel[key] = value;
  cacheFormModel[key] = value;
  const { validateTrigger } = unref(getFormProps);
  if (!validateTrigger || validateTrigger === "change") {
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    schemaFormRef.value?.validateFields([key]).catch((_) => {});
  }
};
// 获取栅栏Row配置
const getRowConfig = computed((): Recordable => {
  const formProps: any = unref(getFormProps);
  console.debug("rowProps", formProps);
  return {
    style: {},
    ...formProps.formSchema.rowProps,
  };
});

// 监听FORM表单变化

watch(
  formModel,
  () => {
    emit("change", formModel);
  },
  { deep: true }
);

/**
 * @description: Is it time
 */
function itemIsDateType(key: string) {
  return unref(formSchemaRef).schemas.some((item) => {
    return item.field === key ? dateItemType.includes(item.component) : false;
  });
}

/**
 * @description: Set form value
 */
async function setFieldsValue(values: Recordable): Promise<void> {
  const schemas = unref(formSchemaRef).schemas;
  const fields = schemas.map((item) => item.field).filter(Boolean);

  Object.assign(cacheFormModel, values);

  const validKeys: string[] = [];
  Object.keys(values).forEach((key) => {
    const schema = schemas.find((item) => item.field === key);
    let value = values[key];

    const hasKey = Reflect.has(values, key);
    value = handleInputNumberValue(schema?.component, value);
    // 0| '' is allow
    if (hasKey && fields.includes(key)) {
      // time type
      if (itemIsDateType(key)) {
        if (Array.isArray(value)) {
          const arr: any[] = [];
          for (const ele of value) {
            arr.push(ele ? dayjs(ele) : null);
          }
          formModel[key] = arr;
        } else {
          const { componentProps } = schema || {};
          let _props = componentProps as any;
          if (typeof componentProps === "function") {
            _props = _props({ formModel });
          }
          formModel[key] = value
            ? _props?.valueFormat
              ? value
              : dayjs(value)
            : null;
        }
      } else {
        formModel[key] = value;
      }
      validKeys.push(key);
    }
  });
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  validateFields(validKeys).catch((_) => {});
}

async function resetSchema(
  data: Partial<FormItemSchema> | Partial<FormItemSchema>[]
) {
  let updateData: Partial<FormItemSchema>[] = [];
  if (isObject(data)) {
    updateData.push(data as FormItemSchema);
  }
  if (isArray(data)) {
    updateData = [...data];
  }

  unref(formSchemaRef).schemas = updateData as FormItemSchema[];
}

/**
 * @description  更新formItemSchema
 */
async function updateSchema(
  data: Partial<FormItemSchema> | Partial<FormItemSchema>[]
) {
  let updateData: Partial<FormItemSchema>[] = [];
  if (isObject(data)) {
    updateData.push(data as FormItemSchema);
  }
  if (isArray(data)) {
    updateData = [...data];
  }

  const hasField = updateData.every(
    (item) =>
      item.component === "Divider" || (Reflect.has(item, "field") && item.field)
  );

  if (!hasField) {
    console.error(
      "All children of the form Schema array that need to be updated must contain the `field` field"
    );
    return;
  }
  const schema: FormItemSchema[] = [];
  updateData.forEach((item) => {
    unref(formSchemaRef).schemas.forEach((val) => {
      if (val.field === item.field) {
        // const newSchema = merge(val, item);

        const newSchema = mergeWith(val, item, (a, b) =>
          isArray(b) ? b : undefined
        );
        schema.push(newSchema);
      } else {
        schema.push(val);
      }
    });
  });
  unref(formSchemaRef).schemas = uniqBy(schema, "field");
}

async function resetFields(): Promise<void> {
  const { resetFunc, submitOnReset } = unref(getFormProps);
  resetFunc && isFunction(resetFunc) && (await resetFunc());

  // console.debug("reset form",defaultFormValues);
  Object.keys(formModel).forEach((key) => {
    formModel[key] = defaultFormValues[key];
  });

  emit("reset", formModel);
  submitOnReset && handleSubmit();
  setTimeout(clearValidate);
}

async function validateFields(nameList?: NamePath[] | undefined) {
  return schemaFormRef.value?.validateFields(nameList);
}

async function validate(nameList?: NamePath[] | undefined) {
  await schemaFormRef.value?.validate(nameList);
  //convert rangePicker
  await rangeDateFormat(formModel);
  return formModel;
}

async function rangeDateFormat(values: Recordable): Promise<void> {
  const schemas = unref(formSchemaRef).schemas;
  const fields = schemas.map((item) => item.field).filter(Boolean);

  Object.keys(values).forEach((key) => {
    const schema = schemas.find((item) => item.field === key);
    let value = values[key];
    //过滤自定义字段
    if (fields.includes(key)) {
      const { component, componentProps } = schema;
      if (
        component == "RangePicker" &&
        value &&
        isArray(value) &&
        (componentProps as any).valueFormat
      ) {
        const format = (componentProps as any).valueFormat;
        formModel[key] = [
          dayjs(value[0]).format(format),
          dayjs(value[1]).format(format),
        ];
      }
    }
  });
}

async function clearValidate(name?: string | string[]) {
  await schemaFormRef.value?.clearValidate(name);
}

async function scrollToField(
  name: NamePath,
  options?: ScrollOptions | undefined
) {
  await schemaFormRef.value?.scrollToField(name, options);
}

const handleSubmit = () => {
  emit("submit");
};

defineExpose({
  formModel,
  validateFields,
  resetFields,
  validate,
  setFieldsValue,
  setFormModel,
  updateSchema,
  scrollToField,
  resetSchema,
});
</script>
