const validationEl = {
    idsOfInputsWithNoNeedToUpdateDuringTyping: [
        'address-lookup-query',
        'modal-address-lookup-query',
    ],
};

const formElements = {
    formInvalid: 'form_invalid',
    fieldRow: '.form-row[data-row-required]',
    rowValidationError: 'form-row_validation-error',
};

const patterns = {
    guidPattern: /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i,
    userFriendlyIdPattern: /^[A-Za-z]{2,3}\d{6}$/,
};

export function addFieldValidation(fieldSelector, validationFunction) {
    $(fieldSelector).each(function (index, element) {
        $(element).blur(function () {
            updateInputValueBeforeValidation(this);
            validationFunction(this);
            if (!validationEl.idsOfInputsWithNoNeedToUpdateDuringTyping.includes(element.id)) {
                $(this).keyup(function () {
                    updateInputValueBeforeValidation(this);
                    validationFunction(this);
                });
            }
        });
    });
}

export function updateInputValueBeforeValidation(element) {
    $(element).val($.trim($(element).val()));
}

export function validateEmail(element) {
    updateStylingError(element.id, isValidEmail(element.value), invalidEmailAddress);
}

export function isValidEmail(value) {
    const ipAddressPattern =
            '(?:(?:25[0-5]|2[0-4][\\d]|[01]?[\\d][\\d]?)\\.){3}(?:25[0-5]|2[0-4][\\d]|[01]?[\\d][\\d]?)',
        emailPattern = new RegExp(
            '^(?=.{1,64}@)[\\w\\+-]+(\\.[\\w\\+-]+)*@(([A-Za-z\\d]+[A-Za-z\\d-]*\\.){1,3}[A-Za-z]{2,16}|' +
                ipAddressPattern +
                ')$'
        ),
        charactersLimit = 254,
        isCharactersLimitReached = value.length <= charactersLimit;
    return emailPattern.test(value) && isCharactersLimitReached;
}

export function validatePassword(element) {
    updateStylingError(element.id, isValidPassword(element.value), invalidPassword);
}

export function isValidPassword(value) {
    return value.length > 0;
}

export function validateTitle(element) {
    updateStylingError(element.id, isValidTitle(element.value), invalidTitle);
}

export function isValidTitle(value) {
    return value.length > 0;
}

export function isValidOrderNumber(value) {
    return value.length >= 9;
}

export function validateFirstName(element) {
    updateStylingError(element.id, isValidInputTextField(element.value), invalidFirstName);
}

export function validateLastName(element) {
    updateStylingError(element.id, isValidInputTextField(element.value), invalidLastName);
}

export function validateAddress1(element) {
    updateStylingError(element.id, isValidInputTextField(element.value), invalidAddressOne);
}

export function validateCity(element) {
    updateStylingError(element.id, isValidInputTextField(element.value), invalidTownCity);
}

export function isValidFieldSize(value) {
    return value.length > 0 && value.length < 256;
}

export function isValidPasswordSize(value) {
    return value.length > 5 && value.length < 256;
}

export function isValidInputTextField(value) {
    var regExp = new RegExp("^[\\w ()/&.,'-]+$|^$");
    return isValidFieldSize(value) && regExp.test(value);
}

export function validateMobile(element, allowEmptyValue) {
    updateStylingError(
        element.id,
        isValidPhoneNumber(element.value, allowEmptyValue),
        invalidMobile
    );
}

export function validatePhone(element) {
    updateStylingError(element.id, isValidPhoneNumber(element.value, true), invalidTelephone);
}

export function isValidPhoneNumber(value, allowEmptyValue) {
    value = value.replace(/\s+/g, '');

    var phonePatterElevenDigit = new RegExp(
        '^0{1}\\d{10}$|^0{1}\\d{5}\\s{1}\\d{5}$|^0{1}\\d{4}\\s{1}\\d{6}$|^0{1}\\d{3}\\s{1}\\d{3}\\s{1}\\d{4}$|^0{1}\\d{2}\\s{1}\\d{4}\\s{1}\\d{4}$|^0{1}\\d{3}\\s{1}\\d{7}$'
    );
    var phonePatternTenDigit = new RegExp(
        '^0{1}\\d{9}$|^0{1}\\d{5}\\s{1}\\d{4}$|^0{1}\\d{4}\\s{1}\\d{5}$'
    );
    var result = phonePatterElevenDigit.test(value) || phonePatternTenDigit.test(value);

    if (allowEmptyValue) {
        return value.length === 0 || result;
    } else {
        return value.length > 0 && result;
    }
}

export function validatePostcode(element, ifConvert) {
    var elementValue = element.value;
    var isValid = isValidPostcodeField(elementValue);
    if (ifConvert && isValid) {
        elementValue = elementValue.replace(/\s/g, '');
        $('#' + $.escapeSelector(element.id)).val(
            elementValue.substr(0, elementValue.length - 3) +
                ' ' +
                elementValue.substr(elementValue.length - 3)
        );
    }
    updateStylingError(element.id, isValid, invalidPostCode);
}

export function isValidPostcodeField(value) {
    const transformedValue = value.toUpperCase().replaceAll(' ', '');
    const regExp = new RegExp(
        '^(GIR 0AA)|((([A-PR-UWYZ][0-9][0-9]?)|([A-PR-UWYZ][A-HK-Y][0-9][0-9]?)|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))[0-9][A-BD-HJLNP-UW-Z]{2})$'
    );
    return transformedValue.replace(/\s+/g, '').length > 4 && regExp.test(transformedValue);
}

export function validateHouseName(element) {
    updateStylingError(element.id, isValidHouseName(element.value));
}

export function isValidHouseName(value) {
    var regExp = new RegExp("^[\\w ()/&.,'-]+$|^$");
    return value.length > 0 && regExp.test(value);
}

export function validateAddressSelection(element) {
    updateStylingError(element.id, isValidAddressSelection(element), selectAddressMessage);
}

export function isValidAddressSelection(element) {
    return !$('#' + $.escapeSelector(element.id)).is(':visible');
}

export function validateAddress2(element) {
    updateStylingError(element.id, isValidAddress2(element.value), invalidAddressTwo);
}

export function isValidAddress2(value) {
    var regExp = new RegExp("^[\\w ().,'-]+$|^$");
    return value.length === 0 || regExp.test(value);
}

export function validateVoucher(element) {
    updateStylingErrorForElement(element, isValidVoucher(element.value), invalidVoucherCode);
}

export function isValidVoucher(value) {
    return value.trim().length > 0;
}

export function validateNameOnCard(element) {
    updateStylingError(element.id, true);
    var fieldSizeIsValid = isValidInputTextField(element.value);

    if (fieldSizeIsValid) {
        if (element.value.length > nameOnCardMaxLength) {
            updateStylingError(element.id, false, invalidNameOnCardLength);
        } else if (isNameOnCardContainsOnly16Digits(element.value)) {
            updateStylingError(element.id, false, invalidCustomNameOnCard);
        } else if (isNameOnCardContainsAtLeast14Digits(element.value)) {
            updateStylingError(element.id, false, validationWarnNameOnCard);
        }
    } else {
        updateStylingError(element.id, fieldSizeIsValid, invalidNameOnCard);
    }
}

export function isNameOnCardContainsOnly16Digits(value) {
    var regExp = new RegExp('^\\d{16}$');
    return regExp.test(value);
}

export function isNameOnCardContainsAtLeast14Digits(value) {
    var regExp = new RegExp('^(.*\\d){14,}.*$');
    return regExp.test(value);
}

export function validateStoreId(element) {
    updateStylingError(element.id, isValidStoreId(element.value), invalidStoreId);
}

export function isValidStoreId(value) {
    return value !== '0';
}

export function updateStylingError(elementId, isValid, errorMessage) {
    let element = $(elementId);
    updateStylingErrorForElement(element, isValid, errorMessage);
}

export function updateStylingErrorForElement(element, isValid, errorMessage) {
    var parentElem = $(element).closest('.form-row');
    if (!parentElem.is(':visible')) {
        return;
    }
    removeErrorStyling(parentElem);
    if (!isValid) {
        wrapWithError(parentElem);
        addErrorMessage(parentElem, errorMessage);
    }
}

export function removeErrorStyling(element) {
    element.removeClass('form-row_validation-error');
    element.find('.form-row__error').remove();
}

export function wrapWithError(element) {
    $(element).addClass('form-row_validation-error');
}

export function addErrorMessage(element, errorMessage) {
    if (errorMessage) {
        $(element).append('<div class="form-row__error">' + errorMessage + '</div>');
    }
}

export function showValidationErrors(form, errorMsg) {
    var $form = $(form);
    if (!$form.hasClass(formElements.formInvalid)) {
        $form.find('.form-row__error').remove();
        $form
            .addClass(formElements.formInvalid)
            .find(formElements.fieldRow)
            .append(validationError(errorMsg))
            .addClass(formElements.rowValidationError);
    } else {
        $form.find(formElements.fieldRow).find('.form-row__error').text(errorMsg);
    }

    var $firstMsg = $form.find(formElements.fieldRow).first();
    if ($firstMsg.length && !isElementInViewport($firstMsg)) {
        var inModal = $firstMsg.closest('.modal').length,
            $scrollableEl = inModal ? $firstMsg.closest('.modal') : $('html, body'),
            y = inModal ? 0 : $firstMsg.offset().top - 30;

        $scrollableEl.animate({ scrollTop: y }, 300);
    }
}

function isElementInViewport(el) {
    if (typeof jQuery === 'function' && el instanceof jQuery) {
        el = el[0];
    }
    var rect = el.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

function validationError(errorMsg) {
    return `<div class="form-row__error"> ${errorMsg || 'Error message here'}</div>`;
}

export function clearValidationErrors(form) {
    const $form = $(form);
    if ($form.hasClass('form_invalid')) {
        $form
            .removeClass('form_invalid')
            .find('.form-row[data-row-required]')
            .removeClass('form-row_validation-error')
            .find('.form-row__error')
            .remove();
    }
}

export function validateGuidPattern(value) {
    return patterns.guidPattern.test(value);
}

export function validateUserFriendlyIdPattern(value) {
    return patterns.userFriendlyIdPattern.test(value);
}

export function showErrorText(
    $input,
    message,
    fieldSelector,
    errorTextClass,
    errorClass,
    ariaInvalidAttr
) {
    const $field = $input.closest(fieldSelector);
    $field.addClass(errorClass);
    $input.attr(ariaInvalidAttr, 'true');

    let $err = $field.find(errorTextClass);
    if (!$err.length) {
        $err = $(`<div class="${errorTextClass.slice(1)}" />`).appendTo($field);
    }
    $err.text(message);
}

export function clearErrorText($input, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr) {
    const $field = $input.closest(fieldSelector);
    $field.removeClass(errorClass);
    $input.removeAttr(ariaInvalidAttr);
    $field.find(errorTextClass).remove();
}

function digitsOnly(value) {
    return String(value || '').replace(/\D/g, '');
}

function hasExactDigits(value, n) {
    return digitsOnly(value).length === Number(n);
}

function validateByPredicate(
    $input,
    predicate,
    message,
    { fieldSelector, errorText: errorTextClass, fieldError: errorClass, ariaInvalidAttr }
) {
    const ok = !!predicate(String($input.val() || ''));
    if (!ok) {
        showErrorText($input, message, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr);
    } else {
        clearErrorText($input, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr);
    }
    return ok;
}

export function validateExactDigits($input, n, message, cfg) {
    return validateByPredicate($input, (v) => hasExactDigits(v, n), message, cfg);
}
