'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var vue = require('vue');
var lodashEs = require('lodash-es');
var validateUtil = require('./utils/validateUtil.cjs');
var messages = require('./utils/messages.cjs');
var asyncUtil = require('./utils/asyncUtil.cjs');

function getIsRequired(rules) {
  let isRequired = false;
  if (rules && rules.length) {
    rules.every((rule) => {
      if (rule.required) {
        isRequired = true;
        return false;
      }
      return true;
    });
  }
  return isRequired;
}
function toArray(value) {
  if (value === void 0 || value === null)
    return [];
  return Array.isArray(value) ? value : [value];
}
function getPropByPath(obj, path, strict) {
  let tempObj = obj;
  path = path.replace(/\[(\w+)\]/g, ".$1");
  path = path.replace(/^\./, "");
  const keyArr = path.split(".");
  let i = 0;
  for (let len = keyArr.length; i < len - 1; ++i) {
    if (!tempObj && !strict)
      break;
    const key = keyArr[i];
    if (key in tempObj) {
      tempObj = tempObj[key];
    } else {
      if (strict)
        throw new Error("please transfer a valid name path to validate!");
      break;
    }
  }
  return {
    o: tempObj,
    k: keyArr[i],
    v: tempObj ? tempObj[keyArr[i]] : null,
    isValid: tempObj && keyArr[i] in tempObj
  };
}
function useForm(modelRef, rulesRef = vue.ref({}), options) {
  const initialModel = lodashEs.cloneDeep(vue.unref(modelRef));
  const validateInfos = vue.reactive({});
  const rulesKeys = vue.shallowRef([]);
  const resetFields = (newValues) => {
    Object.assign(vue.unref(modelRef), {
      ...lodashEs.cloneDeep(initialModel),
      ...newValues
    });
    vue.nextTick(() => {
      Object.keys(validateInfos).forEach((key) => {
        validateInfos[key] = {
          autoLink: false,
          required: getIsRequired(vue.unref(rulesRef)[key])
        };
      });
    });
  };
  const filterRules = (rules = [], trigger) => {
    if (!trigger.length) {
      return rules;
    } else {
      return rules.filter((rule) => {
        const triggerList = toArray(rule.trigger || "change");
        return lodashEs.intersection(triggerList, trigger).length;
      });
    }
  };
  let lastValidatePromise = null;
  const validateField = (name, value, rules, option = {}) => {
    const promise = validateUtil.validateRules(
      [name],
      value,
      rules,
      {
        validateMessages: messages.defaultValidateMessages,
        ...option
      },
      !!option.validateFirst
    );
    if (!validateInfos[name])
      return promise.catch((e) => e);
    validateInfos[name].validateStatus = "validating";
    promise.catch((e) => e).then((results = []) => {
      var _a;
      if (validateInfos[name].validateStatus === "validating") {
        const res = results.filter((result) => result && result.errors.length);
        validateInfos[name].validateStatus = res.length ? "error" : "success";
        validateInfos[name].help = res.length ? res.map((r) => r.errors) : null;
        (_a = options == null ? void 0 : options.onValidate) == null ? void 0 : _a.call(
          options,
          name,
          !res.length,
          res.length ? vue.toRaw(validateInfos[name].help[0]) : null
        );
      }
    });
    return promise;
  };
  const validateFields = (names, option = {}, strict) => {
    const promiseList = [];
    const values = {};
    for (let i = 0; i < names.length; i++) {
      const name = names[i];
      const prop = getPropByPath(vue.unref(modelRef), name, strict);
      if (!prop.isValid)
        continue;
      values[name] = prop.v;
      const rules = filterRules(vue.unref(rulesRef)[name], toArray(option && option.trigger));
      if (rules.length) {
        promiseList.push(
          validateField(name, prop.v, rules, option || {}).then(() => ({
            name,
            errors: [],
            warnings: []
          })).catch((ruleErrors) => {
            const mergedErrors = [];
            const mergedWarnings = [];
            ruleErrors.forEach(({ rule: { warningOnly }, errors }) => {
              if (warningOnly)
                mergedWarnings.push(...errors);
              else
                mergedErrors.push(...errors);
            });
            if (mergedErrors.length) {
              return Promise.reject({
                name,
                errors: mergedErrors,
                warnings: mergedWarnings
              });
            }
            return {
              name,
              errors: mergedErrors,
              warnings: mergedWarnings
            };
          })
        );
      }
    }
    const summaryPromise = asyncUtil.allPromiseFinish(promiseList);
    lastValidatePromise = summaryPromise;
    const returnPromise = summaryPromise.then(() => {
      if (lastValidatePromise === summaryPromise)
        return Promise.resolve(values);
      return Promise.reject([]);
    }).catch((results) => {
      const errorList = results.filter(
        (result) => result && result.errors.length
      );
      return Promise.reject({
        values,
        errorFields: errorList,
        outOfDate: lastValidatePromise !== summaryPromise
      });
    });
    returnPromise.catch((e) => e);
    return returnPromise;
  };
  const validate = (names, option) => {
    let keys = [];
    let strict = true;
    if (!names) {
      strict = false;
      keys = rulesKeys.value;
    } else if (Array.isArray(names)) {
      keys = names;
    } else {
      keys = [names];
    }
    const promises = validateFields(keys, option || {}, strict);
    promises.catch((e) => e);
    return promises;
  };
  const clearValidate = (names) => {
    let keys = [];
    if (!names)
      keys = rulesKeys.value;
    else if (Array.isArray(names))
      keys = names;
    else
      keys = [names];
    keys.forEach((key) => {
      validateInfos[key] && Object.assign(validateInfos[key], {
        validateStatus: "",
        help: null
      });
    });
  };
  const mergeValidateInfo = (items) => {
    const info = { autoLink: false };
    const help = [];
    const infos = Array.isArray(items) ? items : [items];
    for (let i = 0; i < infos.length; i++) {
      const arg = infos[i];
      if ((arg == null ? void 0 : arg.validateStatus) === "error") {
        info.validateStatus = "error";
        arg.help && help.push(arg.help);
      }
      info.required = info.required || (arg == null ? void 0 : arg.required);
    }
    info.help = help;
    return info;
  };
  let oldModel = initialModel;
  let isFirstTime = true;
  const modelFn = (model) => {
    const names = [];
    rulesKeys.value.forEach((key) => {
      const prop = getPropByPath(model, key, false);
      const oldProp = getPropByPath(oldModel, key, false);
      const isFirstValidation = isFirstTime && (options == null ? void 0 : options.immediate) && prop.isValid;
      if (isFirstValidation || !lodashEs.isEqual(prop.v, oldProp.v))
        names.push(key);
    });
    validate(names, { trigger: "change" });
    isFirstTime = false;
    oldModel = lodashEs.cloneDeep(vue.toRaw(model));
  };
  const debounceOptions = options == null ? void 0 : options.debounce;
  let first = true;
  vue.watch(
    rulesRef,
    () => {
      rulesKeys.value = rulesRef ? Object.keys(vue.unref(rulesRef)) : [];
      if (!first && options && options.validateOnRuleChange)
        validate();
      first = false;
    },
    { deep: true, immediate: true }
  );
  vue.watch(
    rulesKeys,
    () => {
      const newValidateInfos = {};
      rulesKeys.value.forEach((key) => {
        newValidateInfos[key] = Object.assign({}, validateInfos[key], {
          autoLink: false,
          required: getIsRequired(vue.unref(rulesRef)[key])
        });
        delete validateInfos[key];
      });
      for (const key in validateInfos) {
        if (Object.prototype.hasOwnProperty.call(validateInfos, key))
          delete validateInfos[key];
      }
      Object.assign(validateInfos, newValidateInfos);
    },
    { immediate: true }
  );
  vue.watch(
    modelRef,
    debounceOptions && debounceOptions.wait ? lodashEs.debounce(modelFn, debounceOptions.wait, lodashEs.omit(debounceOptions, ["wait"])) : modelFn,
    { immediate: options && !!options.immediate, deep: true }
  );
  return {
    modelRef,
    rulesRef,
    initialModel,
    validateInfos,
    resetFields,
    validate,
    validateField,
    mergeValidateInfo,
    clearValidate
  };
}

exports.default = useForm;
//# sourceMappingURL=useForm.cjs.map
