/**
 * GiraForms - Frontend JavaScript
 */

(function ($) {
	'use strict';

	/**
	 * Form handler
	 */
	class GiraFormsHandler {
		constructor(form) {
			this.form = $(form);
			this.submitButton = this.form.find('button[type="submit"]');
			this.messageContainer = this.form.find('.giraforms-message');
			this.formId = this.form.data('form-id');
			this.enableAjax = this.form.data('ajax') === 'true' || this.form.data('ajax') === true;
			this.redirectUrl = this.form.data('redirect-url');
			this.successMessage = this.form.data('success-message');

			this.init();
		}

		init() {
			// Handle form submission
			this.form.on('submit', (e) => this.handleSubmit(e));

			// Skip blur/input validation for multi-step forms (handled by multi-step.js)
			if (this.form.hasClass('giraforms-multi-step-form')) {
				return;
			}

			// Real-time validation (not for multi-step forms)
			this.form.find('input, textarea, select').on('blur', (e) => {
				this.validateField($(e.target));
			});

			// Clear errors on input (not for multi-step forms)
			this.form.find('input, textarea, select').on('input', (e) => {
				this.clearFieldError($(e.target));
			});

			// Custom interactive fields (calendar, rating, etc.) often store values in hidden inputs.
			this.form.find('.giraforms-field input[type="hidden"][required]').on('change', (e) => {
				this.validateField($(e.target));
			});
		}

		handleSubmit(e) {
			// Validate all fields
			if (!this.validateForm()) {
				e.preventDefault();
				const $firstError = this.form.find('.giraforms-field.has-error').first();
				if ($firstError.length) {
					const $focusTarget = $firstError.find('input, textarea, select, button').not('[type="hidden"]').first();
					if ($focusTarget.length) {
						$focusTarget.trigger('focus');
					} else {
						$firstError.attr('tabindex', '-1').trigger('focus');
					}
				}
				return false;
			}

			// Pre-submit hooks (e.g., Stripe payment processing).
			// Hooks are async functions that receive the form jQuery object.
			// Return false from a hook to block submission.
			if (window.giraFormsPreSubmit && window.giraFormsPreSubmit.length) {
				e.preventDefault();
				this._runPreSubmitHooks();
				return false;
			}

			// No hooks — continue with normal submit flow.
			return this._finalizeSubmit(e);
		}

		async _runPreSubmitHooks() {
			try {
				for (const hook of window.giraFormsPreSubmit) {
					const result = await hook(this.form);
					if (result === false) return;
				}
			} catch (err) {
				return;
			}

			// All hooks passed — continue with submission.
			this._finalizeSubmit(null);
		}

		/**
		 * Finalize submission after validation (and optional pre-submit hooks).
		 * When called from _runPreSubmitHooks, e is null (already prevented).
		 */
		_finalizeSubmit(e) {
			// Check for reCAPTCHA v3 (invisible) - must execute before submit
			const $recaptchaV3 = this.form.find('.giraforms-recaptcha-v3');
			if ($recaptchaV3.length && typeof grecaptcha !== 'undefined') {
				const siteKey = $recaptchaV3.data('sitekey');
				if (siteKey) {
					if (e) e.preventDefault();
					grecaptcha.ready(() => {
						grecaptcha.execute(siteKey, { action: 'submit' }).then((token) => {
							$recaptchaV3.val(token);
							// Inject version marker for server-side detection.
							if (!this.form.find('input[name="giraforms_recaptcha_version"]').length) {
								this.form.append('<input type="hidden" name="giraforms_recaptcha_version" value="v3">');
							}
							if (this.enableAjax) {
								this._doSubmit();
							} else {
								HTMLFormElement.prototype.submit.call(this.form[0]);
							}
						});
					});
					return false;
				}
			}

			// If AJAX is disabled, allow normal form submission
			if (!this.enableAjax) {
				if (e) return true;
				HTMLFormElement.prototype.submit.call(this.form[0]);
				return;
			}

			if (e) e.preventDefault();
			return this._doSubmit();
		}

		_doSubmit() {
			// Disable submit button and show loading text
			const originalText = this.submitButton.text();
			this.submitButton.prop('disabled', true).addClass('loading').text(giraForms.strings.submitting);

			// Get form data
			const formData = new FormData(this.form[0]);
			formData.append('action', 'giraForms_submit');
			formData.append('nonce', giraForms.nonce);
			formData.append('formId', this.formId);
			formData.append('postId', this.form.find('input[name="giraForms_post_id"]').val());
			formData.append('fields', JSON.stringify(this.getFieldsConfig()));
			formData.append('data', JSON.stringify(this.getFormData()));

			// Submit via AJAX
			$.ajax({
				url: giraForms.ajaxUrl,
				type: 'POST',
				data: formData,
				processData: false,
				contentType: false,
				success: (response) => this.handleSuccess(response),
				error: (xhr) => this.handleError(xhr),
				complete: () => {
					this.submitButton.prop('disabled', false).removeClass('loading').text(originalText);
				},
			});

			return false;
		}

		validateForm() {
			let isValid = true;
			const validatedGroups = new Set();
			const fields = this.form
				.find('input, textarea, select')
				.not('[type="hidden"]')
				.add(this.form.find('.giraforms-field input[type="hidden"][required]'));

			fields.each((index, field) => {
				const $f = $(field);
				const name = $f.attr('name');

				// For checkbox/radio groups (name ends with []), validate only once per group.
				if (name && name.endsWith('[]')) {
					if (validatedGroups.has(name)) {
						return;
					}
					validatedGroups.add(name);
				}

				if (!this.validateField($f)) {
					isValid = false;
				}
			});

			return isValid;
		}

		validateField($field) {
			const fieldName = $field.attr('name');
			const fieldType = $field.attr('type') || $field.prop('tagName').toLowerCase();
			const value = $field.val();

			// Checkbox group: required is determined by the wrapper's label, not by the input itself.
			if (fieldType === 'checkbox' && fieldName && fieldName.endsWith('[]')) {
				const $wrapper = $field.closest('.giraforms-field');
				const isRequired = $wrapper.find('> label').hasClass('required');
				this.clearFieldError($field);
				if (isRequired) {
					const checked = $wrapper.find('input[type="checkbox"]:checked').length > 0;
					if (!checked) {
						const customMsg = $field.data('required-message');
						this.showFieldError($field, customMsg || giraForms.strings.requiredField);
						return false;
					}
				}
				return true;
			}

			const isRequired = $field.prop('required');
			const isEmpty = Array.isArray(value) ? value.length === 0 : !value;

			// Clear previous error
			this.clearFieldError($field);

			// Required validation
			if (isRequired && isEmpty) {
				const customMsg = $field.data('required-message');
				this.showFieldError($field, customMsg || giraForms.strings.requiredField);
				return false;
			}

			// Type-specific validation
			if (!isEmpty) {
				switch (fieldType) {
					case 'email':
						if (!this.isValidEmail(value)) {
							this.showFieldError($field, giraForms.strings.invalidEmail);
							return false;
						}
						break;

					case 'tel':
						if (!this.isValidPhone(value)) {
							this.showFieldError($field, giraForms.strings.invalidPhone);
							return false;
						}
						break;
				}
			}

			return true;
		}

		isValidEmail(email) {
			// Mirrors WordPress is_email(): min 6 chars, local part allows RFC special chars,
			// domain labels are alphanumeric + hyphens only, no consecutive dots, min 2 labels.
			if (email.length < 6) return false;
			const re = /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.\-]+@[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)+$/;
			return re.test(email);
		}

		isValidPhone(phone) {
			const cleaned = phone.replace(/[\s\-\(\)\.]/g, '');
			const re = /^\+?[0-9]{7,15}$/;
			return re.test(cleaned);
		}

		showFieldError($field, message) {
			const $wrapper = $field.closest('.giraforms-field');
			$wrapper.addClass('has-error');

			if (!$wrapper.find('.giraforms-field-error').length) {
				const fieldName = ($field.attr('name') || $field.attr('id') || Math.random().toString(36).substring(2, 9)).replace(/[^a-zA-Z0-9]/g, '_');
				const errorId = 'giraforms-error-' + fieldName;
				$wrapper.append(
					'<div class="giraforms-field-error" id="' + errorId + '" role="alert">' + message + '</div>'
				);
				$field.attr('aria-describedby', errorId).attr('aria-invalid', 'true');
			}
		}

		clearFieldError($field) {
			const $wrapper = $field.closest('.giraforms-field');
			$wrapper.removeClass('has-error');
			$wrapper.find('.giraforms-field-error').remove();
			$field.removeAttr('aria-describedby').removeAttr('aria-invalid');
		}

		getFieldsConfig() {
			const fields = [];
			const processedFields = new Set();

			// Iterate over field wrappers to avoid duplicates for checkbox/radio groups
			this.form.find('.giraforms-field').each((index, wrapper) => {
				const $wrapper = $(wrapper);

				// Find the first input/select/textarea in this wrapper
				const $field = $wrapper.find('input, textarea, select').not('[type="submit"]').first();
				if (!$field.length) {
					return;
				}

				// Get the field name (remove [] for checkbox arrays)
				let fieldName = $field.attr('name');
				if (!fieldName) {
					return;
				}

				// Remove [] suffix for checkbox/multi-select arrays
				const cleanFieldName = fieldName.replace(/\[\]$/, '');

				// Skip if already processed (avoid duplicates)
				if (processedFields.has(cleanFieldName)) {
					return;
				}
				processedFields.add(cleanFieldName);

				// Get label: check multiple possible locations
				let label = '';
				// 1. Direct child label (most common)
				let $firstLabel = $wrapper.find('> label').first();
				// 2. Label inside range-header (range-slider-field)
				if (!$firstLabel.length) {
					$firstLabel = $wrapper.find('.giraforms-range-header > label').first();
				}
				// 3. Label inside checkbox-wrapper (consent-field, checkbox-field)
				if (!$firstLabel.length) {
					$firstLabel = $wrapper.find('.giraforms-checkbox-wrapper > label').first();
				}
				if ($firstLabel.length) {
					// Clone to avoid modifying the DOM, then remove nested elements like required spans
					const $labelClone = $firstLabel.clone();
					$labelClone.find('.required, span').remove();
					label = $labelClone.text().trim();
				}
				// 4. Fallback to data-label attribute (hidden fields)
				if (!label) {
					label = $field.attr('data-label') || $wrapper.attr('data-label') || '';
				}

				// Get field type
				let fieldType = $field.attr('type');
				if (!fieldType) {
					fieldType = $field.prop('tagName').toLowerCase();
					if (fieldType === 'select') {
						fieldType = 'select';
					} else if (fieldType === 'textarea') {
						fieldType = 'textarea';
					}
				}

				fields.push({
					name: cleanFieldName,
					type: fieldType,
					label: label,
					required: $field.prop('required'),
				});
			});

			return fields;
		}

		getFormData() {
			const data = {};
			const formData = new FormData(this.form[0]);

			for (let [key, value] of formData.entries()) {
				// Skip internal fields
				if (key.indexOf('giraForms_') === 0 || key === 'action' || key === 'nonce') {
					continue;
				}

				// Normalize key: strip [] suffix (multi-select, checkboxes)
				const normalizedKey = key.replace(/\[\]$/, '');

				// Accumulate multiple values as array
				if (data[normalizedKey] !== undefined) {
					if (!Array.isArray(data[normalizedKey])) {
						data[normalizedKey] = [data[normalizedKey]];
					}
					data[normalizedKey].push(value);
				} else {
					data[normalizedKey] = value;
				}
			}

			return data;
		}

		handleSuccess(response) {
			if (response.success) {
				// Check for redirect URL
				if (this.redirectUrl && this.redirectUrl !== '') {
					window.location.href = this.redirectUrl;
					return;
				}

				// Show success message
				const message = this.successMessage || giraForms.strings.success;
				this.showMessage(message, 'success');
				this.form[0].reset();
				this.resetCaptchas();

				// Scroll to message
				$('html, body').animate({
					scrollTop: this.messageContainer.offset().top - 100,
				}, 500);

				// Trigger custom event
				$(document).trigger('giraFormsSubmitSuccess', [response.data]);
			} else {
				const errorMessage = (response.data && response.data.message) ? response.data.message : giraForms.strings.error;
				this.showMessage(errorMessage, 'error');

				// Show field-specific errors
				if (response.data && response.data.errors) {
					this.showFieldErrors(response.data.errors);
				}
			}
		}

		handleError(xhr) {
			// Log detailed error to console for debugging
			console.error('GiraForms submission error:', xhr);

			let errorMessage = giraForms.strings.error;
			const responseData = (xhr.responseJSON && xhr.responseJSON.data) ? xhr.responseJSON.data : null;

			if (responseData && responseData.message) {
				errorMessage = responseData.message;
			} else if (xhr.responseText) {
				try {
					const response = JSON.parse(xhr.responseText);
					if (response.data && response.data.message) {
						errorMessage = response.data.message;
					}
				} catch (e) {
					// JSON parse failed, use generic error
				}
			}

			// Show field-specific errors (e.g. server-side validation failures)
			if (responseData && responseData.errors) {
				this.showFieldErrors(responseData.errors);
				const $firstError = this.form.find('.giraforms-field.has-error').first();
				if ($firstError.length) {
					const $focusTarget = $firstError.find('input, textarea, select, button').not('[type="hidden"]').first();
					if ($focusTarget.length) {
						$focusTarget.trigger('focus');
					} else {
						$firstError.attr('tabindex', '-1').trigger('focus');
					}
				}
			}

			this.showMessage(errorMessage, 'error');
		}

		resetCaptchas() {
			// Reset Turnstile
			if (typeof turnstile !== 'undefined') {
				this.form.find('.cf-turnstile').each(function () {
					turnstile.reset(this);
				});
			}
			// Reset reCAPTCHA v2
			if (typeof grecaptcha !== 'undefined' && this.form.find('.g-recaptcha').length) {
				try { grecaptcha.reset(); } catch (ex) { /* ignore */ }
			}
			// Reset hCaptcha
			if (typeof hcaptcha !== 'undefined') {
				this.form.find('.h-captcha').each(function () {
					try { hcaptcha.reset(); } catch (ex) { /* ignore */ }
				});
			}
		}

		showMessage(message, type) {
			this.messageContainer
				.html(message)
				.removeClass('success error')
				.addClass(type)
				.show();
		}

		showFieldErrors(errors) {
			$.each(errors, (fieldName, message) => {
				// Try exact name first, then with [] suffix (multi-select, checkboxes)
				let $field = this.form.find('[name="' + fieldName + '"]');
				if (!$field.length) {
					$field = this.form.find('[name="' + fieldName + '[]"]');
				}
				if ($field.length) {
					this.showFieldError($field.first(), message);
				}
			});
		}
	}

	

	

	/**
	 * Initialize on document ready
	 */
	$(document).ready(function () {
		// Populate nonce fields
		if (typeof giraForms !== 'undefined' && giraForms.nonce) {
			$('.giraforms-nonce-field').val(giraForms.nonce);
		}

		// Populate dynamic fields (post_id, timestamp)
		$('.giraforms-form').each(function () {
			const $form = $(this);

			// Set timestamp
			const $timestamp = $form.find('input[name="giraforms_timestamp"]');
			if ($timestamp.length && !$timestamp.val()) {
				$timestamp.val(Math.floor(Date.now() / 1000));
			}

			// Set post_id from giraForms data if available
			const $postId = $form.find('input[name="giraForms_post_id"]');
			if ($postId.length && !$postId.val() && typeof giraForms !== 'undefined' && giraForms.postId) {
				$postId.val(giraForms.postId);
			}
		});

		// Initialize form handlers
		$('.giraforms-form').each(function () {
			new GiraFormsHandler(this);
		});

		// Display messages from non-AJAX submissions (stored in transients)
		if (typeof giraForms !== 'undefined' && giraForms.formMessages) {
			$.each(giraForms.formMessages, function (formId, data) {
				const $form = $('.giraforms-form[data-form-id="' + formId + '"]');
				if ($form.length) {
					const $messageContainer = $form.find('.giraforms-message');
					if ($messageContainer.length) {
						$messageContainer
							.html(data.message)
							.removeClass('success error')
							.addClass(data.type)
							.show();

						// Scroll to message
						$('html, body').animate({
							scrollTop: $messageContainer.offset().top - 100,
						}, 500);
					}
				}
			});
		}

		

		

		// Initialize password toggle
		$('.giraforms-password-toggle').on('click', function() {
			const $button = $(this);
			const $input = $button.siblings('input');
			const $icon = $button.find('.dashicons');

			if ($input.attr('type') === 'password') {
				$input.attr('type', 'text');
				$icon.removeClass('dashicons-visibility').addClass('dashicons-hidden');
			} else {
				$input.attr('type', 'password');
				$icon.removeClass('dashicons-hidden').addClass('dashicons-visibility');
			}
		});

		// Initialize color picker sync
		$('.giraforms-color-input').on('input change', function() {
			const $colorInput = $(this);
			const color = $colorInput.val();
			const $hexInput = $colorInput.closest('.giraforms-color-picker-wrapper').find('.giraforms-color-hex-input');
			const $preview = $colorInput.closest('.giraforms-color-preview');

			if ($hexInput.length) {
				$hexInput.val(color);
			}
			$preview.css('background-color', color);
		});

		$('.giraforms-color-hex-input').on('input', function() {
			const $hexInput = $(this);
			const color = $hexInput.val();
			const $colorInput = $hexInput.closest('.giraforms-color-picker-wrapper').find('.giraforms-color-input');
			const $preview = $colorInput.closest('.giraforms-color-preview');

			// Validate hex color
			if (/^#[0-9A-F]{6}$/i.test(color)) {
				$colorInput.val(color);
				$preview.css('background-color', color);
			}
		});

		// Initialize range slider value update
		$('.giraforms-range-input').on('input', function() {
			const $rangeInput = $(this);
			const value = $rangeInput.val();
			const fieldName = $rangeInput.attr('name');
			const $valueDisplay = $('.giraforms-range-value[data-field="' + fieldName + '"] .giraforms-range-value-number');

			if ($valueDisplay.length) {
				$valueDisplay.text(value);
			}
		});

		

		

		

		
	});
})(jQuery);
