<template>
  <ElForm
    :style="getStyle()"
    :model="modelFormData"
    :label-width="labelWidth"
    class="FormConfig"
    ref="ElFormRef"
    size="mini"
  >
    <template v-for="(item, index) in formList">
      <ElFormItem
        class="formItem"
        :rules="getRules(item)"
        v-if="formItemVisible(item)"
        :key="`${item.key}|${index}`"
        :label="item.label"
        :style="gridStyle(item, index)"
        :prop="item.type !== 'MultipleLabelItem' ? item.key : void 0"
        :required="item.required"
      >
        <slot :name="item.key" :data="item" :formData="modelFormData">
          <ProxyCom
            :value="getValue(item)"
            :item="item"
            @update:value="(data) => updateValue(item, data)"
          />
          <!-- <component :is="getComponent(item.type)" v-bind="getBindData(item)" v-model="modelFormData[item.key]"
            v-on="resolveListeners(item.ons)" /> -->
        </slot>
      </ElFormItem>
    </template>
  </ElForm>
</template>

<script>
import { resolveName, defaultComponentOptions, RegexpMap } from "./utils/index";
import { reduceDefaultValue } from "./utils/defaultValue";
import { set as pathSet, get as pathGet, cloneDeep } from "lodash";

import ProxyCom from "./Proxy.vue";

const files = require.context(
  "./components",
  true,
  /^.\/[^/]+\/index\.vue$|^.\/[^/]+.vue$/
);

const components = files.keys().reduce(
  (prev, key) => {
    prev[key.match(/[^./]+/g)[0]] = files(key).default;

    return prev;
  },
  {
    Empty: "div",
  }
);

export default {
  name: "FormConfig",
  components: {
    ProxyCom,
  },
  provide() {
    return {
      getComponent: this.getComponent,
      getBindData: this.getBindData,
      getFormData: () => this.modelFormData,
      getValue: this.getValue,
      updateValue: this.updateValue,
      parent: this,
      getRules: this.getRules,
    };
  },
  props: {
    /**
     * {
     *  label: String;
     *  key: String;
     *  type: 'input' | 'timePicker',
     *  gridArea?: "",
     *  gridColumn?: "",
     *  gridRow?: "",
     *  options?: {}
     * }[]
     */
    dFormData: {
      type: Object,
      default: () => {},
    },
    /**
     * {
     *  label: String;
     *  key: String;
     *  type: 'input' | 'timePicker',
     *  gridArea?: "",
     *  gridColumn?: "",
     *  gridRow?: "",
     *  options?: {}
     * }[]
     */
    value: Object,
    formList: {
      type: Array,
      default: () => [],
    },
    labelWidth: {
      type: String,
      default: "auto",
    },
    rules: {
      type: Object,
      default: null,
    },
    column: {
      type: [String, Number],
      default: "3",
    },
  },
  model: {
    prop: "value",
    event: "update:value",
  },
  data() {
    return {
      formData: {},
    };
  },
  watch: {
    formList: {
      immediate: true,
      handler() {
        this.reset(true);
      },
    },
  },
  // created() {
  //   this.reset(true);
  // },
  computed: {
    modelFormData: {
      get() {
        return this.formData;
      },
      set(data) {
        this.formData = data;
        this.$emit("update:value", this.formData);
      },
    },
    gridStyle() {
      return (item, index) => ({
        gridRow: `span ${item.gridRow || 1}`,
        gridColumn: `span ${
          item.gridColumn || (item.isAlone && this.column) || 1
        }`,
      });
    },
    formItemVisible() {
      return (item) => {
        const result =
          item && item.visible ? item.visible(this.modelFormData) : true;

        // if (!result) {
        //   delete this.formData[item.key];
        // }

        return result;
      };
    },
  },
  methods: {
    getValue(item) {
      return pathGet(this.formData, item.key);
    },
    updateValue(item, data) {
      // 对于数组的修改无响应式 采用此方法
      this.modelFormData = { ...pathSet(this.modelFormData, item.key, data) };
    },
    reset(isFirst) {
      return (this.modelFormData = reduceDefaultValue(
        this.formList,
        isFirst ? this.value || this.dFormData : {}
      ));
    },
    getStyle() {
      return {
        gridTemplateColumns: `repeat(${this.column}, 1fr)`,
      };
    },
    getBindData(item) {
      let componentKey = resolveName(item.type || "input");

      return {
        placeholder: "请输入",
        ...defaultComponentOptions[componentKey],
        ...item.options,
      };
    },
    getComponent(type) {
      if (!type) type = "input";

      const componentKey = resolveName(type);

      const ElComponentKey = `El${componentKey}`;

      return (
        components[componentKey] || components[ElComponentKey] || ElComponentKey
      );
    },
    getRules(item) {
      // 自定义的没添加

      /**
       * validator 手动情况下 此方法需要改传参 ({
       * // 当前总数据
       * data,
       * // 当前字段的值
       * value,
       * // 回调跟 el 一样
       * callback,
       * // 当前的item config
       * config
       * })
       */
      const ruleMatch = (value) => {
        /**
         * @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]}
         */
        const rules = item.rules || [];
        for (let index = 0; index < rules.length; index++) {
          const rule = rules[index];
          // 返回值 true 通过 false 异常
          if (typeof rule.callback === "function") {
            if (!rule.callback(value, this.modelFormData))
              return new Error(rule.message || "内容错误");
          } else if (RegexpMap[rule.type]) {
            if (!RegexpMap[rule.type].reg.test(value))
              return new Error(rule.message || RegexpMap[rule.type].message);
          }
        }
      };

      if (item.required)
        return [
          {
            validator: (_, __, callback) => {
              const value = pathGet(this.modelFormData, item.key);
              if (
                (!value && typeof value != "number") ||
                (typeof value === "string" && !value.trim()) ||
                (typeof value === "object" && value.length == 0)
              )
                return callback(
                  new Error(
                    `${item.options?.placeholder || `${item.label}不能为空`}`
                  )
                );

              const err = ruleMatch(value);

              if (err) return callback(err);

              callback();
            },
            trigger: ["blur", "change"],
          },
        ];
      else if (item.rules?.length)
        return [
          {
            validator: (_, __, callback) => {
              /**
               * @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]}
               */

              const err = ruleMatch(pathGet(this.modelFormData, item.key));

              if (err) return callback(err);

              callback();
            },
            trigger: ["blur", "change"],
          },
        ];
    },
    validate() {
      return new Promise((resolve, reject) => {
        this.$refs.ElFormRef.validate((bool) => {
          if (bool) resolve(cloneDeep(this.modelFormData));
          else reject("表单验证未通过");
        });
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.list-item {
  display: inline-block;
  margin-right: 10px;
}

.list-enter-active,
.list-leave-active {
  transition: all 1s;
  position: absolute;
}

.list-enter,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

.FormConfig {
  display: grid;
  align-content: start;
  width: 100%;
  gap: 15px 15px;
  // overflow-x: hidden;
  padding-right: 9px;

  .formItem {
    display: flex;
    align-items: center;
  }

  ::v-deep {
    .el-form-item {
      align-items: center;
      margin: 0;
      height: 100%;
      width: 100%;

      &:first-child {
        .el-form-item__label-wrap {
          // padding-top: 9px;
          margin: 0 !important;
        }
      }

      .el-form-item__label-wrap {
        width: fit-content;
      }

      .el-form-item__label {
        height: 22px;
        font-size: 15px;
        // font-family: PingFang SC, PingFang SC;
        font-weight: 400;
        color: #3de8ff;
        line-height: unset;
        // -webkit-background-clip: text;
        // -webkit-text-fill-color: transparent;
      }

      .el-form-item__content {
        margin: 0 !important;
        flex: 1;
        height: 100%;

        .el-input__prefix {
          color: #fff;
        }
      }
    }
  }
}
</style>