<!-- @format -->

<template>
  <a-form
    class="ns-form"
    :class="getFormClass.class"
    v-bind="getBindValue"
    ref="formElRef"
    :model="formModel">
    <div v-if="showAction && showExpandAll" class="ns-form-title ns-title-extra-box">
      <span> {{ title ? title : '查询' }}</span>
      <a-button type="link" class="ns-operate-expand" @click="expandAll = !expandAll">
        <template v-if="expandAll">
          收起筛选
          <UpOutlined />
        </template>
        <template v-else>
          展开筛选
          <DownOutlined />
        </template>
      </a-button>
    </div>
    <a-row
      v-show="expandAll"
      class="ns-form-body"
      :justify="getFormClass.justify"
      :gutter="getFormClass.gutter">
      <template v-for="(schema, index) in getSchema" :key="schema.field">
        <ns-form-item
          :show="expandRef || index < splitNumber"
          :span="getComponentSpan(schema)"
          :schema="schema"
          :index="index"
          :formModel="formModel">
          <template #[item]="data" v-for="item in Object.keys($slots)">
            <slot :name="item" v-bind="data || {}"></slot>
          </template>
        </ns-form-item>
      </template>

      <!-- space-between兼容 -->
      <a-col
        v-if="
          getFormClass.justify === 'space-between' && showAction
            ? expandRef && getSchema.length % splitNumber === 0
            : (getSchema.length + 1) % splitNumber === 0
        "
        :span="getFormClass.span" />
      <a-col v-if="showAction" :span="getFormClass.span" class="ns-operate">
        <a-button @click="reset">重置</a-button>
        <a-button type="primary" html-type="submit" :loading="loading">搜索</a-button>
        <a-button
          type="link"
          class="ns-operate-expand"
          @click="expandRef = !expandRef"
          v-if="getSchema.length > splitNumber && showExpand">
          <template v-if="expandRef">
            收起
            <UpOutlined />
          </template>
          <template v-else>
            展开
            <DownOutlined />
          </template>
        </a-button>
      </a-col>
    </a-row>
    <!--    <template v-for="(schema, index) in getSchema" :key="schema.field">-->
    <!--      <ns-form-item :schema="schema" :index="index" :formModel="formModel">-->
    <!--        <template #[item]="data" v-for="item in Object.keys($slots)">-->
    <!--          <slot :name="item" v-bind="data || {}"></slot>-->
    <!--        </template>-->
    <!--      </ns-form-item>-->
    <!--    </template>-->
    <!--    <a-row-->
    <!--      v-if="showAction"-->
    <!--      style="text-align: right; margin-left: auto"-->
    <!--      class="ns-form-item ns-form-item-op">-->
    <!--      <a-col span="24" class="operate">-->
    <!--        <a-button @click="reset">重置</a-button>-->
    <!--        <a-button style="margin-left: 10px" type="primary" html-type="submit" :loading="loading"-->
    <!--          >搜索</a-button-->
    <!--        >-->
    <!--      </a-col>-->
    <!--    </a-row>-->
  </a-form>
</template>

<!--suppress TypeScriptCheckImport -->
<script lang="ts">
  import { computed, defineComponent, nextTick, provide, ref, toRefs, watch } from 'vue';
  import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
  import NsFormItem from '/nerv-lib/component/form/form/form-item.vue';
  import { useFormModel } from '/nerv-lib/component/form/form/use-form-model';
  import { formConfig } from '/nerv-base/config/form.config';
  import { formProps } from '/nerv-lib/component/form/form/props';
  import { isBoolean, isFunction } from 'lodash-es';
  import FormValidator from 'async-validator';
  import { watchDebounced, useElementSize } from '@vueuse/core';
  export default defineComponent({
    name: 'NsForm',
    components: { NsFormItem, DownOutlined, UpOutlined },
    props: formProps,
    emits: ['finish', 'submit', 'reset'],
    setup(props, { attrs, emit }) {
      const formElRef = ref<null | Recordable>(null);
      const validateResult = ref(false);
      const { schemas } = toRefs(props);
      const isInitDefaultValueRef = ref(false);
      const expandRef = ref(props.expand);
      const expandAll = ref(props.expandAll);
      const title = ref(props.title);
      const formModel = computed(() => {
        return props.model;
      });
      const childForms = ref<any[]>([]);

      function addChildForm(form: any) {
        childForms.value.push(form);
      }
      let splitNumber = ref(4);
      const { width: formWidth } = useElementSize(formElRef);
      provide('addChildForm', addChildForm);

      const getFormClass = computed(() => {
        if (props.formLayout) {
          return formConfig.formLayout[props.formLayout as keyof typeof formConfig.formLayout];
        }
        return formConfig.formLayout.vertical;
      });
      const FULL_SPAN_LIST = ['NsChildForm'];

      const getComponentSpan = (schema: FormSchema) => {
        if (FULL_SPAN_LIST.includes(schema.component || '')) {
          return 24;
        }
        if (schema.class?.includes('ns-form-item-full')) {
          return 24;
        }

        return getFormClass.value.span;
      };

      provide('formLayout', getFormClass.value);

      const getBindValue = computed(() => ({
        ...getFormClass.value,
        ...attrs,
        ...props,
        title: '',
        onSubmit: submit,
        onFinish: finish,
      }));

      const getSchema = computed((): FormSchema[] => {
        const { schemas } = props;
        const formSchemas = schemas.filter((schema) => schema.component);
        return formSchemas as FormSchema[];
      });

      watchDebounced(
        () => formModel.value,
        () => {
          const rules = {};
          setRules(rules, getSchema.value);
          const validator = new FormValidator(rules);
          validator
            .validate(formModel.value)
            .then(() => {
              validateResult.value = true;
            })
            .catch(() => {
              validateResult.value = false;
            });
        },
        {
          debounce: 100,
          deep: true,
        },
      );

      watchDebounced(
        () => formWidth.value,
        (val) => {
          if (val <= 768 && getFormClass.value['sm']) {
            getFormClass.value.span = getFormClass.value['sm'];
            splitNumber.value = 2;
          }
          if (val > 768 && getFormClass.value['lg']) {
            getFormClass.value.span = getFormClass.value['lg'];
            splitNumber.value = 3;
          }
        },
        {
          debounce: 100,
          deep: true,
        },
      );

      function setRules(rules: Recordable, schemas: any[]) {
        schemas.forEach((schema) => {
          if (schema.rules) {
            const { ifShow } = schema;
            let isIfShow = true;
            if (isBoolean(ifShow)) {
              isIfShow = ifShow;
            }
            if (isFunction(ifShow)) {
              isIfShow = ifShow(formModel.value);
            }

            // console.log('ifShow', isIfShow);
            if (isIfShow) {
              rules[schema.field] = schema.rules;
            }
          }
          if (schema.componentProps?.schemas) {
            setRules(rules, schema.componentProps.schemas);
          }
        });
      }

      const {
        handleFormModel,
        initFormModel,
        resetFormModel,
        setFormModel,
        unsetFormModel,
        getFormModel,
      } = useFormModel({
        schemas,
        formModel,
        formElRef,
        emit,
      });

      watch(
        () => getSchema.value,
        () => {
          if (isInitDefaultValueRef.value) {
            return;
          }
          initFormModel();
          isInitDefaultValueRef.value = true;
        },
        {
          immediate: true,
          deep: true,
        },
      );

      /**
       * 数据验证成功后回调
       */
      const submit = () => {
        emit('submit', formModel.value);
        return new Promise((resolve) => {
          resolve(formModel.value);
        });
      };

      /**
       * 提交表单且数据验证成功后回调事件
       */
      function finish() {
        const data = handleFormModel();
        emit('finish', data);
        return new Promise((resolve) => {
          resolve(data);
        });
      }

      /**
       * 主动执行提交
       * @param val
       */
      function triggerSubmit(val?: string[]) {
        return submit().then(() => {
          return nextTick().then(() => {
            return new Promise((resolve, reject) => {
              (formElRef as Recordable).value
                .validate(val)
                .then((_) => {
                  resolve(finish());
                })
                .catch((e) => {
                  reject(e);
                });
            });
          });
        });
      }

      /**
       * 重置表单
       */
      const reset = () => {
        resetFormModel();
      };

      provide('setFormModel', setFormModel);
      provide('unsetFormModel', unsetFormModel);
      provide('getFormModel', getFormModel);
      provide('submit', triggerSubmit);

      return {
        expandRef,
        formElRef,
        getBindValue,
        getSchema,
        formModel,
        setFormModel,
        getFormClass,
        validateResult,
        reset,
        triggerSubmit,
        getComponentSpan,
        splitNumber,
        finish,
        expandAll,
        title,
      };
    },
  });
</script>
<style lang="less" scoped>
  @gap: 16px;
  .ns-form {
    .ant-row {
      flex: 1;
    }
    .ns-operate {
      margin-bottom: @gap;
      text-align: right;
      margin-left: auto;

      .ns-operate-expand {
        display: inline-block;
        padding: 4px 2px;
        border: unset !important;
        .anticon {
          margin-left: 4px;
        }
        &:hover {
          border: unset !important;
        }
        &:focus {
          border: unset !important;
        }
      }
      .ant-btn {
        margin-left: 6px;
      }
    }
    .ns-form-title {
      text-align: left;
      height: 22px;
      // line-height: 32px;
      //font-size: 16px;
      font-weight: bold;
      user-select: text;
      margin-bottom: calc(@gap - 0px);
      display: flex;
      justify-content: space-between;
      align-items: center;
      :deep(.ant-btn) {
        padding: 0;
      }
    }
  }
</style>
