Source: app/validation/addressValidation.js

import { groupMigrationOrMember } from 'common/src/app/selectors/accountStateSelectors';
import { addressFields } from '../data/enum/FieldNames/AccountFieldNames';
import { getApiNoCache } from '../actions/externalApiActions';
import { POSTCODE_IO_VALIDATE } from '../data/externalApis';
import * as ValidateOn from '../enhanced-redux-form/data/ValidateOn';
import { countryCodeValue, countryCode } from '../data/enum/Countries';
import * as AddressFormats from '../data/AddressFormats';
import { POSTAL_CODES } from '../data/regexPatterns';
import CountryPostalCode from '../data/enum/CountryPostalCode';
import getCountry from '../util/getCountry';

const addressFieldsArray = [
  addressFields.ADDRESS_LINE_1,
  addressFields.CITY_OR_TOWN,
  addressFields.STATE,
  addressFields.ZIP_OR_POSTAL,
  addressFields.COUNTY,
  addressFields.COUNTRY,
];

// These are the only mandatory address field(s) for the group member
const requiredGroupAddressField = [addressFields.ZIP_OR_POSTAL];

/**
 * Returns amended fieldnames
 * @param {string} addressField | address fieldname
 * @param {string} addressType | billing, shipping or null
 */
const formatAddressType = (addressField, addressType) =>
  addressType ? `${addressType}.${addressField}` : addressField;

/**
 * Returns rules for region specific field
 * @param {string} region
 * @param {string} fieldName
 * @param {string} rule
 *
 */
const getCountryFieldRules = (country, fieldName, rule) => {
  const countryCodeString = countryCodeValue[country] || countryCode.DEFAULT;
  // For IRELAND we need to do the validation for the COUNTY_IE field
  const fieldNameValue =
    country === countryCode.IRELAND && fieldName === addressFields.COUNTY
      ? addressFields.COUNTY_IE
      : fieldName;
  const currentRules = countryCodeString && AddressFormats[countryCodeString][fieldNameValue];

  return currentRules && currentRules.find(fieldRule => fieldRule === rule);
};

/**
 * Helper function to determine if the postcode is BFPO valid
 *
 * @param {string} postcode
 */
export const isBfpoPostcode = postcode => {
  const postcodeUpper = postcode.toUpperCase();

  return postcodeUpper.startsWith('BF1') || postcodeUpper.startsWith('BF2');
};

/**
 * Helper function to determine if a UK postcode is valid, skips checks if
 * postcode is a BFPO postcode
 *
 * @param {string} postcode
 */
export const isValidUkPostcode = postcode => async dispatch => {
  if (!isBfpoPostcode(postcode)) {
    return await dispatch(getApiNoCache(POSTCODE_IO_VALIDATE, postcode));
  }

  return true;
};

export const createIsValidUkPostcodeRule = async (value, values, fieldName, dispatch) => {
  const addressType = fieldName.substring(0, fieldName.indexOf('.'));

  // Only run validation when checking the postcode field
  if (fieldName === formatAddressType(addressFields.ZIP_OR_POSTAL, addressType)) {
    // if country selected is UK perform validation
    const countryValue = addressType
      ? values?.[addressType]?.[addressFields.COUNTRY]
      : values?.[addressFields.COUNTRY];
    if (countryValue === countryCode.GB && value) {
      return await dispatch(isValidUkPostcode(value));
    }

    return true;
  }
  return true;
};

/**
 *
 * @param {string} addressType | current validations address type
 * Returns an object with validation fields filled for address type and region
 */
const address = (addressType = '', validateOn = [ValidateOn.BLUR]) =>
  addressFieldsArray.reduce((obj, fieldName) => {
    const fieldNameWithType = formatAddressType(fieldName, addressType);
    let country = '';
    // eslint-disable-next-line no-param-reassign
    obj[fieldNameWithType] = {
      validators: [
        {
          rule: async (value, values, fieldname, dispatch) =>
            await createIsValidUkPostcodeRule(value, values, fieldNameWithType, dispatch),
          message: {
            locale: `validation.errors.${addressFields.ZIP_OR_POSTAL}.invalid`,
          },
        },
        // Adds required validation to all fields
        {
          rule: (value, values, fieldname, dispatch) => {
            country = getCountry(values, addressType);
            return (dispatch(groupMigrationOrMember()) &&
              requiredGroupAddressField.indexOf(fieldName) > -1) ||
              (!dispatch(groupMigrationOrMember()) &&
                getCountryFieldRules(getCountry(values, addressType), fieldName, 'required'))
              ? value || value === 0
              : true;
          },
          message: {
            locale: `validation.errors.${fieldName}.required`,
            params: {
              POSTAL_CODE: () => CountryPostalCode[countryCodeValue[country]] || 'Zipcode',
            },
          },
        },
        // Adds format validation to all fields
        {
          rule: (value, values) => {
            country = getCountry(values, addressType);
            return getCountryFieldRules(country, fieldName, 'postCode')
              ? POSTAL_CODES[country].test(value)
              : true;
          },
          message: {
            locale: `validation.errors.${fieldName}.formatError`,
            params: {
              POSTAL_CODE: () => CountryPostalCode[countryCodeValue[country]] || 'Zipcode',
            },
          },
        },
      ],
      validateOn,
    };
    return obj;
  }, {});

export default address;