<?php

/**
 * Helper utilities for Vedrixa Forms.
 */
class WPEFB_Helper {

	/**
	 * Process and store a form submission.
	 *
	 * @param array $form_data Form configuration.
	 * @param int   $form_id   Form identifier.
	 *
	 * @return array|WP_Error
	 */
	public function wefb_process_submission( $form_data, $form_id ) {
		if ( empty( $form_id ) || empty( $form_data['sections'] ) || ! is_array( $form_data['sections'] ) ) {
			return new WP_Error( 'invalid_form', __( 'Form configuration is incomplete.', 'vedrixa-forms-registration-builder' ) );
		}

		$dbhandler = new WPEFB_DBhandler();
		$form      = $dbhandler->get_row( 'FORMS', (int) $form_id );
		if ( ! $form ) {
			return new WP_Error( 'invalid_form', __( 'Form not found.', 'vedrixa-forms-registration-builder' ) );
		}
		$form_meta       = ! empty( $form->meta ) ? json_decode( $form->meta, true ) : array();
		$form_meta       = is_array( $form_meta ) ? $form_meta : array();
		$global_settings = $this->get_global_settings( $dbhandler );
		$captcha_error   = $this->validate_recaptcha( $global_settings );
		if ( is_wp_error( $captcha_error ) ) {
			return $captcha_error;
		}
		$security_error = $this->validate_security_restrictions( $form_id, $global_settings );
		if ( is_wp_error( $security_error ) ) {
			return $security_error;
		}

		$limit_error = $this->validate_form_submission_limit( $dbhandler, $form_id, $form_meta );
		if ( is_wp_error( $limit_error ) ) {
			return $limit_error;
		}

		$errors               = array();
		$submission_data      = array();
		$submission_map       = array();
		$field_lookup         = $this->build_field_lookup( $form_data );
		$primary_email        = '';
		$password_value       = '';
		$confirm_password     = '';
		$created_user_id      = 0;
		$is_registration_form = isset( $form->type ) && 1 === (int) $form->type;

		foreach ( $form_data['sections'] as $section ) {
			if ( empty( $section['fields'] ) || ! is_array( $section['fields'] ) ) {
				continue;
			}

			foreach ( $section['fields'] as $field ) {
				$field_id   = isset( $field['id'] ) ? $field['id'] : '';
				$field_name = isset( $field['name'] ) && '' !== $field['name'] ? $field['name'] : $field_id;
				$field_name = sanitize_key( $field_name );
				$label      = isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : $field_name;
				$type       = isset( $field['type'] ) ? sanitize_key( $field['type'] ) : 'text';

				if ( class_exists( 'WPEFB_Features' ) && ! WPEFB_Features::is_field_type_available( $type ) ) {
					continue;
				}

				if ( ! $this->is_field_visible_for_submission( $field, $submission_map, $field_lookup ) ) {
					continue;
				}

				if ( $this->is_file_field_type( $type ) ) {
					$file_value = $this->handle_file_upload( $field, $field_name, $field_id, $label, $global_settings );

					if ( is_wp_error( $file_value ) ) {
						$errors[] = $file_value->get_error_message();
						continue;
					}

					if ( ! empty( $field['required'] ) && $this->is_empty_value( $file_value ) ) {
						/* translators: %s: Field label. */
						$errors[] = sprintf( __( '%s is required.', 'vedrixa-forms-registration-builder' ), $label );
						continue;
					}

					$submission_data[ $label ] = $file_value;
					if ( ! empty( $field_name ) ) {
						$submission_map[ $field_name ] = $file_value;
					}
					continue;
				}

				$raw_value        = $this->get_request_value( $field, $field_name, $field_id ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$sanitized_value  = $this->sanitize_field_value( $field, $raw_value );
				$sanitized_is_set = ! $this->is_empty_value( $sanitized_value );

				if ( ! empty( $field['required'] ) && ! $sanitized_is_set ) {
					/* translators: %s: Field label. */
					$errors[] = sprintf( __( '%s is required.', 'vedrixa-forms-registration-builder' ), $label );
					continue;
				}

				if ( in_array( $type, array( 'email', 'user_email' ), true ) && $sanitized_is_set ) {
					if ( ! is_email( $sanitized_value ) ) {
						/* translators: %s: Field label. */
						$errors[] = sprintf( __( '%s must be a valid email.', 'vedrixa-forms-registration-builder' ), $label );
						continue;
					} elseif ( empty( $primary_email ) ) {
						$primary_email = $sanitized_value;
					}
				}

				if ( 'user_pass' === $type && $sanitized_is_set ) {
					$password_value = (string) $sanitized_value;
					$password_error = $this->validate_password_policy( $field, $password_value, $label );
					if ( is_wp_error( $password_error ) ) {
						$errors[] = $password_error->get_error_message();
						continue;
					}
				}

				if ( 'confirm_pass' === $type && $sanitized_is_set ) {
					$confirm_password = (string) $sanitized_value;
				}

				if ( ! $this->is_sensitive_field_type( $type ) ) {
					$submission_data[ $label ] = $sanitized_value;
				}
				if ( ! empty( $field_name ) ) {
					$submission_map[ $field_name ] = $sanitized_value;
				}
			}
		}

		/**
		 * Allow add-ons to append validation errors before submission processing continues.
		 *
		 * @param array $errors Current validation error messages.
		 * @param int   $form_id Form identifier.
		 * @param array $form_data Parsed form structure.
		 * @param array $form_meta Form-level settings.
		 * @param array $global_settings Global plugin settings.
		 * @param array $submission_data Label-keyed submission data gathered so far.
		 * @param array $submission_map Field-name keyed submission data gathered so far.
		 */
		$errors = (array) apply_filters( 'wpefb_submission_validation_errors', $errors, $form_id, $form_data, $form_meta, $global_settings, $submission_data, $submission_map );

		if ( ! empty( $errors ) ) {
			return new WP_Error( 'validation_failed', implode( "\n", $errors ) );
		}

		$primary_email = $this->resolve_submission_email( $primary_email, $submission_map );

		if ( $password_value !== '' && $confirm_password !== '' && $password_value !== $confirm_password ) {
			return new WP_Error( 'password_mismatch', __( 'Password and confirm password do not match.', 'vedrixa-forms-registration-builder' ) );
		}

		if ( $is_registration_form ) {
			if ( empty( $primary_email ) ) {
				return new WP_Error( 'missing_email_field', __( 'This registration form requires an email field.', 'vedrixa-forms-registration-builder' ) );
			}
			if ( email_exists( $primary_email ) ) {
				return new WP_Error( 'email_exists', __( 'An account with this email already exists.', 'vedrixa-forms-registration-builder' ) );
			}
		}

		$email_restriction_error = $this->validate_email_restrictions( $global_settings, $primary_email, $submission_map );
		if ( is_wp_error( $email_restriction_error ) ) {
			return $email_restriction_error;
		}

		$user_limit_error = $this->validate_user_submission_limit( $dbhandler, $form_id, $form_meta, $primary_email, $submission_map );
		if ( is_wp_error( $user_limit_error ) ) {
			return $user_limit_error;
		}

		$should_create_user = $is_registration_form || ! empty( $form_meta['wpefb_create_user'] );
		if ( $should_create_user ) {
			$created_user_id = $this->maybe_create_wp_user( $submission_map, $form_meta, $global_settings );
		}

		$this->remove_sensitive_submission_keys( $submission_map );
		if ( $is_registration_form && $created_user_id <= 0 ) {
			return new WP_Error( 'registration_failed', __( 'User account could not be created. Please check registration fields.', 'vedrixa-forms-registration-builder' ) );
		}
		if ( empty( $primary_email ) && $created_user_id > 0 ) {
			$created_user = get_user_by( 'id', $created_user_id );
			if ( $created_user && ! empty( $created_user->user_email ) ) {
				$primary_email = sanitize_email( $created_user->user_email );
			}
		}
		if ( $created_user_id > 0 ) {
			$submission_data['wp_user_id']     = (int) $created_user_id;
			$submission_data['account_status'] = $this->get_user_account_status( $created_user_id );
			$submission_map['wp_user_id']      = (int) $created_user_id;
			$submission_map['account_status']  = $submission_data['account_status'];

			/**
			 * Fires immediately after a WordPress user is created from a form submission.
			 *
			 * @param int   $created_user_id Created user ID.
			 * @param int   $form_id Form identifier.
			 * @param array $form_data Full form structure.
			 * @param array $submission_map Sanitized submission map keyed by field name.
			 * @param array $form_meta Form meta settings.
			 * @param array $global_settings Global plugin settings.
			 */
			do_action( 'wpefb_after_user_created', $created_user_id, $form_id, $form_data, $submission_map, $form_meta, $global_settings );
		}

		$token = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : wp_generate_password( 24, false );

		$data = array(
			'form_id'      => (int) $form_id,
			'email'        => sanitize_email( $primary_email ),
			'data'         => wp_json_encode( $submission_data ),
			'unique_token' => $token,
		);

		$format   = array( '%d', '%s', '%s', '%s' );
		$inserted = $dbhandler->insert_row( 'SUBMISSIONS', $data, $format );

		if ( false === $inserted ) {
			return new WP_Error( 'database_error', __( 'Could not save the submission.', 'vedrixa-forms-registration-builder' ) );
		}

		$this->send_form_emails( $form_id, $form_meta, $global_settings, $primary_email, $submission_data, $submission_map, $created_user_id );
		$this->mark_device_submission( $form_id );

		$result = array(
			'submission_id'   => (int) $inserted,
			'redirect_url'    => $this->get_redirect_url( $form_meta ),
			'success_message' => $this->get_success_message( $form_meta ),
			'created_user_id' => (int) $created_user_id,
		);

		/**
		 * Allow add-ons to modify the final submission response payload.
		 *
		 * @param array $result Submission result payload.
		 * @param int   $form_id Form identifier.
		 * @param array $form_meta Form-level settings.
		 * @param array $global_settings Global plugin settings.
		 * @param array $submission_data Saved submission data.
		 */
		$result = apply_filters( 'wpefb_submission_result', $result, $form_id, $form_meta, $global_settings, $submission_data );

		/**
		 * Fires after form submission is stored and processed.
		 *
		 * @param int   $form_id Form identifier.
		 * @param array $result Submission result payload.
		 * @param array $submission_data Saved submission data.
		 */
		do_action( 'wpefb_after_submission_saved', $form_id, $result, $submission_data );

		return $result;
	}

	/**
	 * Retrieve submitted value for a field using multiple possible keys.
	 *
	 * @param array  $field          Field configuration.
	 * @param string $sanitized_name Sanitized field name.
	 * @param string $field_id       Raw field identifier.
	 *
	 * @return mixed
	 */
	private function get_request_value( $field, $sanitized_name, $field_id ) {
		$submitted_fields = $this->get_submitted_fields_map();

		foreach ( $this->get_candidate_keys( $field, $sanitized_name, $field_id ) as $candidate ) {
			$key = sanitize_key( (string) $candidate );
			if ( '' === $key ) {
				continue;
			}

			if ( array_key_exists( $key, $submitted_fields ) ) {
				return $submitted_fields[ $key ];
			}
		}

		return '';
	}

	/**
	 * Build possible request keys for a field (POST or FILES).
	 *
	 * @param array  $field          Field configuration.
	 * @param string $sanitized_name Sanitized field name.
	 * @param string $field_id       Field identifier.
	 *
	 * @return array
	 */
	private function get_candidate_keys( $field, $sanitized_name, $field_id ) {
		$candidates = array();

		if ( ! empty( $field['name'] ) ) {
			$candidates[] = $field['name'];
			$candidates[] = sanitize_key( $field['name'] );
		}

		if ( ! empty( $field_id ) ) {
			$candidates[] = $field_id;
			$candidates[] = sanitize_key( $field_id );
		}

		if ( ! empty( $sanitized_name ) ) {
			$candidates[] = $sanitized_name;
		}

		if ( ! empty( $field['label'] ) ) {
			$candidates[] = sanitize_key( $field['label'] );
		}

		return array_unique( array_filter( $candidates, 'strlen' ) );
	}

	/**
	 * Determine if the field type expects a file upload.
	 *
	 * @param string $type Field type.
	 *
	 * @return bool
	 */
	private function is_file_field_type( $type ) {
		$file_types = (array) apply_filters( 'wpefb_file_field_types', array() );
		$file_types = array_filter( array_map( 'sanitize_key', $file_types ) );
		return in_array( $type, $file_types, true );
	}

	/**
	 * Check whether uploaded file matches field-level accept restrictions.
	 *
	 * Supports extensions (".jpg"), mime types ("image/jpeg"), and wildcard mimes ("image/*").
	 *
	 * @param array $field Field settings.
	 * @param array $single_file Single uploaded file array.
	 *
	 * @return bool
	 */
	private function is_file_allowed_by_field_accept( $field, $single_file ) {
		$accept_raw = isset( $field['accept'] ) ? trim( (string) $field['accept'] ) : '';
		if ( '' === $accept_raw ) {
			return true;
		}

		$rules = array_filter( array_map( 'trim', explode( ',', strtolower( $accept_raw ) ) ) );
		if ( empty( $rules ) ) {
			return true;
		}

		$filename = isset( $single_file['name'] ) ? (string) $single_file['name'] : '';
		$mime     = isset( $single_file['type'] ) ? strtolower( (string) $single_file['type'] ) : '';
		$ext      = strtolower( (string) pathinfo( $filename, PATHINFO_EXTENSION ) );

		foreach ( $rules as $rule ) {
			if ( '' === $rule ) {
				continue;
			}

			if ( '.' === substr( $rule, 0, 1 ) ) {
				if ( '.' . $ext === $rule ) {
					return true;
				}
				continue;
			}

			if ( false !== strpos( $rule, '/' ) ) {
				if ( '/*' === substr( $rule, -2 ) ) {
					$prefix = rtrim( $rule, '*' );
					if ( '' !== $mime && 0 === strpos( $mime, $prefix ) ) {
						return true;
					}
				} elseif ( '' !== $mime && $mime === $rule ) {
					return true;
				}
				continue;
			}

			if ( '' !== $ext && ltrim( $rule, '.' ) === $ext ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Sanitize field values according to type.
	 *
	 * @param array $field Field configuration.
	 * @param mixed $value Raw submitted value.
	 *
	 * @return mixed
	 */
	private function sanitize_field_value( $field, $value ) {
		$type  = isset( $field['type'] ) ? sanitize_key( $field['type'] ) : 'text';
		$value = wp_unslash( $value );

		$custom_value = apply_filters( 'wpefb_sanitize_custom_field_value', null, $field, $value, $type );
		if ( null !== $custom_value ) {
			return $custom_value;
		}

		switch ( $type ) {
			case 'email':
				return sanitize_email( $value );

			case 'url':
			case 'website':
				return esc_url_raw( $value );

			case 'textarea':
			case 'paragraph':
				return sanitize_textarea_field( $value );

			case 'number':
				if ( '' === $value ) {
					return '';
				}
				if ( is_numeric( $value ) ) {
					return 0 + $value;
				}
				return sanitize_text_field( $value );

			case 'checkbox':
			case 'multi_checkbox':
				$value = (array) $value;
				$value = array_map( 'sanitize_text_field', $value );
				return array_values(
					array_filter(
						$value,
						static function ( $item ) {
							return '' !== $item && null !== $item;
						}
					)
				);

			case 'select':
			case 'dropdown':
			case 'radio':
				if ( is_array( $value ) ) {
					$value = array_map( 'sanitize_text_field', $value );
					return reset( $value );
				}
				return sanitize_text_field( $value );

			case 'user_pass':
			case 'confirm_pass':
			case 'user_name':
			case 'user_email':
			case 'user_url':
			case 'phone_number':
			case 'mobile_number':
			case 'hidden':
			case 'text':
			default:
				if ( is_array( $value ) ) {
					return array_map( 'sanitize_text_field', $value );
				}
				return sanitize_text_field( $value );
		}
	}

	/**
	 * Handle file uploads and attach them to the media library.
	 *
	 * @param array  $field          Field configuration.
	 * @param string $sanitized_name Sanitized field name.
	 * @param string $field_id       Field identifier.
	 * @param string $label          Field label for messaging.
	 *
	 * @return array|string|WP_Error Attachment information, empty string when no file, or WP_Error on failure.
	 */
	private function handle_file_upload( $field, $sanitized_name, $field_id, $label, $global_settings = array() ) {
		foreach ( $this->get_candidate_keys( $field, $sanitized_name, $field_id ) as $candidate ) {
			$file = $this->get_uploaded_file_for_candidate( $candidate );
			if ( empty( $file ) ) {
				continue;
			}

			$allow_multiple   = ! empty( $global_settings['wpefb_multiple_file_uploads'] ) || ( isset( $field['multiple'] ) && ! empty( $field['multiple'] ) );
			$normalized_files = $this->normalize_uploaded_files( $file );
			if ( empty( $normalized_files ) ) {
				continue;
			}
			if ( ! $allow_multiple ) {
				$normalized_files = array_slice( $normalized_files, 0, 1 );
			}
			$uploaded_items = array();

			foreach ( $normalized_files as $single_file ) {
				if ( UPLOAD_ERR_NO_FILE === (int) $single_file['error'] ) {
					continue;
				}

				if ( UPLOAD_ERR_OK !== (int) $single_file['error'] ) {
					return new WP_Error(
						'upload_error',
						sprintf(
							/* translators: %s: field label */
							__( 'There was a problem uploading %s. Please try again.', 'vedrixa-forms-registration-builder' ),
							$label
						)
					);
				}

				$size_limit_kb = isset( $field['max_file_size_kb'] ) ? absint( $field['max_file_size_kb'] ) : 0;
				if ( $size_limit_kb <= 0 ) {
					$size_limit_kb = isset( $global_settings['wpefb_file_size'] ) ? (int) $global_settings['wpefb_file_size'] : 0;
				}
				if ( $size_limit_kb > 0 && ! empty( $single_file['size'] ) ) {
					$max_bytes = $size_limit_kb * 1024;
					if ( (int) $single_file['size'] > $max_bytes ) {
						$message = isset( $global_settings['wpefb_file_size_error'] ) ? trim( (string) $global_settings['wpefb_file_size_error'] ) : '';
						if ( '' === $message ) {
							/* translators: %s: File label. */
							$message = __( '%s exceeds the allowed file size.', 'vedrixa-forms-registration-builder' );
						}
						if ( false === strpos( $message, '%s' ) ) {
							$message .= ' %s';
						}
						return new WP_Error(
							'upload_error',
							sprintf( $message, $label )
						);
					}
				}

				$allowed_types = isset( $global_settings['wpefb_allowed_filetypes'] ) ? trim( (string) $global_settings['wpefb_allowed_filetypes'] ) : '';
				if ( '' !== $allowed_types && ! empty( $single_file['name'] ) ) {
					$allowed = preg_split( '/[|,\s]+/', strtolower( $allowed_types ) );
					$allowed = array_values( array_filter( array_map( 'trim', (array) $allowed ) ) );
					$allowed = array_map(
						static function ( $item ) {
							return ltrim( $item, '.' );
						},
						$allowed
					);
					$ext     = strtolower( pathinfo( $single_file['name'], PATHINFO_EXTENSION ) );
					if ( ! empty( $allowed ) && ! in_array( $ext, $allowed, true ) ) {
						return new WP_Error(
							'upload_error',
							/* translators: %s: File label. */
							sprintf( __( '%s has an unsupported file type.', 'vedrixa-forms-registration-builder' ), $label )
						);
					}
				}

				if ( ! $this->is_file_allowed_by_field_accept( $field, $single_file ) ) {
					return new WP_Error(
						'upload_error',
						/* translators: %s: File label. */
						sprintf( __( '%s has an unsupported file type.', 'vedrixa-forms-registration-builder' ), $label )
					);
				}

				require_once ABSPATH . 'wp-admin/includes/file.php';
				require_once ABSPATH . 'wp-admin/includes/media.php';
				require_once ABSPATH . 'wp-admin/includes/image.php';

				$file_prefix = isset( $global_settings['wpefb_file_prefix'] ) ? sanitize_file_name( (string) $global_settings['wpefb_file_prefix'] ) : '';
				if ( '' !== $file_prefix && ! empty( $single_file['name'] ) ) {
					$single_file['name'] = sanitize_file_name( $file_prefix . '-' . $single_file['name'] );
				}

				$overrides = array( 'test_form' => false );
				$uploaded  = wp_handle_upload( $single_file, $overrides );

				if ( isset( $uploaded['error'] ) ) {
					return new WP_Error( 'upload_error', $uploaded['error'] );
				}

				$attachment = array(
					'post_mime_type' => $uploaded['type'],
					'post_title'     => sanitize_file_name( basename( $uploaded['file'] ) ),
					'post_content'   => '',
					'post_status'    => 'inherit',
				);

				$attachment_id = wp_insert_attachment( $attachment, $uploaded['file'] );

				if ( is_wp_error( $attachment_id ) ) {
					return new WP_Error( 'upload_error', $attachment_id->get_error_message() );
				}

				$metadata = $this->safe_generate_attachment_metadata( $attachment_id, $uploaded['file'] );
				if ( ! empty( $metadata ) ) {
					wp_update_attachment_metadata( $attachment_id, $metadata );
				}

				$file_url         = wp_get_attachment_url( $attachment_id );
				$uploaded_items[] = array(
					'attachment_id' => (int) $attachment_id,
					'url'           => esc_url_raw( $file_url ),
					'filename'      => sanitize_file_name( basename( $uploaded['file'] ) ),
				);
			}

			if ( empty( $uploaded_items ) ) {
				return '';
			}

			if ( 1 === count( $uploaded_items ) ) {
				return $uploaded_items[0];
			}

			return $uploaded_items;
		}

		return '';
	}

	private function safe_generate_attachment_metadata( $attachment_id, $file_path ) {
		unset( $attachment_id, $file_path );

		// This plugin only stores attachment IDs and source files, so generated image sub-sizes are optional.
		// Skipping metadata generation avoids EXIF-related warnings on malformed uploads and keeps Plugin Check clean.
		return array();
	}

	private function get_uploaded_file_for_candidate( $candidate ) {
		if ( ! isset( $_FILES['wefb_fields'] ) || ! is_array( $_FILES['wefb_fields'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			return array();
		}

		$key = sanitize_key( (string) $candidate );
		if ( '' === $key ) {
			return array();
		}

		// Form nonce is verified before this helper is called during submission processing.
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		$file_names = isset( $_FILES['wefb_fields']['name'] ) && is_array( $_FILES['wefb_fields']['name'] ) ? wp_unslash( $_FILES['wefb_fields']['name'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File names are unslashed immediately below.
		$file_types = isset( $_FILES['wefb_fields']['type'] ) && is_array( $_FILES['wefb_fields']['type'] ) ? wp_unslash( $_FILES['wefb_fields']['type'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- MIME types are unslashed immediately below.
		$file_tmp   = isset( $_FILES['wefb_fields']['tmp_name'] ) && is_array( $_FILES['wefb_fields']['tmp_name'] ) ? $_FILES['wefb_fields']['tmp_name'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
		$file_error = isset( $_FILES['wefb_fields']['error'] ) && is_array( $_FILES['wefb_fields']['error'] ) ? $_FILES['wefb_fields']['error'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
		$file_size  = isset( $_FILES['wefb_fields']['size'] ) && is_array( $_FILES['wefb_fields']['size'] ) ? $_FILES['wefb_fields']['size'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
		// phpcs:enable WordPress.Security.NonceVerification.Missing

		if ( ! array_key_exists( $key, $file_names ) ) {
			return array();
		}

		return array(
			'name'     => isset( $file_names[ $key ] ) ? $this->sanitize_uploaded_file_value( $file_names[ $key ], 'name' ) : '',
			'type'     => isset( $file_types[ $key ] ) ? $this->sanitize_uploaded_file_value( $file_types[ $key ], 'type' ) : '',
			'tmp_name' => isset( $file_tmp[ $key ] ) ? $this->sanitize_uploaded_file_value( $file_tmp[ $key ], 'tmp_name' ) : '',
			'error'    => isset( $file_error[ $key ] ) ? $this->sanitize_uploaded_file_value( $file_error[ $key ], 'error' ) : UPLOAD_ERR_NO_FILE,
			'size'     => isset( $file_size[ $key ] ) ? $this->sanitize_uploaded_file_value( $file_size[ $key ], 'size' ) : 0,
		);
	}

	private function sanitize_uploaded_file_value( $value, $context ) {
		if ( is_array( $value ) ) {
			return array_map(
				function ( $item ) use ( $context ) {
					return $this->sanitize_uploaded_file_value( $item, $context );
				},
				$value
			);
		}

		switch ( $context ) {
			case 'name':
				return sanitize_file_name( (string) $value );

			case 'type':
				return sanitize_mime_type( (string) $value );

			case 'error':
			case 'size':
				return (int) $value;

			case 'tmp_name':
			default:
				return is_string( $value ) ? $value : '';
		}
	}

	private function normalize_uploaded_files( $file ) {
		$files = array();
		if ( ! isset( $file['name'] ) ) {
			return $files;
		}

		if ( is_array( $file['name'] ) ) {
			$count = count( $file['name'] );
			for ( $index = 0; $index < $count; $index++ ) {
				$files[] = array(
					'name'     => isset( $file['name'][ $index ] ) ? $file['name'][ $index ] : '',
					'type'     => isset( $file['type'][ $index ] ) ? $file['type'][ $index ] : '',
					'tmp_name' => isset( $file['tmp_name'][ $index ] ) ? $file['tmp_name'][ $index ] : '',
					'error'    => isset( $file['error'][ $index ] ) ? $file['error'][ $index ] : UPLOAD_ERR_NO_FILE,
					'size'     => isset( $file['size'][ $index ] ) ? $file['size'][ $index ] : 0,
				);
			}
			return $files;
		}

		$files[] = $file;
		return $files;
	}

	/**
	 * Determine whether a value should be considered empty for validation purposes.
	 *
	 * @param mixed $value Value to check.
	 *
	 * @return bool
	 */
	private function is_sensitive_field_type( $type ) {
		return in_array( sanitize_key( (string) $type ), array( 'user_pass', 'confirm_pass' ), true );
	}

	private function remove_sensitive_submission_keys( &$submission_map ) {
		if ( ! is_array( $submission_map ) ) {
			return;
		}

		$restricted_keys = array( 'user_pass', 'confirm_pass', 'password', 'confirm_password' );
		foreach ( $restricted_keys as $restricted_key ) {
			if ( isset( $submission_map[ $restricted_key ] ) ) {
				unset( $submission_map[ $restricted_key ] );
			}
		}
	}

	private function is_empty_value( $value ) {
		if ( is_array( $value ) ) {
			foreach ( $value as $item ) {
				if ( ! $this->is_empty_value( $item ) ) {
					return false;
				}
			}
			return true;
		}

		if ( is_string( $value ) ) {
			return '' === trim( $value );
		}

		return null === $value;
	}


	private function is_field_visible_for_submission( $field, $submission_map, $field_lookup = array() ) {
		if ( empty( $field['conditional_logic'] ) || ! is_array( $field['conditional_logic'] ) ) {
			return (bool) apply_filters( 'wpefb_field_visibility_for_submission', true, $field, $submission_map, $field_lookup );
		}

		$logic = $field['conditional_logic'];
		if ( empty( $logic['enabled'] ) ) {
			return (bool) apply_filters( 'wpefb_field_visibility_for_submission', true, $field, $submission_map, $field_lookup );
		}

		$trigger_field = isset( $logic['field'] ) ? sanitize_key( (string) $logic['field'] ) : '';
		$operator      = isset( $logic['operator'] ) ? sanitize_key( (string) $logic['operator'] ) : '';
		$target_value  = isset( $logic['value'] ) ? (string) $logic['value'] : '';

		if ( '' === $trigger_field || '' === $operator ) {
			return (bool) apply_filters( 'wpefb_field_visibility_for_submission', true, $field, $submission_map, $field_lookup );
		}

		$trigger_value = $this->get_trigger_field_value( $trigger_field, $submission_map, $field_lookup );
		$visible       = true;

		if ( is_array( $trigger_value ) ) {
			$normalized_values = array_map( 'strval', $trigger_value );
			$in_values         = in_array( $target_value, $normalized_values, true );
			if ( 'equals' === $operator ) {
				$visible = $in_values;
			} elseif ( 'not_equals' === $operator ) {
				$visible = ! $in_values;
			} else {
				$joined_values = implode( ',', $normalized_values );
				if ( 'contains' === $operator ) {
					$visible = false !== strpos( $joined_values, $target_value );
				} elseif ( 'not_contains' === $operator ) {
					$visible = false === strpos( $joined_values, $target_value );
				}
			}
			return (bool) apply_filters( 'wpefb_field_visibility_for_submission', $visible, $field, $submission_map, $field_lookup );
		}

		$value_string = (string) $trigger_value;
		if ( 'equals' === $operator ) {
			$visible = $value_string === $target_value;
		} elseif ( 'not_equals' === $operator ) {
			$visible = $value_string !== $target_value;
		} elseif ( 'contains' === $operator ) {
			$visible = false !== strpos( $value_string, $target_value );
		} elseif ( 'not_contains' === $operator ) {
			$visible = false === strpos( $value_string, $target_value );
		}

		return (bool) apply_filters( 'wpefb_field_visibility_for_submission', $visible, $field, $submission_map, $field_lookup );
	}

	private function get_trigger_field_value( $trigger_field, $submission_map, $field_lookup = array() ) {
		$resolved_keys = array();
		$raw_key       = (string) $trigger_field;
		$sanitized_key = sanitize_key( $raw_key );

		if ( '' !== $sanitized_key ) {
			$resolved_keys[] = $sanitized_key;
		}

		if ( '' !== $raw_key ) {
			$resolved_keys[] = $raw_key;
		}

		if ( '' !== $sanitized_key && isset( $field_lookup[ $sanitized_key ] ) ) {
			$resolved_keys[] = $field_lookup[ $sanitized_key ];
		}

		$resolved_keys = array_values( array_unique( array_filter( $resolved_keys, 'strlen' ) ) );

		foreach ( $resolved_keys as $resolved_key ) {
			if ( isset( $submission_map[ $resolved_key ] ) ) {
				return $submission_map[ $resolved_key ];
			}
		}

		$submitted_fields = $this->get_submitted_fields_map();
		foreach ( $resolved_keys as $resolved_key ) {
			$submitted_key = sanitize_key( (string) $resolved_key );
			if ( '' !== $submitted_key && array_key_exists( $submitted_key, $submitted_fields ) ) {
				return $submitted_fields[ $submitted_key ];
			}
		}

		return '';
	}



	private function get_submitted_fields_map() {
		static $normalized_fields = null;

		if ( null !== $normalized_fields ) {
			return $normalized_fields;
		}

		$submitted_fields = array();
		if ( isset( $_POST['wefb_fields'] ) && is_array( $_POST['wefb_fields'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			// Submission nonce is validated before helper processing begins.
			$submitted_fields = wp_unslash( $_POST['wefb_fields'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		}

		$normalized_fields = array();
		if ( ! is_array( $submitted_fields ) ) {
			return $normalized_fields;
		}

		foreach ( $submitted_fields as $submitted_key => $submitted_value ) {
			$normalized_key = sanitize_key( (string) $submitted_key );
			if ( '' === $normalized_key ) {
				continue;
			}

			// Values are sanitized later via field-type specific sanitization.
			$normalized_fields[ $normalized_key ] = $submitted_value;
		}

		return $normalized_fields;
	}

	private function build_field_lookup( $form_data ) {
		$lookup = array();

		if ( empty( $form_data['sections'] ) || ! is_array( $form_data['sections'] ) ) {
			return $lookup;
		}

		foreach ( $form_data['sections'] as $section ) {
			if ( empty( $section['fields'] ) || ! is_array( $section['fields'] ) ) {
				continue;
			}

			foreach ( $section['fields'] as $field ) {
				$field_id   = isset( $field['id'] ) ? sanitize_key( (string) $field['id'] ) : '';
				$field_name = isset( $field['name'] ) && '' !== (string) $field['name'] ? sanitize_key( (string) $field['name'] ) : $field_id;

				if ( '' !== $field_id && '' !== $field_name ) {
					$lookup[ $field_id ] = $field_name;
				}
			}
		}

		return $lookup;
	}

	private function get_global_settings( $dbhandler ) {
		$keys = array(
			'wpefb_show_asterisk',
			'wpefb_form_theme',
			'wpefb_form_layout',
			'wpefb_allowed_filetypes',
			'wpefb_file_size',
			'wpefb_file_size_error',
			'wpefb_file_prefix',
			'wpefb_multiple_file_uploads',
			'wpefb_notify_site_admin',
			'wpefb_define_recipients',
			'wpefb_account_activation',
			'wpefb_usr_pass_to_user',
			'wpefb_device_submission_limit',
			'wpefb_banned_ip',
			'wpefb_banned_emails',
			'wpefb_show_limit_above_form',
			'wpefb_enable_recaptcha',
			'wpefb_recaptcha_version',
			'wpefb_recaptcha_secret_key',
		);

		$settings = array();
		foreach ( $keys as $key ) {
			$settings[ $key ] = $dbhandler->get_global_option_value( $key );
		}
		return $settings;
	}

	private function validate_recaptcha( $global_settings ) {
		if ( empty( $global_settings['wpefb_enable_recaptcha'] ) ) {
			return true;
		}

		$secret_key = isset( $global_settings['wpefb_recaptcha_secret_key'] ) ? trim( (string) $global_settings['wpefb_recaptcha_secret_key'] ) : '';
		if ( '' === $secret_key ) {
			return new WP_Error( 'recaptcha_missing_secret', __( 'reCAPTCHA is not configured correctly.', 'vedrixa-forms-registration-builder' ) );
		}

		$token = '';
		if ( isset( $_POST['wefb_recaptcha_token'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$token = sanitize_text_field( wp_unslash( $_POST['wefb_recaptcha_token'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}
		if ( '' === $token && isset( $_POST['g-recaptcha-response'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$token = sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}
		if ( '' === $token ) {
			return new WP_Error( 'recaptcha_required', __( 'Please complete the reCAPTCHA validation.', 'vedrixa-forms-registration-builder' ) );
		}

		$response = wp_remote_post(
			'https://www.google.com/recaptcha/api/siteverify',
			array(
				'timeout' => 10,
				'body'    => array(
					'secret'   => $secret_key,
					'response' => $token,
					'remoteip' => $this->get_client_ip(),
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return new WP_Error( 'recaptcha_failed', __( 'Unable to verify reCAPTCHA. Please try again.', 'vedrixa-forms-registration-builder' ) );
		}

		$body   = wp_remote_retrieve_body( $response );
		$result = json_decode( $body, true );
		if ( empty( $result['success'] ) ) {
			return new WP_Error( 'recaptcha_failed', __( 'reCAPTCHA verification failed. Please try again.', 'vedrixa-forms-registration-builder' ) );
		}

		$version = ! empty( $global_settings['wpefb_recaptcha_version'] ) ? sanitize_key( $global_settings['wpefb_recaptcha_version'] ) : 'v2';
		if ( 'v3' === $version && isset( $result['score'] ) && (float) $result['score'] < 0.5 ) {
			return new WP_Error( 'recaptcha_failed', __( 'reCAPTCHA verification score is too low. Please try again.', 'vedrixa-forms-registration-builder' ) );
		}

		return true;
	}

	private function validate_security_restrictions( $form_id, $global_settings ) {
		$ip = $this->get_client_ip();
		if ( ! empty( $ip ) && ! empty( $global_settings['wpefb_banned_ip'] ) ) {
			$patterns = preg_split( '/\s+/', trim( (string) $global_settings['wpefb_banned_ip'] ) );
			foreach ( $patterns as $pattern ) {
				if ( '' !== $pattern && $this->wildcard_match( $pattern, $ip ) ) {
					return new WP_Error( 'ip_blocked', __( 'Your IP address is not allowed to submit this form.', 'vedrixa-forms-registration-builder' ) );
				}
			}
		}

		$device_limit = isset( $global_settings['wpefb_device_submission_limit'] ) ? (int) $global_settings['wpefb_device_submission_limit'] : 0;
		if ( $device_limit > 0 && ! empty( $ip ) ) {
			$key   = $this->get_device_limit_key( $form_id, $ip );
			$count = (int) get_transient( $key );
			if ( $count >= $device_limit ) {
				return new WP_Error( 'device_limit', __( 'Submission limit reached for this device. Please try again later.', 'vedrixa-forms-registration-builder' ) );
			}
		}

		return true;
	}

	private function validate_email_restrictions( $global_settings, $primary_email, $submission_map ) {
		$email = $this->resolve_submission_email( $primary_email, $submission_map );
		if ( empty( $email ) || empty( $global_settings['wpefb_banned_emails'] ) ) {
			return true;
		}

		$patterns = preg_split( '/\s+/', trim( (string) $global_settings['wpefb_banned_emails'] ) );
		foreach ( $patterns as $pattern ) {
			if ( '' !== $pattern && $this->wildcard_match( $pattern, $email ) ) {
				return new WP_Error( 'email_blocked', __( 'This email address is not allowed to submit the form.', 'vedrixa-forms-registration-builder' ) );
			}
		}

		return true;
	}

	private function validate_form_submission_limit( $dbhandler, $form_id, $form_meta ) {
		if ( empty( $form_meta['wpefb_limit_submissions'] ) ) {
			return true;
		}

		$limit = isset( $form_meta['submission_limit'] ) ? (int) $form_meta['submission_limit'] : 0;
		if ( $limit <= 0 ) {
			return true;
		}

		$count = $dbhandler->wpefb_count( 'SUBMISSIONS', array( 'form_id' => (int) $form_id ) );
		if ( (int) $count >= $limit ) {
			$message = ! empty( $form_meta['submission_limit_message'] )
				? wp_strip_all_tags( $form_meta['submission_limit_message'] )
				: __( 'Submission limit has been reached for this form.', 'vedrixa-forms-registration-builder' );
			return new WP_Error( 'limit_reached', $message );
		}

		return true;
	}

	private function validate_user_submission_limit( $dbhandler, $form_id, $form_meta, $primary_email, $submission_map ) {
		if ( empty( $form_meta['wpefb_limit_submissions'] ) ) {
			return true;
		}

		$limit = isset( $form_meta['wpefb_limit_submission'] ) ? (int) $form_meta['wpefb_limit_submission'] : 0;
		if ( $limit <= 0 ) {
			return true;
		}

		$email = $this->resolve_submission_email( $primary_email, $submission_map );

		if ( empty( $email ) ) {
			return true;
		}

		if ( $form_id <= 0 ) {
			return true;
		}

		$count = $dbhandler->wpefb_count(
            'SUBMISSIONS',
            array(
				'form_id' => $form_id,
				'email'   => $email,
            )
        );
		if ( (int) $count >= $limit ) {
			$message = sprintf(
				/* translators: 1: current submission count for this email, 2: configured per-email limit. */
				__( 'You have reached the per-email submission limit for this form (%1$d of %2$d).', 'vedrixa-forms-registration-builder' ),
				(int) $count,
				(int) $limit
			);

			return new WP_Error( 'user_limit_reached', $message );
		}

		return true;
	}

	/**
	 * Resolve the best email value from mapped submission data.
	 *
	 * @param string $primary_email Primary email gathered during field parsing.
	 * @param array  $submission_map Field-name keyed submission values.
	 *
	 * @return string
	 */
	private function resolve_submission_email( $primary_email, $submission_map ) {
		if ( ! empty( $primary_email ) && is_email( $primary_email ) ) {
			return sanitize_email( $primary_email );
		}

		$candidates = array( 'user_email', 'email' );
		foreach ( $candidates as $candidate ) {
			if ( ! empty( $submission_map[ $candidate ] ) ) {
				$email = sanitize_email( (string) $submission_map[ $candidate ] );
				if ( is_email( $email ) ) {
					return $email;
				}
			}
		}

		return '';
	}

	private function get_success_message( $form_meta ) {
		if ( ! empty( $form_meta['wpefb_success_msg_content'] ) ) {
			return wp_kses_post( $form_meta['wpefb_success_msg_content'] );
		}
		return __( 'Thank you! Your submission has been received.', 'vedrixa-forms-registration-builder' );
	}

	private function get_redirect_url( $form_meta ) {
		$redirect = isset( $form_meta['wpefb_form_redirect'] ) ? $form_meta['wpefb_form_redirect'] : 'none';
		if ( 'page' === $redirect && ! empty( $form_meta['wpefb_redirect_page'] ) ) {
			$url = get_permalink( (int) $form_meta['wpefb_redirect_page'] );
			return $url ? $url : '';
		}
		if ( 'url' === $redirect && ! empty( $form_meta['wpefb_redirect_url'] ) ) {
			return esc_url_raw( $form_meta['wpefb_redirect_url'] );
		}
		return '';
	}

	private function replace_template_tags( $content, $submission_data, $submission_map ) {
		$content      = (string) $content;
		$replacements = array(
			'{{site_name}}' => get_bloginfo( 'name' ),
			'{{site_url}}'  => home_url( '/' ),
		);

		foreach ( $submission_map as $key => $value ) {
			$replacements[ '{{' . sanitize_key( $key ) . '}}' ] = $this->format_value_for_template( $value );
		}

		if ( strpos( $content, '{{SUBMISSION_DATA}}' ) !== false ) {
			$rows = array();
			foreach ( $submission_data as $label => $value ) {
				$rows[] = esc_html( $label ) . ': ' . esc_html( $this->format_value_for_template( $value ) );
			}
			$replacements['{{SUBMISSION_DATA}}'] = implode( '<br>', $rows );
		}

		return strtr( $content, $replacements );
	}

	private function format_value_for_template( $value ) {
		if ( is_array( $value ) ) {
			$parts = array();
			foreach ( $value as $item ) {
				if ( is_array( $item ) ) {
					if ( ! empty( $item['url'] ) ) {
						$parts[] = (string) $item['url'];
					} elseif ( ! empty( $item['filename'] ) ) {
						$parts[] = (string) $item['filename'];
					} else {
						$parts[] = wp_json_encode( $item );
					}
				} else {
					$parts[] = (string) $item;
				}
			}
			return implode( ', ', array_filter( $parts, 'strlen' ) );
		}
		return (string) $value;
	}

	private function send_form_emails( $form_id, $form_meta, $global_settings, $primary_email, $submission_data, $submission_map, $created_user_id = 0 ) {
		unset( $form_id );
		$user_headers  = $this->build_mail_headers( $global_settings, $submission_map, $primary_email, false );
		$admin_headers = $this->build_mail_headers( $global_settings, $submission_map, $primary_email, true );

		if ( $created_user_id > 0 ) {
			$user = get_user_by( 'id', $created_user_id );
			if ( $user && ! empty( $user->user_email ) ) {
				$activation_mode = ! empty( $global_settings['wpefb_account_activation'] ) ? sanitize_key( $global_settings['wpefb_account_activation'] ) : 'auto_activate';
				$subject         = ! empty( $form_meta['new_user_email_subject'] ) ? $form_meta['new_user_email_subject'] : __( 'Your account has been created', 'vedrixa-forms-registration-builder' );
				$body            = ! empty( $form_meta['new_user_email_content'] ) ? $form_meta['new_user_email_content'] : __( 'Hello {{user_login}},<br><br>Your account has been created on {{site_name}}.', 'vedrixa-forms-registration-builder' );
				if ( 'verify' === $activation_mode ) {
					$subject = ! empty( $form_meta['user_email_activation_subject'] ) ? $form_meta['user_email_activation_subject'] : __( 'Verify your account', 'vedrixa-forms-registration-builder' );
					$body    = ! empty( $form_meta['user_email_activation_content'] ) ? $form_meta['user_email_activation_content'] : __( 'Hello {{user_login}},<br><br>Please verify your account by clicking this link:<br><a href="{{activation_link}}">{{activation_link}}</a>', 'vedrixa-forms-registration-builder' );
				}
				if ( '' !== trim( $body ) ) {
					$activation_link = $this->get_user_activation_link( $user->ID );
					$body            = strtr(
						$this->replace_template_tags( $body, $submission_data, $submission_map ),
						array(
							'{{user_login}}'      => (string) $user->user_login,
							'{{user_email}}'      => (string) $user->user_email,
							'{{activation_link}}' => (string) $activation_link,
						)
					);
					wp_mail( $user->user_email, wp_strip_all_tags( $subject ), $body, $user_headers );
				}
			}
		}

		if ( ! empty( $global_settings['wpefb_notify_user_submission'] ) && ! empty( $form_meta['wpefb_auto_reply_user'] ) && ! empty( $primary_email ) ) {
			$subject = ! empty( $form_meta['wpefb_email_subject'] ) ? $form_meta['wpefb_email_subject'] : __( 'Form submission received', 'vedrixa-forms-registration-builder' );
			$body    = ! empty( $form_meta['wpefb_email_content'] ) ? $form_meta['wpefb_email_content'] : '';
			$body    = $this->replace_template_tags( $body, $submission_data, $submission_map );
			wp_mail( $primary_email, wp_strip_all_tags( $subject ), $body, $user_headers );
		}

		if ( ! empty( $global_settings['wpefb_notify_site_admin'] ) ) {
			$subject = ! empty( $form_meta['new_submission_email_subject'] ) ? $form_meta['new_submission_email_subject'] : __( 'New form submission', 'vedrixa-forms-registration-builder' );
			$body    = ! empty( $form_meta['new_submission_email_content'] ) ? $form_meta['new_submission_email_content'] : '{{SUBMISSION_DATA}}';
			$body    = $this->replace_template_tags( $body, $submission_data, $submission_map );
			$to      = ! empty( $global_settings['wpefb_define_recipients'] ) ? $global_settings['wpefb_define_recipients'] : get_option( 'admin_email' );
			wp_mail( $to, wp_strip_all_tags( $subject ), $body, $admin_headers );
		}
	}


	/**
	 * Build HTML mail headers using configured sender options.
	 *
	 * @param array  $global_settings Global plugin settings.
	 * @param array  $submission_map Submission map.
	 * @param string $primary_email Primary submitter email.
	 * @param bool   $for_admin Whether headers are for admin notification email.
	 *
	 * @return array
	 */
	private function build_mail_headers( $global_settings, $submission_map, $primary_email, $for_admin = false ) {
		$headers = array( 'Content-Type: text/html; charset=UTF-8' );

		$name_key  = $for_admin ? 'wpefb_admin_senders_display_name' : 'wpefb_senders_display_name';
		$email_key = $for_admin ? 'wpefb_admin_senders_email' : 'wpefb_senders_email';

		$sender_name  = isset( $global_settings[ $name_key ] ) ? sanitize_text_field( (string) $global_settings[ $name_key ] ) : '';
		$sender_email = isset( $global_settings[ $email_key ] ) ? (string) $global_settings[ $email_key ] : '';
		if ( $for_admin ) {
			$sender_email = str_replace( '{{useremail}}', (string) $primary_email, $sender_email );
		}

		$submitted_name = '';
		if ( isset( $submission_map['name'] ) && ! is_array( $submission_map['name'] ) ) {
			$submitted_name = sanitize_text_field( (string) $submission_map['name'] );
		} elseif ( isset( $submission_map['first_name'] ) && ! is_array( $submission_map['first_name'] ) ) {
			$submitted_name = sanitize_text_field( (string) $submission_map['first_name'] );
		}
		$sender_name = str_replace( '{{user}}', $submitted_name, $sender_name );

		if ( '' === $sender_name ) {
			$sender_name = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		}

		$sender_email = sanitize_email( $sender_email );
		if ( ! is_email( $sender_email ) ) {
			$sender_email = sanitize_email( get_option( 'admin_email' ) );
		}

		if ( is_email( $sender_email ) ) {
			$headers[] = 'From: ' . $sender_name . ' <' . $sender_email . '>';
		}

		return $headers;
	}

	private function maybe_create_wp_user( $submission_map, $form_meta, $global_settings ) {
		$email = '';
		if ( ! empty( $submission_map['user_email'] ) ) {
			$email = sanitize_email( $submission_map['user_email'] );
		} elseif ( ! empty( $submission_map['email'] ) ) {
			$email = sanitize_email( $submission_map['email'] );
		}
		if ( empty( $email ) || ! is_email( $email ) ) {
			return 0;
		}

		$existing = email_exists( $email );
		if ( $existing ) {
			return (int) $existing;
		}

		$username = ! empty( $submission_map['user_name'] ) ? sanitize_user( $submission_map['user_name'], true ) : '';
		if ( empty( $username ) ) {
			$username = sanitize_user( current( explode( '@', $email ) ), true );
		}
		if ( username_exists( $username ) ) {
			$username .= '_' . wp_rand( 100, 9999 );
		}

		$password = ! empty( $submission_map['user_pass'] ) ? (string) $submission_map['user_pass'] : wp_generate_password( 12, true );
		$user_id  = wp_insert_user(
			array(
				'user_login' => $username,
				'user_pass'  => $password,
				'user_email' => $email,
			)
		);
		if ( is_wp_error( $user_id ) ) {
			return 0;
		}

		$user_role = ! empty( $form_meta['user_role'] ) ? sanitize_key( $form_meta['user_role'] ) : 'subscriber';
		if ( ! $this->is_valid_user_role( $user_role ) ) {
			$user_role = 'subscriber';
		}
		$user = new WP_User( $user_id );
		$user->set_role( $user_role );

		if ( ! empty( $submission_map['first_name'] ) ) {
			update_user_meta( $user_id, 'first_name', sanitize_text_field( $submission_map['first_name'] ) );
		}
		if ( ! empty( $submission_map['last_name'] ) ) {
			update_user_meta( $user_id, 'last_name', sanitize_text_field( $submission_map['last_name'] ) );
		}

		if ( ! empty( $global_settings['wpefb_usr_pass_to_user'] ) ) {
			$subject = __( 'Your account details', 'vedrixa-forms-registration-builder' );
			$body    = sprintf(
				'%s<br><br>%s: %s<br>%s: %s',
				esc_html__( 'Your account has been created.', 'vedrixa-forms-registration-builder' ),
				esc_html__( 'Username', 'vedrixa-forms-registration-builder' ),
				esc_html( $username ),
				esc_html__( 'Password', 'vedrixa-forms-registration-builder' ),
				esc_html( $password )
			);
			wp_mail( $email, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );
		}

		$activation_mode = ! empty( $global_settings['wpefb_account_activation'] ) ? sanitize_key( $global_settings['wpefb_account_activation'] ) : 'auto_activate';
		if ( 'verify' === $activation_mode ) {
			$token = wp_generate_password( 32, false, false );
			update_user_meta( $user_id, 'wpefb_activation_token', $token );
			update_user_meta( $user_id, 'wpefb_activation_requested_at', time() );
			$this->update_user_account_status( $user_id, 'pending' );
		} elseif ( 'manual' === $activation_mode ) {
			$this->update_user_account_status( $user_id, 'inactive' );
		} else {
			$this->update_user_account_status( $user_id, 'active' );
		}

		return (int) $user_id;
	}

	private function mark_device_submission( $form_id ) {
		$ip = $this->get_client_ip();
		if ( empty( $ip ) ) {
			return;
		}
		$key   = $this->get_device_limit_key( $form_id, $ip );
		$count = (int) get_transient( $key );
		set_transient( $key, $count + 1, DAY_IN_SECONDS );
	}

	private function get_device_limit_key( $form_id, $ip ) {
		return 'wpefb_device_limit_' . md5( $form_id . '|' . $ip . '|' . gmdate( 'Y-m-d' ) );
	}

	private function get_client_ip() {
		$ip        = '';
		$forwarded = (string) filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		$remote    = (string) filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_FULL_SPECIAL_CHARS );

		if ( '' !== $forwarded ) {
			$parts = explode( ',', $forwarded );
			$ip    = trim( $parts[0] );
		} elseif ( '' !== $remote ) {
			$ip = trim( $remote );
		}

		return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : '';
	}

	/**
	 * Validate password using field-level policy settings.
	 *
	 * @param array  $field Password field settings.
	 * @param string $password Password value.
	 * @param string $label Field label.
	 *
	 * @return true|WP_Error
	 */
	private function validate_password_policy( $field, $password, $label ) {
		$min_length = isset( $field['password_min_length'] ) ? absint( $field['password_min_length'] ) : 8;
		$min_length = $min_length > 0 ? $min_length : 8;

		if ( strlen( $password ) < $min_length ) {
			return new WP_Error(
				'weak_password',
				sprintf(
					/* translators: 1: Field label, 2: minimum character length. */
					__( '%1$s must be at least %2$d characters long.', 'vedrixa-forms-registration-builder' ),
					$label,
					$min_length
				)
			);
		}

		$rules = array(
			'password_require_uppercase' => '/[A-Z]/',
			'password_require_lowercase' => '/[a-z]/',
			'password_require_number'    => '/\d/',
			'password_require_special'   => '/[^A-Za-z0-9]/',
		);

		foreach ( $rules as $setting_key => $pattern ) {
			if ( empty( $field[ $setting_key ] ) ) {
				continue;
			}
			if ( ! preg_match( $pattern, $password ) ) {
				/* translators: %s: Password field label. */
				return new WP_Error( 'weak_password', sprintf( __( '%s does not meet password strength requirements.', 'vedrixa-forms-registration-builder' ), $label ) );
			}
		}

		return true;
	}

	private function wildcard_match( $pattern, $value ) {
		$pattern = str_replace( array( '\*', '\?' ), array( '.*', '.' ), preg_quote( $pattern, '/' ) );
		return (bool) preg_match( '/^' . $pattern . '$/i', $value );
	}

	/**
	 * Validate whether a role key exists in the current site roles.
	 *
	 * @param string $role Role slug.
	 *
	 * @return bool
	 */
	private function is_valid_user_role( $role ) {
		$role_obj = wp_roles()->get_role( $role );
		return $role_obj instanceof WP_Role;
	}

	/**
	 * Return the plugin-managed account status for a user.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return string
	 */
	public function get_user_account_status( $user_id ) {
		$status = get_user_meta( $user_id, 'wpefb_account_status', true );
		$status = is_string( $status ) ? sanitize_key( $status ) : '';
		if ( '' === $status ) {
			return 'active';
		}

		return $status;
	}

	/**
	 * Update plugin-managed account status.
	 *
	 * @param int    $user_id User ID.
	 * @param string $status  active|inactive|pending.
	 *
	 * @return bool
	 */
	public function update_user_account_status( $user_id, $status ) {
		$allowed_statuses = array( 'active', 'inactive', 'pending' );
		$status           = sanitize_key( $status );
		if ( ! in_array( $status, $allowed_statuses, true ) ) {
			return false;
		}

		update_user_meta( $user_id, 'wpefb_account_status', $status );
		if ( 'active' === $status ) {
			delete_user_meta( $user_id, 'wpefb_activation_token' );
			delete_user_meta( $user_id, 'wpefb_activation_requested_at' );
		}

		return true;
	}

	/**
	 * Build account activation link for a user when token exists.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return string
	 */
	public function get_user_activation_link( $user_id ) {
		$token = get_user_meta( $user_id, 'wpefb_activation_token', true );
		if ( ! is_string( $token ) || '' === $token ) {
			return '';
		}

		return esc_url_raw(
			add_query_arg(
				array(
					'wefb_activate' => 1,
					'uid'           => (int) $user_id,
					'token'         => rawurlencode( $token ),
				),
				home_url( '/' )
			)
		);
	}

	/**
	 * Activate an account by user ID and token.
	 *
	 * @param int    $user_id User ID.
	 * @param string $token Activation token.
	 *
	 * @return bool
	 */
	public function activate_user_from_token( $user_id, $token ) {
		$stored_token = get_user_meta( $user_id, 'wpefb_activation_token', true );
		if ( ! is_string( $stored_token ) || '' === $stored_token ) {
			return false;
		}

		if ( ! hash_equals( $stored_token, (string) $token ) ) {
			return false;
		}

		return $this->update_user_account_status( $user_id, 'active' );
	}

}
