Source: app/validation/validationUtils.js

import Pages from '../data/enum/Pages';
import matchRoute from '../util/routeCheckUtils';
import { measurementFields, membershipCardFields } from '../data/enum/FieldNames/AccountFieldNames';
import { clearValidation } from '../enhanced-redux-form/actions/enhancedFormActions';
import * as ValidateOn from '../enhanced-redux-form/data/ValidateOn';
import { required, minLength, maxLength } from '../util/form/basic-validations';
import { userProfileSelector } from '../selectors/userProfileSelectors';
import { isBmiRangeSafe, isTransferBmiRangeSafe, isDangerous, isAtRisk } from '../util/BmiUtils';
import { validateWeight, updateWeight } from '../actions/resources/weighInActions';
import {
  ERROR_BMI_TOO_LOW,
  ERROR_BMI_NOT_ALLOWED,
  ERROR_BMI_DANGEROUS,
} from '../data/validationErrorCodes';
import WeightHeightRanges from '../data/enum/WeightHeightRanges';
import MembershipCard from '../data/enum/MembershipCard';

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjuntion
 * with other validation rules for a given field
 */

export const createRequiredRule = (fieldName, formName = 'validation') => ({
  rule: required,
  message: { locale: `${formName}.errors.${fieldName}.required` },
});

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const regexTester = regex => value => regex.test(value);

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const getHeight = () => (dispatch, getState) => {
  const profile = userProfileSelector(getState());
  return profile && profile.height;
};

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const getSupplimentaryHeight = () => (dispatch, getState) =>
  getState().supplementary.profileHeight;

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const bmiLowRule = (form, fieldName, isGroupMember) => ({
  rule: (value, values, _, dispatch) => {
    const groupRoutes = [Pages.GR_MEDICAL_CHECK, Pages.NEW_GROUP_JOURNEY_TARGET_WEIGHT];
    const matchRoutes = () => (__, getState) =>
      groupRoutes.map(route => matchRoute(route, getState())).filter(x => x).length > 0;

    // Do we match on the group routes?
    const isGroupFlow = isGroupMember || dispatch(matchRoutes());

    !isGroupFlow && dispatch(clearValidation(form, [fieldName, measurementFields.HEIGHT]));

    const height = values[measurementFields.HEIGHT] || dispatch(getHeight()) || null;

    if (!isGroupFlow && values && values[fieldName] && height) {
      if (fieldName === measurementFields.TARGET_WEIGHT && isDangerous(values[fieldName], height)) {
        // return false (because we will catch the target error in the bmiDangerousRule check)
        return false;
      }

      return !isAtRisk(values[fieldName], height);
    }

    return true;
  },
  code: ERROR_BMI_TOO_LOW,
});

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const bmiDangerousRule = (form, fieldName) => ({
  rule: (value, values, _, dispatch) => {
    const groupRoutes = [Pages.GR_MEDICAL_CHECK];
    const matchRoutes = () => (__, getState) =>
      groupRoutes.map(route => matchRoute(route, getState())).filter(x => x).length > 0;

    // Do we match on the group routes?
    const isGroupFlow = dispatch(matchRoutes());

    !isGroupFlow && dispatch(clearValidation(form, [fieldName, measurementFields.HEIGHT]));

    const height = values[measurementFields.HEIGHT] || dispatch(getHeight()) || null;

    if (!isGroupFlow && values && values[fieldName] && height) {
      if (fieldName === measurementFields.TARGET_WEIGHT) {
        return !isDangerous(values[fieldName], height);
      }
    }

    return true;
  },
  code: ERROR_BMI_DANGEROUS,
});

// Always return true, as we dont want to block the form
// validateWeight will add server validation into the
// weighin section of state
export const weighInValidation = () => ({
  rule: (value, values, _, dispatch) => {
    dispatch(validateWeight(value));
    return true;
  },
  code: ERROR_BMI_TOO_LOW,
});

export const editWeighInValidation = () => ({
  rule: (value, values, _, dispatch) => {
    dispatch(updateWeight(true));
    return true;
  },
  code: ERROR_BMI_TOO_LOW,
});

/*
 * Simple helper function to create a 'required' validation rule, to be used in conjunction
 * with other validation rules for a given field
 */
export const bmiNotAllowedRule = (form, fieldName, isGroupMember) => ({
  rule: (value, values, _, dispatch) => {
    const groupRoutes = [Pages.GR_MEDICAL_CHECK];
    const matchRoutes = () => (__, getState) =>
      groupRoutes.map(route => matchRoute(route, getState())).filter(x => x).length > 0;

    const matchTransferRoute = () => (__, getState) =>
      matchRoute(Pages.TRANSFER_TO_ONLINE_BMI, getState());

    // Do we match on the group routes?
    const isGroupFlow = isGroupMember || dispatch(matchRoutes());
    const isTransferFlow = dispatch(matchTransferRoute());

    !isGroupFlow && dispatch(clearValidation(form, [fieldName, measurementFields.HEIGHT]));

    const height = values[measurementFields.HEIGHT] || dispatch(getHeight());

    if (isTransferFlow && values && values[fieldName] && height) {
      return isTransferBmiRangeSafe(values[fieldName], height);
    }

    if (!isGroupFlow && values && values[fieldName] && height) {
      return isBmiRangeSafe(values[fieldName], height);
    }
    // values are not set yet.
    // the other 'required' rules will fail in this case, so we can return 'true' here
    return true;
  },
  code: ERROR_BMI_NOT_ALLOWED,
});

/**
 * Checks weight value against the defined upper range for weight
 * Called from inside a validation rule
 **/
export const weightUpperRange = () => ({
  rule: value => {
    if (value === undefined || value < WeightHeightRanges.WEIGHT_UPPER_LIMIT) {
      return true;
    }
    return false;
  },
  message: { locale: 'validation.errors.initialWeight.tooHigh' },
});

/**
 * Checks weight value against the defined lower range for weight
 * Called from inside a validation rule
 **/
export const weightLowerRange = () => ({
  rule: value => {
    if (value === undefined || value > WeightHeightRanges.WEIGHT_LOWER_LIMIT) {
      return true;
    }
    return false;
  },
  message: { locale: 'validation.errors.initialWeight.tooLow' },
});

export const membershipCardNumberLength = cardNumberRequired => ({
  rule: value => {
    // pass validation when field is empty not required to stop crash
    if (!cardNumberRequired && !value) {
      return true;
    }

    const valueNoSpace = stripSpaces(value);

    return (
      (valueNoSpace.length === MembershipCard.CARD_MIN_LENGTH ||
        valueNoSpace.length === MembershipCard.CARD_MAX_LENGTH) &&
      !!Number(valueNoSpace)
    );
  },
  message: { locale: `validation.errors.${membershipCardFields.CARD_NUMBER}.range` },
});

// Strip spaces from a string
export const stripSpaces = value => {
  const spaceRegex = new RegExp(' ', 'g');
  return value.replace(spaceRegex, '');
};

/*
 * Simple helper function to create a complete 'required' validation config,
 * when there is no other validations needed upo the field.
 */
const createSimpleRequiredValidation = (fieldName, validateOn, formName) => ({
  [fieldName]: {
    validators: [createRequiredRule(fieldName, formName)],
    validateOn: validateOn || [ValidateOn.BLUR],
  },
});

export default createSimpleRequiredValidation;
export const usernameValid = regexTester(/^[a-z0-9]([._][a-z0-9]|[a-z0-9])*$/i);
export const usernameMaxLength = value => maxLength(value, 19);
export const usernameMinLength = value => minLength(value, 3);