/* global wpforms_settings, grecaptcha, hcaptcha, turnstile, wpformsRecaptchaCallback, wpformsRecaptchaV3Execute, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck, Choices, WPFormsPasswordField, WPFormsEntryPreview, punycode, tinyMCE, WPFormsUtils, JQueryDeferred, JQueryXHR, WPFormsRepeaterField, WPFormsPhoneField */
/* eslint-disable no-unused-expressions, no-shadow, no-unused-vars */
/**
 * @param wpforms_settings.hn_data
 */
// noinspection ES6ConvertVarToLetConst
/**
 * WPForms object.
 *
 * @since 1.4.0
 */
var wpforms = window.wpforms || ( function( document, window, $ ) { // eslint-disable-line no-var
	/**
	 * Public functions and properties.
	 *
	 * @since 1.8.9
	 *
	 * @type {Object}
	 */
	const app = {
		/**
		 * Cache.
		 *
		 * @since 1.8.5
		 */
		cache: {},
		/**
		 * Is updating token via ajax flag.
		 *
		 * @since 1.8.8
		 */
		isUpdatingToken: false,
		/**
		 * Start the engine.
		 *
		 * @since 1.2.3
		 */
		init() {
			// Document ready.
			$( app.ready );
			// Page load.
			$( window ).on( 'load', function() {
				// In the case of jQuery 3.+, we need to wait for a ready event first.
				if ( typeof $.ready.then === 'function' ) {
					$.ready.then( app.load );
				} else {
					app.load();
				}
			} );
			app.bindUIActions();
			app.bindOptinMonster();
		},
		/**
		 * Document ready.
		 *
		 * @since 1.2.3
		 */
		ready() {
			// Clear URL - remove wpforms_form_id.
			app.clearUrlQuery();
			// Set user identifier.
			app.setUserIdentifier();
			app.loadValidation();
			app.loadHoneypot();
			app.loadDatePicker();
			app.loadTimePicker();
			app.loadInputMask();
			app.loadPayments();
			app.loadMailcheck();
			app.loadChoicesJS();
			app.initTokenUpdater();
			app.restoreSubmitButtonOnEventPersisted();
			app.bindChoicesJS();
			// Randomize elements.
			$( '.wpforms-randomize' ).each( function() {
				const $list = $( this ),
					$listItems = $list.children();
				while ( $listItems.length ) {
					$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[ 0 ] );
				}
			} );
			// Unlock pagebreak navigation.
			$( '.wpforms-page-button' ).prop( 'disabled', false );
			// Init forms' start timestamp.
			app.initFormsStartTime();
			$( document ).trigger( 'wpformsReady' );
		},
		/**
		 * Page load.
		 *
		 * @since 1.2.3
		 */
		load() {
		},
		//--------------------------------------------------------------------//
		// Initializing
		//--------------------------------------------------------------------//
		/**
		 * Remove wpforms_form_id from URL.
		 *
		 * @since 1.5.2
		 */
		clearUrlQuery() {
			const loc = window.location;
			let query = loc.search;
			if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
				query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
				history.replaceState( {}, null, loc.origin + loc.pathname + query );
			}
		},
		/**
		 * Load honeypot v2 field.
		 *
		 * @since 1.9.0
		 */
		loadHoneypot() {
			$( '.wpforms-form' ).each( function() {
				const $form = $( this ),
					formId = $form.data( 'formid' ),
					fieldIds = [],
					fieldLabels = [];
				// Bail early if honeypot protection is disabled for the form.
				if ( wpforms_settings.hn_data[ formId ] === undefined ) {
					return;
				}
				// Collect all field IDs and labels.
				$( `#wpforms-form-${ formId } .wpforms-field` ).each( function() {
					const $field = $( this );
					fieldIds.push( $field.data( 'field-id' ) );
					fieldLabels.push( $field.find( '.wpforms-field-label' ).text() );
				} );
				const label = app.getHoneypotRandomLabel( fieldLabels.join( ' ' ).split( ' ' ) ),
					honeypotFieldId = app.getHoneypotFieldId( fieldIds );
				// Insert the honeypot field before a random field.
				const insertBeforeId = fieldIds[ Math.floor( Math.random() * fieldIds.length ) ],
					honeypotIdAttr = `wpforms-${ formId }-field_${ honeypotFieldId }`,
					$insertBeforeField = $( `#wpforms-${ formId }-field_${ insertBeforeId }-container`, $form ),
					inlineStyles = 'position: absolute !important; overflow: hidden !important; display: inline !important; height: 1px !important; width: 1px !important; z-index: -1000 !important; padding: 0 !important;',
					labelInlineStyles = 'counter-increment: none;',
					fieldHTML = `
						
							
							
						
`;
				$insertBeforeField.before( fieldHTML );
				// Add inline properties for honeypot field on the form.
				const $fieldContainer = $( `#wpforms-${ formId }-field_${ wpforms_settings.hn_data[ formId ] }-container`, $form );
				$fieldContainer.find( 'input' ).attr( {
					tabindex: '-1',
					'aria-hidden': 'true',
				} );
				$fieldContainer.find( 'label' ).text( label ).attr( 'aria-hidden', 'true' );
			} );
		},
		/**
		 * Generate random Honeypot label.
		 *
		 * @since 1.9.0
		 *
		 * @param {Array} words List of words.
		 *
		 * @return {string} Honeypot label.
		 */
		getHoneypotRandomLabel( words ) {
			let label = '';
			for ( let i = 0; i < 3; i++ ) {
				label += words[ Math.floor( Math.random() * words.length ) ] + ' ';
			}
			return label.trim();
		},
		/**
		 * Get Honeypot field ID.
		 *
		 * @since 1.9.0
		 *
		 * @param {Array} fieldIds List of the form field IDs.
		 *
		 * @return {number} Honeypot field ID.
		 */
		getHoneypotFieldId( fieldIds ) {
			const maxId = Math.max( ...fieldIds );
			let honeypotFieldId = 0;
			// Find the first available field ID.
			for ( let i = 1; i < maxId; i++ ) {
				if ( ! fieldIds.includes( i ) ) {
					honeypotFieldId = i;
					break;
				}
			}
			// If no available field ID found, use the max ID + 1.
			if ( ! honeypotFieldId ) {
				honeypotFieldId = maxId + 1;
			}
			return honeypotFieldId;
		},
		/**
		 * Load jQuery Validation.
		 *
		 * @since 1.2.3
		 */
		loadValidation() { // eslint-disable-line max-lines-per-function
			// Only load if jQuery validation library exists.
			if ( typeof $.fn.validate === 'undefined' ) {
				if ( window.location.hash && '#wpformsdebug' === window.location.hash ) {
					// eslint-disable-next-line no-console
					console.log( 'jQuery Validation library not found.' );
				}
				return;
			}
			// jQuery Validation library will not correctly validate
			// fields that do not have a name attribute, so we use the
			// `wpforms-input-temp-name` class to add a temporary name
			// attribute before validation is initialized, then remove it
			// before the form submits.
			$( '.wpforms-input-temp-name' ).each( function( index, el ) {
				const random = Math.floor( Math.random() * 9999 ) + 1;
				$( this ).attr( 'name', 'wpf-temp-' + random );
			} );
			// Prepend URL field contents with https:// if user input doesn't contain a schema.
			$( document ).on( 'change', '.wpforms-validate input[type=url]', function() {
				const url = $( this ).val();
				if ( ! url ) {
					return false;
				}
				if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
					$( this ).val( 'https://' + url );
				}
			} );
			$.validator.messages.required = wpforms_settings.val_required;
			$.validator.messages.url = wpforms_settings.val_url;
			$.validator.messages.email = wpforms_settings.val_email;
			$.validator.messages.number = wpforms_settings.val_number;
			// Payments: Validate method for Credit Card Number.
			if ( typeof $.fn.payment !== 'undefined' ) {
				$.validator.addMethod( 'creditcard', function( value, element ) {
					//var type  = $.payment.cardType(value);
					const valid = $.payment.validateCardNumber( value );
					return this.optional( element ) || valid;
				}, wpforms_settings.val_creditcard );
				// @todo validate CVC and expiration
			}
			// Validate method for file extensions.
			$.validator.addMethod( 'extension', function( value, element, param ) {
				param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
				return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
			}, wpforms_settings.val_fileextension );
			// Validate method for file size.
			$.validator.addMethod( 'maxsize', function( value, element, param ) {
				const maxSize = param,
					optionalValue = this.optional( element );
				let i, len, file;
				if ( optionalValue ) {
					return optionalValue;
				}
				if ( element.files && element.files.length ) {
					i = 0;
					len = element.files.length;
					for ( ; i < len; i++ ) {
						file = element.files[ i ];
						if ( file.size > maxSize ) {
							return false;
						}
					}
				}
				return true;
			}, wpforms_settings.val_filesize );
			$.validator.addMethod( 'step', function( value, element, param ) {
				const decimalPlaces = function( num ) {
					if ( Math.floor( num ) === num ) {
						return 0;
					}
					return num.toString().split( '.' )[ 1 ].length || 0;
				};
				const decimals = decimalPlaces( param );
				const decimalToInt = function( num ) {
					return Math.round( num * Math.pow( 10, decimals ) );
				};
				const min = decimalToInt( $( element ).attr( 'min' ) );
				value = decimalToInt( value ) - min;
				return this.optional( element ) || decimalToInt( value ) % decimalToInt( param ) === 0;
			} );
			// Validate email addresses.
			$.validator.methods.email = function( value, element ) {
				/**
				 * This function combines is_email() from WordPress core
				 * and wpforms_is_email() to validate email addresses.
				 *
				 * @see https://developer.wordpress.org/reference/functions/is_email/
				 * @see https://github.com/awesomemotive/wpforms-plugin/blob/develop/wpforms/includes/functions/checks.php#L45
				 *
				 * @param {string} value The email address to validate.
				 *
				 * @return {boolean} True if the email address is valid, false otherwise.
				 */
				const isEmail = function( value ) { // eslint-disable-line complexity
					if ( typeof value !== 'string' ) {
						// Do not allow callables, arrays, and objects.
						return false;
					}
					// Check the length and position of the @ character.
					const atIndex = value.indexOf( '@', 1 );
					if ( value.length < 6 || value.length > 254 || atIndex === -1 ) {
						return false;
					}
					// Check for more than one "@" symbol.
					if ( value.indexOf( '@', atIndex + 1 ) !== -1 ) {
						return false;
					}
					// Split email address into local and domain parts.
					const [ local, domain ] = value.split( '@' );
					// Check local and domain parts for existence.
					if ( ! local || ! domain ) {
						return false;
					}
					// Check local part for invalid characters and length.
					const localRegex = /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/;
					if ( ! localRegex.test( local ) || local.length > 63 ) {
						return false;
					}
					// Check domain part for sequences of periods, leading and trailing periods, and whitespace.
					const domainRegex = /\.{2,}/;
					if ( domainRegex.test( domain ) || domain.trim( ' \t\n\r\0\x0B.' ) !== domain ) {
						return false;
					}
					// Check domain part for length.
					const domainArr = domain.split( '.' );
					if ( domainArr.length < 2 ) {
						return false;
					}
					// Check domain label for length, leading and trailing periods, and whitespace.
					const domainLabelRegex = /^[a-z0-9-]+$/i;
					for ( const domainLabel of domainArr ) {
						if (
							domainLabel.length > 63 ||
							domainLabel.trim( ' \t\n\r\0\x0B-' ) !== domainLabel ||
							! domainLabelRegex.test( domainLabel )
						) {
							return false;
						}
					}
					return true;
				};
				// Congratulations! The email address is valid.
				return this.optional( element ) || isEmail( value );
			};
			// Validate email by allowlist/blocklist.
			$.validator.addMethod( 'restricted-email', function( value, element ) {
				const $el = $( element );
				if ( ! $el.val().length ) {
					return true;
				}
				const $form = $el.closest( '.wpforms-form' ),
					formId = $form.data( 'formid' );
				if (
					! Object.prototype.hasOwnProperty.call( app.cache, formId ) ||
					! Object.prototype.hasOwnProperty.call( app.cache[ formId ], 'restrictedEmailValidation' ) ||
					! Object.prototype.hasOwnProperty.call( app.cache[ formId ].restrictedEmailValidation, value )
				) {
					app.restrictedEmailRequest( element, value );
					return 'pending';
				}
				return app.cache[ formId ].restrictedEmailValidation[ value ];
			}, wpforms_settings.val_email_restricted );
			// Validate confirmations.
			$.validator.addMethod( 'confirm', function( value, element, param ) {
				const field = $( element ).closest( '.wpforms-field' );
				return $( field.find( 'input' )[ 0 ] ).val() === $( field.find( 'input' )[ 1 ] ).val();
			}, wpforms_settings.val_confirm );
			// Validate required payments.
			$.validator.addMethod( 'required-payment', function( value, element ) {
				return app.amountSanitize( value ) > 0;
			}, wpforms_settings.val_requiredpayment );
			// Validate 12-hour time.
			$.validator.addMethod( 'time12h', function( value, element ) {
				// noinspection RegExpRedundantEscape
				return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); // eslint-disable-line no-useless-escape
			}, wpforms_settings.val_time12h );
			// Validate 24-hour time.
			$.validator.addMethod( 'time24h', function( value, element ) {
				// noinspection RegExpRedundantEscape
				return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value ); // eslint-disable-line no-useless-escape
			}, wpforms_settings.val_time24h );
			// Validate Turnstile captcha.
			$.validator.addMethod( 'turnstile', function( value ) {
				return value;
			}, wpforms_settings.val_turnstile_fail_msg );
			// Validate time limits.
			$.validator.addMethod( 'time-limit', function( value, element ) { // eslint-disable-line complexity
				const $input = $( element ),
					minTime = $input.data( 'min-time' ),
					isLimited = typeof minTime !== 'undefined';
				if ( ! isLimited ) {
					return true;
				}
				const isRequired = $input.prop( 'required' );
				if ( ! isRequired && app.empty( value ) ) {
					return true;
				}
				const maxTime = $input.data( 'max-time' );
				if ( app.compareTimesGreaterThan( maxTime, minTime ) ) {
					return app.compareTimesGreaterThan( value, minTime ) && app.compareTimesGreaterThan( maxTime, value );
				}
				return ( app.compareTimesGreaterThan( value, minTime ) && app.compareTimesGreaterThan( value, maxTime ) ) ||
					( app.compareTimesGreaterThan( minTime, value ) && app.compareTimesGreaterThan( maxTime, value ) );
			}, function( params, element ) {
				const $input = $( element );
				let minTime = $input.data( 'min-time' ),
					maxTime = $input.data( 'max-time' );
				// Replace `00:**pm` with `12:**pm`.
				minTime = minTime.replace( /^00:([0-9]{2})pm$/, '12:$1pm' );
				maxTime = maxTime.replace( /^00:([0-9]{2})pm$/, '12:$1pm' );
				// Proper format time: add space before AM/PM, make uppercase.
				minTime = minTime.replace( /(am|pm)/g, ' $1' ).toUpperCase();
				maxTime = maxTime.replace( /(am|pm)/g, ' $1' ).toUpperCase();
				return wpforms_settings.val_time_limit
					.replace( '{minTime}', minTime )
					.replace( '{maxTime}', maxTime );
			} );
			// Validate checkbox choice limit.
			$.validator.addMethod( 'check-limit', function( value, element ) {
				const $ul = $( element ).closest( 'ul' ),
					choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
				if ( 0 === choiceLimit ) {
					return true;
				}
				const $checked = $ul.find( 'input[type="checkbox"]:checked' );
				return $checked.length <= choiceLimit;
			}, function( params, element ) {
				const	choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
				return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
			} );
			// Validate Inputmask completeness.
			$.validator.addMethod( 'inputmask-incomplete', function( value, element ) {
				if ( value.length === 0 || typeof $.fn.inputmask === 'undefined' ) {
					return true;
				}
				return $( element ).inputmask( 'isComplete' );
			}, wpforms_settings.val_inputmask_incomplete );
			// Validate Payment item value on zero.
			$.validator.addMethod( 'required-positive-number', function( value, element ) {
				return app.amountSanitize( value ) > 0;
			}, wpforms_settings.val_number_positive );
			/**
			 * Validate Payment item minimum price value.
			 *
			 * @since 1.8.6
			 */
			$.validator.addMethod( 'required-minimum-price', function( value, element, param ) {
				const $el = $( element );
				/**
				 * The validation is passed in the following cases:
				 * 1) if a field is not filled in and not required.
				 * 2) if the minimum required price is equal to or less than the typed value.
				 * Note: since the param is returned in decimal format at all times, we need to format the value to compare it.
				 */
				return ( value === '' && ! $el.hasClass( 'wpforms-field-required' ) ) || Number( app.amountSanitize( app.amountFormat( param ) ) ) <= Number( app.amountSanitize( value ) );
			}, wpforms_settings.val_minimum_price );
			// Validate password strength.
			$.validator.addMethod( 'password-strength', function( value, element ) {
				const $el = $( element );
				// Need to check if the password strength to remove the error message.
				const strength = WPFormsPasswordField.passwordStrength( value, element );
				/**
				 * The validation is passed in the following cases:
				 * 1) if a field is not filled in and not required.
				 * 2) if the password strength is equal to or greater than the specified level.
				 */
				return ( value === '' && ! $el.hasClass( 'wpforms-field-required' ) ) || strength >= Number( $el.data( 'password-strength-level' ) );
			}, wpforms_settings.val_password_strength );
			// Finally, load jQuery Validation library for our forms.
			$( '.wpforms-validate' ).each( function() { // eslint-disable-line max-lines-per-function
				const form = $( this ),
					formID = form.data( 'formid' );
				let	properties;
				// TODO: cleanup this BC with wpforms_validate.
				if ( typeof window[ 'wpforms_' + formID ] !== 'undefined' && window[ 'wpforms_' + formID ].hasOwnProperty( 'validate' ) ) {
					properties = window[ 'wpforms_' + formID ].validate;
				} else if ( typeof wpforms_validate !== 'undefined' ) {
					properties = wpforms_validate;
				} else {
					properties = {
						errorElement: app.isModernMarkupEnabled() ? 'em' : 'label',
						errorClass: 'wpforms-error',
						validClass: 'wpforms-valid',
						ignore: ':hidden:not(textarea.wp-editor-area), .wpforms-conditional-hide textarea.wp-editor-area',
						ignoreTitle: true,
						errorPlacement( error, element ) { // eslint-disable-line complexity
							if ( app.isLikertScaleField( element ) ) {
								element.closest( 'table' ).hasClass( 'single-row' )
									? element.closest( '.wpforms-field' ).append( error )
									: element.closest( 'tr' ).find( 'th' ).append( error );
							} else if ( app.isWrappedField( element ) ) {
								element.closest( '.wpforms-field' ).append( error );
							} else if ( app.isDateTimeField( element ) ) {
								app.dateTimeErrorPlacement( element, error );
							} else if ( app.isFieldInColumn( element ) ) {
								element.parent().append( error );
							} else if ( app.isFieldHasHint( element ) ) {
								element.parent().append( error );
							} else if ( app.isLeadFormsSelect( element ) ) {
								element.parent().parent().append( error );
							} else if ( element.hasClass( 'wp-editor-area' ) ) {
								element.parent().parent().parent().append( error );
							} else {
								error.insertAfter( element );
							}
							if ( app.isModernMarkupEnabled() ) {
								error.attr( {
									role: 'alert',
									'aria-label': wpforms_settings.errorMessagePrefix,
									for: '',
								} );
							}
						},
						highlight( element, errorClass, validClass ) { // eslint-disable-line complexity
							const $element = $( element ),
								$field = $element.closest( '.wpforms-field' ),
								inputName = $element.attr( 'name' );
							if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
								$field.find( 'input[name="' + inputName + '"]' ).addClass( errorClass ).removeClass( validClass );
							} else {
								$element.addClass( errorClass ).removeClass( validClass );
							}
							// Remove password strength container for empty required password field.
							if (
								$element.attr( 'type' ) === 'password' &&
								$element.val().trim() === '' &&
								window.WPFormsPasswordField &&
								$element.data( 'rule-password-strength' ) &&
								$element.hasClass( 'wpforms-field-required' )
							) {
								WPFormsPasswordField.passwordStrength( '', element );
							}
							$field.addClass( 'wpforms-has-error' );
						},
						unhighlight( element, errorClass, validClass ) {
							const $element = $( element ),
								$field = $element.closest( '.wpforms-field' ),
								inputName = $element.attr( 'name' );
							if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
								$field.find( 'input[name="' + inputName + '"]' ).addClass( validClass ).removeClass( errorClass );
							} else {
								$element.addClass( validClass ).removeClass( errorClass );
							}
							// Remove the error class from the field container if there are no subfield errors.
							if ( ! $field.find( ':input.wpforms-error,[data-dz-errormessage]:not(:empty)' ).length ) {
								$field.removeClass( 'wpforms-has-error' );
							}
							// Remove an error message to be sure the next time the `errorPlacement` method will be executed.
							if ( app.isModernMarkupEnabled() ) {
								$element.parent().find( 'em.wpforms-error' ).remove();
							}
						},
						submitHandler( form ) {
							/**
							 * Captcha error handler.
							 *
							 * @since 1.8.4
							 *
							 * @param {jQuery} $form      current form element.
							 * @param {jQuery} $container current form container.
							 */
							const captchaErrorDisplay = function( $form, $container ) {
								let errorTag = 'label',
									errorRole = '';
								if ( app.isModernMarkupEnabled() ) {
									errorTag = 'em';
									errorRole = 'role="alert"';
								}
								const error = `<${ errorTag } id="wpforms-field_recaptcha-error" class="wpforms-error" ${ errorRole }> ${ wpforms_settings.val_recaptcha_fail_msg }${ errorTag }>`;
								$form.find( '.wpforms-recaptcha-container' ).append( error );
								app.restoreSubmitButton( $form, $container );
							};
							const disableSubmitButton = function( $form ) {
								const $submit = $form.find( '.wpforms-submit' );
								$submit.prop( 'disabled', true );
								WPFormsUtils.triggerEvent( $form, 'wpformsFormSubmitButtonDisable', [ $form, $submit ] );
							};
							/**
							 * Submit handler routine.
							 *
							 * @since 1.7.2
							 *
							 * @return {boolean|void} False if form won't submit.
							 */
							const submitHandlerRoutine = function() { // eslint-disable-line complexity
								const $form = $( form ),
									$container = $form.closest( '.wpforms-container' ),
									$submit = $form.find( '.wpforms-submit' ),
									isCaptchaInvalid = $submit.data( 'captchaInvalid' ),
									altText = $submit.data( 'alt-text' ),
									recaptchaID = $submit.get( 0 ).recaptchaID;
								if ( $form.data( 'token' ) && 0 === $( '.wpforms-token', $form ).length ) {
									$( '' )
										.val( $form.data( 'token' ) )
										.appendTo( $form );
								}
								$form.find( '#wpforms-field_recaptcha-error' ).remove();
								disableSubmitButton( $form );
								// Display processing text.
								if ( altText ) {
									$submit.text( altText );
								}
								if ( isCaptchaInvalid ) {
									return captchaErrorDisplay( $form, $container );
								}
								if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
									// The Form contains invisible reCAPTCHA.
									grecaptcha.execute( recaptchaID ).then( null, function() {
										if ( grecaptcha.getResponse() ) {
											return;
										}
										captchaErrorDisplay( $form, $container );
									} );
									return false;
								}
								// Remove name attributes if needed.
								$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
								app.formSubmit( $form );
							};
							// In the case of active Google reCAPTCHA v3, first, we should call `grecaptcha.execute`.
							// This is needed to get a proper grecaptcha token before submitting the form.
							if ( typeof wpformsRecaptchaV3Execute === 'function' ) {
								disableSubmitButton( $( form ) );
								return wpformsRecaptchaV3Execute( submitHandlerRoutine );
							}
							return submitHandlerRoutine();
						},
						invalidHandler( event, validator ) {
							if ( typeof validator.errorList[ 0 ] !== 'undefined' ) {
								app.scrollToError( $( validator.errorList[ 0 ].element ) );
							}
						},
						onkeyup: WPFormsUtils.debounce( // eslint-disable-next-line complexity
							function( element, event ) {
								// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
								const excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
									return; // Disable onkeyup validation for some elements (e.g. remote calls).
								}
								// eslint-disable-next-line no-mixed-operators
								if ( event.which === 9 && this.elementValue( element ) === '' || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
								} else if ( element.name in this.submitted || element.name in this.invalid ) {
									this.element( element );
								}
							},
							1000
						),
						onfocusout: function( element ) { // eslint-disable-line complexity, object-shorthand
							// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
							let validate = false;
							if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
								validate = true; // Empty value error handling for elements with onkeyup validation disabled.
							}
							if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
								validate = true;
							}
							// If the error comes from server validation, we don't need to validate it again,
							// because it will clean the error message too early.
							if ( $( element ).data( 'server-error' ) ) {
								validate = false;
							}
							if ( validate ) {
								this.element( element );
							}
						},
						onclick( element ) {
							let validate = false;
							const type = ( element || {} ).type;
							let $el = $( element );
							if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
								if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
									$el = $el.closest( 'tr' );
								} else {
									$el = $el.closest( '.wpforms-field' );
								}
								$el.find( 'label.wpforms-error, em.wpforms-error' ).remove();
								validate = true;
							}
							if ( validate ) {
								this.element( element );
							}
						},
					};
				}
				form.validate( properties );
				app.loadValidationGroups( form );
			} );
		},
		/**
		 * Request to check if email is restricted.
		 *
		 * @since 1.8.5
		 *
		 * @param {Element} element Email input field.
		 * @param {string}  value   Field value.
		 */
		restrictedEmailRequest( element, value ) {
			const $el = $( element );
			const $form = $el.closest( 'form' );
			const validator = $form.data( 'validator' );
			const formId = $form.data( 'formid' );
			const $field = $el.closest( '.wpforms-field' );
			const fieldId = $field.data( 'field-id' );
			app.cache[ formId ] = app.cache[ formId ] || {};
			validator.startRequest( element );
			$.post( {
				url: wpforms_settings.ajaxurl,
				type: 'post',
				data: {
					action: 'wpforms_restricted_email',
					form_id: formId, // eslint-disable-line camelcase
					field_id: fieldId, // eslint-disable-line camelcase
					email: value,
				},
				dataType: 'json',
				success( response ) {
					const errors = {};
					const isValid = response.success && response.data;
					if ( ! isValid ) {
						errors[ element.name ] = wpforms_settings.val_email_restricted;
						validator.showErrors( errors );
					}
					app.cache[ formId ].restrictedEmailValidation = app.cache[ formId ].restrictedEmailValidation || [];
					if ( ! Object.prototype.hasOwnProperty.call( app.cache[ formId ].restrictedEmailValidation, value ) ) {
						app.cache[ formId ].restrictedEmailValidation[ value ] = isValid;
					}
					validator.stopRequest( element, isValid );
				},
			} );
		},
		/**
		 * Is field inside column.
		 *
		 * @since 1.6.3
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isFieldInColumn( element ) {
			return element.parent().hasClass( 'wpforms-one-half' ) ||
				element.parent().hasClass( 'wpforms-two-fifths' ) ||
				element.parent().hasClass( 'wpforms-one-fifth' );
		},
		/**
		 * Is field has hint (sublabel, description, limit text hint, etc.).
		 *
		 * @since 1.8.1
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isFieldHasHint( element ) {
			return element
				.nextAll( '.wpforms-field-sublabel, .wpforms-field-description, .wpforms-field-limit-text, .wpforms-pass-strength-result' )
				.length > 0;
		},
		/**
		 * Is datetime field.
		 *
		 * @since 1.6.3
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isDateTimeField( element ) {
			return element.hasClass( 'wpforms-timepicker' ) ||
				element.hasClass( 'wpforms-datepicker' ) ||
				( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) );
		},
		/**
		 * Is a field wrapped in some container.
		 *
		 * @since 1.6.3
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isWrappedField( element ) { // eslint-disable-line complexity
			return 'checkbox' === element.attr( 'type' ) ||
			'radio' === element.attr( 'type' ) ||
			'range' === element.attr( 'type' ) ||
			'select' === element.is( 'select' ) ||
			1 === element.data( 'is-wrapped-field' ) ||
			element.parent().hasClass( 'iti' ) ||
			element.hasClass( 'wpforms-validation-group-member' ) ||
			element.hasClass( 'choicesjs-select' ) ||
			element.hasClass( 'wpforms-net-promoter-score-option' ) ||
			element.hasClass( 'wpforms-field-payment-coupon-input' );
		},
		/**
		 * Is likert scale field.
		 *
		 * @since 1.6.3
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isLikertScaleField( element ) {
			return element.hasClass( 'wpforms-likert-scale-option' );
		},
		/**
		 * Is Lead Forms select field.
		 *
		 * @since 1.8.1
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isLeadFormsSelect( element ) {
			return element.parent().hasClass( 'wpforms-lead-forms-select' );
		},
		/**
		 * Is Coupon field.
		 *
		 * @since 1.8.2
		 * @deprecated 1.8.4 Deprecated.
		 *
		 * @param {jQuery} element current form element.
		 *
		 * @return {boolean} true/false.
		 */
		isCoupon( element ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.isCoupon( element )" has been deprecated' );
			return element.closest( '.wpforms-field' ).hasClass( 'wpforms-field-payment-coupon' );
		},
		/**
		 * Print error message into date time fields.
		 *
		 * @since 1.6.3
		 *
		 * @param {jQuery} element current form element.
		 * @param {string} error   Error message.
		 */
		dateTimeErrorPlacement( element, error ) {
			const $wrapper = element.closest( '.wpforms-field-row-block, .wpforms-field-date-time' );
			if ( $wrapper.length ) {
				if ( ! $wrapper.find( 'label.wpforms-error, em.wpforms-error' ).length ) {
					$wrapper.append( error );
				}
			} else {
				element.closest( '.wpforms-field' ).append( error );
			}
		},
		/**
		 * Load jQuery Date Picker.
		 *
		 * @since 1.2.3
		 * @since 1.8.9 Added the `$context` parameter.
		 *
		 * @param {jQuery} $context Container to search for datepicker elements.
		 */
		loadDatePicker( $context ) { // eslint-disable-line max-lines-per-function
			// Only load if jQuery datepicker library exists.
			if ( typeof $.fn.flatpickr === 'undefined' ) {
				return;
			}
			$context = $context?.length ? $context : $( document );
			$context.find( '.wpforms-datepicker-wrap' ).each( function() { // eslint-disable-line complexity, max-lines-per-function
				const element = $( this ),
					$input = element.find( 'input' ),
					form = element.closest( '.wpforms-form' ),
					formID = form.data( 'formid' ),
					fieldID = element.closest( '.wpforms-field' ).data( 'field-id' );
				let properties;
				if ( typeof window[ 'wpforms_' + formID + '_' + fieldID ] !== 'undefined' && window[ 'wpforms_' + formID + '_' + fieldID ].hasOwnProperty( 'datepicker' ) ) {
					properties = window[ 'wpforms_' + formID + '_' + fieldID ].datepicker;
				} else if ( typeof window[ 'wpforms_' + formID ] !== 'undefined' && window[ 'wpforms_' + formID ].hasOwnProperty( 'datepicker' ) ) {
					properties = window[ 'wpforms_' + formID ].datepicker;
				} else if ( typeof wpforms_datepicker !== 'undefined' ) {
					properties = wpforms_datepicker;
				} else {
					properties = {
						disableMobile: true,
					};
				}
				// Redefine locale only if user doesn't do that manually, and we have the locale.
				if (
					! properties.hasOwnProperty( 'locale' ) &&
					typeof wpforms_settings !== 'undefined' &&
					wpforms_settings.hasOwnProperty( 'locale' )
				) {
					properties.locale = wpforms_settings.locale;
				}
				properties.wrap = true;
				properties.dateFormat = $input.data( 'date-format' );
				if ( $input.data( 'disable-past-dates' ) === 1 ) {
					properties.minDate = 'today';
					if ( $input.data( 'disable-todays-date' ) === 1 ) {
						const date = new Date();
						properties.minDate = date.setDate( date.getDate() + 1 );
					}
				}
				let limitDays = $input.data( 'limit-days' );
				const weekDays = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
				if ( limitDays && limitDays !== '' ) {
					limitDays = limitDays.split( ',' );
					properties.disable = [ function( date ) {
						let limitDay = null;
						for ( const i in limitDays ) {
							limitDay = weekDays.indexOf( limitDays[ i ] );
							if ( limitDay === date.getDay() ) {
								return false;
							}
						}
						return true;
					} ];
				}
				// Toggle clear date icon.
				properties.onChange = function( selectedDates, dateStr, instance ) { // eslint-disable-line no-unused-vars
					element.find( '.wpforms-datepicker-clear' )
						.css( 'display', dateStr === '' ? 'none' : 'block' );
				};
				element.flatpickr( properties );
			} );
		},
		/**
		 * Load jQuery Time Picker.
		 *
		 * @since 1.2.3
		 * @since 1.8.9 Added the `$context` parameter.
		 *
		 * @param {jQuery} $context Container to search for datepicker elements.
		 */
		loadTimePicker( $context ) {
			// Only load if jQuery timepicker library exists.
			if ( typeof $.fn.timepicker === 'undefined' ) {
				return;
			}
			$context = $context?.length ? $context : $( document );
			$context.find( '.wpforms-timepicker' ).each( function() { // eslint-disable-line complexity
				const element = $( this ),
					form = element.closest( '.wpforms-form' ),
					formID = form.data( 'formid' ),
					fieldID = element.closest( '.wpforms-field' ).data( 'field-id' );
				let properties;
				if (
					typeof window[ 'wpforms_' + formID + '_' + fieldID ] !== 'undefined' &&
					window[ 'wpforms_' + formID + '_' + fieldID ].hasOwnProperty( 'timepicker' )
				) {
					properties = window[ 'wpforms_' + formID + '_' + fieldID ].timepicker;
				} else if (
					typeof window[ 'wpforms_' + formID ] !== 'undefined' &&
					window[ 'wpforms_' + formID ].hasOwnProperty( 'timepicker' )
				) {
					properties = window[ 'wpforms_' + formID ].timepicker;
				} else if ( typeof wpforms_timepicker !== 'undefined' ) {
					properties = wpforms_timepicker;
				} else {
					properties = {
						scrollDefault: 'now',
						forceRoundTime: true,
					};
				}
				// Retrieve the value from the input element.
				const inputValue = element.val();
				element.timepicker( properties );
				// Check if a value is available.
				if ( inputValue ) {
					// Set the input element's value to the retrieved value.
					element.val( inputValue );
					// Trigger the 'changeTime' event to update the timepicker after programmatically setting the value.
					element.trigger( 'changeTime' );
				}
			} );
		},
		/**
		 * Load jQuery input masks.
		 *
		 * @since 1.2.3
		 * @since 1.8.9 Added the `$context` parameter.
		 *
		 * @param {jQuery} $context Container to search for datepicker elements.
		 */
		loadInputMask( $context ) {
			// Only load if jQuery input mask library exists.
			if ( typeof $.fn.inputmask === 'undefined' ) {
				return;
			}
			$context = $context?.length ? $context : $( document );
			// This setting has no effect when switching to the "RTL" mode.
			$context.find( '.wpforms-masked-input' ).inputmask( { rightAlign: false } );
		},
		/**
		 * Fix the Phone field snippets.
		 *
		 * @since 1.8.7.1
		 * @deprecated 1.9.2
		 *
		 * @param {jQuery} $field Phone field element.
		 */
		fixPhoneFieldSnippets( $field ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Obsolete function called. Function wpforms.fixPhoneFieldSnippets( $field ) has been deprecated, please use the wpforms.repairSmartPhoneHiddenField( $field ) function instead!' );
			$field.siblings( 'input[type="hidden"]' ).each( function() {
				if ( ! $( this ).attr( 'name' ).includes( 'function' ) ) {
					return;
				}
				const data = $field.data( 'plugin_intlTelInput' );
				const options = data.d || data.options;
				if ( ! options ) {
					return;
				}
				const insta = window.intlTelInputGlobals.getInstance( $field[ 0 ] );
				insta.destroy();
				options.initialCountry = options.initialCountry.toLowerCase();
				options.onlyCountries = options.onlyCountries.map( ( v ) => v.toLowerCase() );
				options.preferredCountries = options.preferredCountries.map( ( v ) => v.toLowerCase() );
				window.intlTelInput( $field[ 0 ], options );
				$field.siblings( 'input[type="hidden"]' ).each( function() {
					const $hiddenInput = $( this );
					$hiddenInput.attr( 'name', $hiddenInput.attr( 'name' ).replace( 'wpf-temp-', '' ) );
				} );
			} );
		},
		/**
		 * Compatibility fix with an old intl-tel-input library that may include in other addons.
		 * Also, for custom snippets that use `options.hiddenInput` to recieve fieldId.
		 *
		 * @since 1.9.2
		 * @deprecated 1.9.4
		 *
		 * @param {jQuery} $field Phone field element.
		 */
		repairSmartPhoneHiddenField( $field ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.repairSmartPhoneHiddenField()" has been deprecated, please use the new "WPFormsPhoneField.repairSmartHiddenField()" function instead!' );
			WPFormsPhoneField?.repairSmartHiddenField?.( $field );
		},
		/**
		 * Get a list of default smart phone field options.
		 *
		 * @since 1.9.2
		 * @deprecated 1.9.4
		 *
		 * @return {Object} List of default options.
		 */
		getDefaultSmartPhoneFieldOptions() {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.getDefaultSmartPhoneFieldOptions()" has been deprecated, please use the new "WPFormsPhoneField.getDefaultSmartFieldOptions()" function instead!' );
			return WPFormsPhoneField?.getDefaultSmartFieldOptions?.();
		},
		/**
		 * Load Smartphone field.
		 *
		 * @since 1.5.2
		 * @since 1.8.9 Added the `$context` parameter.
		 * @deprecated 1.9.4
		 *
		 * @param {jQuery} $context Context to search for smartphone elements.
		 */
		loadSmartPhoneField( $context ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.loadSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.loadSmartField()" function instead!' );
			WPFormsPhoneField?.loadSmartField?.( $context );
		},
		/**
		 * Backward compatibility jQuery plugin for IntlTelInput library, to support custom snippets.
		 * e.g., https://wpforms.com/developers/how-to-set-a-default-flag-on-smart-phone-field-with-gdpr/.
		 *
		 * @since 1.9.2
		 * @deprecated 1.9.4
		 */
		loadJqueryIntlTelInput() {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.loadJqueryIntlTelInput()" has been deprecated, please use the new "WPFormsPhoneField.loadJqueryIntlTelInput()" function instead!' );
			WPFormsPhoneField?.loadJqueryIntlTelInput?.();
		},
		/**
		 * Init smart phone field.
		 *
		 * @since 1.9.2
		 * @deprecated 1.9.4
		 *
		 * @param {jQuery} $el          Input field.
		 * @param {Object} inputOptions Options for intlTelInput.
		 */
		initSmartPhoneField( $el, inputOptions ) {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.initSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.initSmartField()" function instead!' );
			WPFormsPhoneField?.initSmartField?.( $el, inputOptions );
		},
		/**
		 * Bind Smartphone field event.
		 *
		 * @since 1.8.9
		 * @deprecated 1.9.4
		 */
		bindSmartPhoneField() {
			// eslint-disable-next-line no-console
			console.warn( 'WARNING! Function "wpforms.bindSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.bindSmartField()" function instead!' );
			WPFormsPhoneField?.bindSmartField?.();
		},
		/**
		 * Payments: Do various payment-related tasks on a load.
		 *
		 * @since 1.2.6
		 */
		loadPayments() {
			// Update Total field(s) with the latest calculation.
			$( '.wpforms-payment-total' ).each( function( index, el ) {
				app.amountTotal( this );
			} );
			// Credit card validation.
			if ( typeof $.fn.payment !== 'undefined' ) {
				$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
				$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
			}
		},
		/**
		 * Load mailcheck.
		 *
		 * @since 1.5.3
		 */
		loadMailcheck() { // eslint-disable-line max-lines-per-function
			// Skip loading if `wpforms_mailcheck_enabled` filter return false.
			if ( ! wpforms_settings.mailcheck_enabled ) {
				return;
			}
			// Only load if a library exists.
			if ( typeof $.fn.mailcheck === 'undefined' ) {
				return;
			}
			if ( wpforms_settings.mailcheck_domains.length > 0 ) {
				Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
			}
			if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
				Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
			}
			// Mailcheck suggestion.
			$( document ).on( 'blur', '.wpforms-field-email input', function() {
				const $input = $( this ),
					id = $input.attr( 'id' );
				$input.mailcheck( {
					suggested( $el, suggestion ) {
						// decodeURI() will throw an error if the percent sign is not followed by two hexadecimal digits.
						suggestion.full = suggestion.full.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
						suggestion.address = suggestion.address.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
						suggestion.domain = suggestion.domain.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
						if ( suggestion.address.match( /^xn--/ ) ) {
							suggestion.full = punycode.toUnicode( decodeURI( suggestion.full ) );
							const parts = suggestion.full.split( '@' );
							suggestion.address = parts[ 0 ];
							suggestion.domain = parts[ 1 ];
						}
						if ( suggestion.domain.match( /^xn--/ ) ) {
							suggestion.domain = punycode.toUnicode( decodeURI( suggestion.domain ) );
						}
						const address = decodeURI( suggestion.address ).replaceAll( /[<>'"()/\\|:;=@%&\s]/ig, '' ).substr( 0, 64 ),
							domain = decodeURI( suggestion.domain ).replaceAll( /[<>'"()/\\|:;=@%&+_\s]/ig, '' );
						suggestion = '' + address + '@' + domain + '';
						suggestion = wpforms_settings.val_email_suggestion.replace( '{suggestion}', suggestion );
						$el.closest( '.wpforms-field' ).find( '#' + id + '_suggestion' ).remove();
						$el.parent().append( '' );
					},
					empty() {
						$( '#' + id + '_suggestion' ).remove();
					},
				} );
			} );
			// Apply a Mailcheck suggestion.
			$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
				const $suggestion = $( this ),
					$field = $suggestion.closest( '.wpforms-field' ),
					id = $suggestion.data( 'id' );
				e.preventDefault();
				$field.find( '#' + id ).val( $suggestion.text() );
				$suggestion.parent().remove();
			} );
		},
		/**
		 * Load Choices.js library for all Modern style Dropdown fields (