"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.VALIDATION_EVENTS = exports.DEFAULT_CONFIG = void 0;
exports.floatBetween0And1 = floatBetween0And1;
exports.isEmail = isEmail;
exports.isFiniteNumber = isFiniteNumber;
exports.isInteger = isInteger;
exports.isNumberGreaterThan = isNumberGreaterThan;
exports.isNumberLessThan = isNumberLessThan;
exports.isUrl = isUrl;
exports.matchCustomPattern = matchCustomPattern;
exports.noEmptyValuesInCommaSeparatedString = noEmptyValuesInCommaSeparatedString;
exports.notLongerThanX = notLongerThanX;
exports.required = required;
exports.useInputValidation = useInputValidation;
var _react = require("react");
var _get = _interopRequireDefault(require("lodash/get"));
var _isNil = _interopRequireDefault(require("lodash/isNil"));
var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));
var _debounce = _interopRequireDefault(require("lodash/debounce"));
var _validationHelpers = require("./validation-helpers");
var _formField = require("../../form-field");
var _message = require("../../message");
var _useTranslation = require("../use-translation");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
// eslint-disable-next-line you-dont-need-lodash-underscore/get

const VALIDATION_EVENTS = exports.VALIDATION_EVENTS = {
  ON_CHANGE: 0,
  ON_BLUR: 1
};
const DEFAULT_CONFIG = exports.DEFAULT_CONFIG = {
  displayLocation: _formField.MESSAGE_DISPLAY_LOCATION.INLINE,
  validationEvent: VALIDATION_EVENTS.ON_CHANGE,
  dependentValues: [],
  debounceTime: 200
};
/**
 *
 * @param {[any, function]} stateArray Tuple of state value and setValue function.
 * @param {array} validations Array of synchronous validations to run.
 * @param {array} asyncValidations Array of asynchronous validations to run.
 * @param {object} config Options of the inputvalidation.
 * @param {string} config.displayLocation A MESSAGE_DISPLAY_LOCATION option, defaulting to INLINE.
 * @param {string} config.validationEvent Defines event on which validation
 * should be triggered. Default is ON_CHANGE
 * @param {number} config.debounceTime time for debounce async validations
 * @param {array} config.dependentValues used to trigger input validation when these values changed.
 */

function useInputValidation([value, setValue], {
  validations = [],
  asyncValidations = []
} = {
  validations: [],
  asyncValidations: []
}, config = DEFAULT_CONFIG) {
  const {
    t
  } = (0, _useTranslation.useTranslation)();
  const {
    displayLocation = _formField.MESSAGE_DISPLAY_LOCATION.INLINE,
    validationEvent = VALIDATION_EVENTS.ON_CHANGE,
    dependentValues = [],
    debounceTime = 200
  } = config;
  const validationErrorWarningMessage = config.validationErrorWarningMessage ?? t('Something went wrong, try again');
  const [validity, setValidity] = (0, _react.useState)(null);
  const isPristine = (0, _react.useRef)(true);
  const isValidating = (0, _react.useRef)(false);
  const isRetryShown = (0, _react.useRef)(false);
  const valueToValidate = (0, _react.useRef)('');
  const isCommitingChange = (0, _react.useRef)(false);
  const hasAsyncValidations = !!asyncValidations.length;
  async function checkValid(val) {
    if (hasAsyncValidations) {
      isValidating.current = true;
      isRetryShown.current = false;
      valueToValidate.current = '';
      setValidity({
        value: [],
        displayLocation,
        isPristine: isPristine.current
      });
    }
    try {
      const validatorsArray = hasAsyncValidations ? await Promise.all([...asyncValidations, ...validations].map(v => v(val))) : validations.map(v => v(val));
      isValidating.current = false;
      const validated = validatorsArray.filter(Boolean);
      if (validated.length) {
        setValidity({
          type: _message.MESSAGE_TYPES.ERROR,
          value: validated,
          displayLocation,
          isPristine: isPristine.current
        });
        return false;
      }
      setValidity(null);
      return true;
    } catch (e) {
      if (['AbortError', 'CanceledError'].includes(e?.name)) {
        return false;
      }
      isValidating.current = false;
      isRetryShown.current = true;
      valueToValidate.current = val;
      setValidity({
        type: _message.MESSAGE_TYPES.WARNING,
        value: [validationErrorWarningMessage],
        displayLocation,
        isPristine: isPristine.current
      });
      return false;
    }
  }
  async function onBlur(e) {
    if (validationEvent === VALIDATION_EVENTS.ON_BLUR) {
      isPristine.current = false;
      const val = e.currentTarget.value;
      const isValid = await checkValid(val);
      if (isValid) {
        isCommitingChange.current = true;
        setValue(val);
      }
    }
  }
  const debouncedCheckRef = (0, _react.useRef)((0, _debounce.default)(checkValid, debounceTime));
  (0, _react.useEffect)(() => {
    // This reference needs to be kept up to date if the validations or asyncValidations change
    debouncedCheckRef.current = (0, _debounce.default)(checkValid, debounceTime);
  }, [validations, asyncValidations]);
  async function onChange(e) {
    if (validationEvent === VALIDATION_EVENTS.ON_CHANGE) {
      isPristine.current = false;
      const val = (0, _get.default)(e, 'currentTarget.value', '');
      if (hasAsyncValidations) {
        debouncedCheckRef.current(val);
      } else {
        await checkValid(val);
      }
      isCommitingChange.current = true;
      setValue(val);
    }
  }
  function onRetry() {
    checkValid(valueToValidate.current);
  }
  (0, _react.useEffect)(() => {
    // Ensure that the value changed outside of the input is still validated.
    // This could be the form being reset to an inital state, for instance.
    // If it occurs from an input change, we don't need to validate it twice.
    // If the change is from outside, treat the value as pristine.
    if (isCommitingChange.current) {
      isCommitingChange.current = false;
    } else {
      isPristine.current = true;
      checkValid(value);
    }
  }, [value]);
  (0, _react.useEffect)(() => {
    checkValid(value);
  }, dependentValues);
  return {
    asyncValidation: {
      isValidating: isValidating.current,
      isRetryShown: isRetryShown.current,
      onRetry
    },
    value,
    validity,
    onBlur,
    onChange
  };
}

/**
 * Requires some kind of value.  Does not pass on `null`, `undefined`, `''`, or `' '`.
 * @param {string} errorMessage Error message to show.
 */
function required(errorMessage) {
  return value => {
    if ((0, _isNil.default)(value) || typeof value === 'string' && !value.trim()) {
      return errorMessage;
    }
  };
}

/**
 * Requires a float value between 0 and 1, having 4 or less digits of precision.
 * So, `0.1234` is valid, but `0.12345` is not.
 * @param {string} errorMessage Error message to show.
 */
function floatBetween0And1(errorMessage) {
  return value => {
    const floatValue = parseFloat(value);
    if (Number.isNaN(floatValue) || floatValue < 0 || floatValue > 1 || value.length > 6) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if the value is a finite number.
 * @param {string} errorMessage Error message to show.
 */
function isFiniteNumber(errorMessage) {
  return value => {
    if (!Number.isFinite(parseFloat(value))) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if the value is an URL.
 * @param {string} errorMessage Error message to show.
 * @param {boolean} allowEmpty prop to allow empty value.
 */
function isUrl(errorMessage, allowEmpty = false) {
  return value => {
    if (!value && allowEmpty) {
      return;
    }
    if (!(0, _validationHelpers.validateUrl)(value)) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if the value is an email.
 * @param {string} errorMessage Error message to show.
 * @param {boolean} allowEmpty prop to allow empty email.
 */
function isEmail(errorMessage, allowEmpty = false) {
  return value => {
    if (!value && allowEmpty) {
      return;
    }
    if (!(0, _validationHelpers.validateEmail)(value)) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if the value is no longer than X.
 * @param {string} errorMessage Error message to show.
 * @param {number} maxLength Max length allowed
 */
function notLongerThanX(errorMessage, maxLength = 100) {
  return value => {
    if (value.length > maxLength) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if number is less than X
 * @param {string} errorMessage Error message to show.
 * @param {number} number The number
 * @param {boolean} allowEmpty prop to allow empty value
 */
function isNumberLessThan(errorMessage, number, allowEmpty = false) {
  return value => {
    if (allowEmpty && !value) {
      return;
    }
    const floatValue = parseFloat(value);
    const isNaN = Number.isNaN(floatValue);
    if (isNaN || floatValue < number) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if number is greater than X
 * @param {string} errorMessage Error message to show.
 * @param {number} number The number
 * @param {boolean} allowEmpty prop to allow empty value
 */
function isNumberGreaterThan(errorMessage, number, allowEmpty = false) {
  return value => {
    if (allowEmpty && !value) {
      return;
    }
    const floatValue = parseFloat(value);
    const isNaN = Number.isNaN(floatValue);
    if (isNaN || floatValue > number) {
      return errorMessage;
    }
  };
}

/**
 * Checks if string matches provided regular expression.
 * @param {string} errorMessage Error message to show.
 * @param {RegExp} customPattern RegExp to validate string.
 */
function matchCustomPattern(errorMessage, customPattern) {
  return value => {
    if (!customPattern.test(value)) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see if the value is an integer.
 * @param {string} errorMessage Error message to show.
 */
function isInteger(errorMessage) {
  return value => {
    if (!Number.isInteger(parseFloat(value))) {
      return errorMessage;
    }
  };
}

/**
 * Checks to see that there no empty values in comma separated string.
 * String itself CAN be empty, "" is allowed, but not "val1,,val2"
 * @param {string} errorMessage Error message to show.
 */
function noEmptyValuesInCommaSeparatedString(errorMessage) {
  return value => {
    if ((0, _isEmpty.default)(value)) {
      return;
    }
    if (value.split(',').map(c => c.trim()).includes('')) {
      return errorMessage;
    }
  };
}