<?php
/**
 * Lite form-plugin integrations — Pro overlay only.
 *
 * Secure design (rafter-secure-design / auth):
 * - Identity: site visitors submitting third-party forms. No new auth primitive.
 * - AuthZ: This file is absent from the wordpress.org Lite zip. Lite keeps
 *   WP + WooCommerce surfaces. Overlay + license unlocks CF7, WPForms,
 *   Formidable, Fluent, Elementor, and Basic Forms.
 *
 * @package CloudflareTurnstile
 */

namespace CloudflareTurnstile;

use WP_PowerSuite\Captcha\Shared\CaptchaRuntime;
use WP_PowerSuite\Captcha\Shared\SkipRules;
use WP_PowerSuite\Captcha\Shared\WidgetLayoutStyles;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class FormIntegrations {

	private static bool $booted = false;

	private static ?Module $module = null;

	/** @var bool Whether Elementor Pro form hooks (injector, validation) are registered. */
	private static bool $elementor_forms_hooks_registered = false;

	public static function boot( Module $m ): void {
		if ( self::$booted ) {
			return;
		}
		self::$booted = true;
		self::$module = $m;

		$s = $m->getCaptchaSettings();

		if ( ! empty( $s['cf7'] ) && class_exists( 'WPCF7' ) ) {
			add_filter(
				'wpcf7_form_elements',
				static function ( string $content ) use ( $m ): string {
					if ( ( $m->getCaptchaSettings()['site_key'] ?? '' ) === '' ) {
						return $content;
					}
					ob_start();
					$m->renderWidget( 'cf7', $m->disableSubmitPair( 'turnstileCF7Callback', '.wpcf7-submit' ) );
					return self::injectBeforeSubmit( $content, (string) ob_get_clean() );
				},
				20
			);
			add_filter(
				'wpcf7_spam',
				static function ( bool $spam, $submission ) use ( $m ): bool {
					if ( $spam ) {
						return true;
					}
					$settings = $m->getCaptchaSettings();
					$skip     = is_array( $settings['skip_rules'] ?? null ) ? $settings['skip_rules'] : [];
					if ( SkipRules::shouldSkip( $skip ) ) {
						return false;
					}
					$posted = null;
					if ( is_object( $submission ) && method_exists( $submission, 'get_posted_data' ) ) {
						$posted = $submission->get_posted_data();
						$posted = is_array( $posted ) ? $posted : null;
					}
					return ! $m->verifyCaptchaToken( $m->postedCaptchaToken( $posted ), 'cf7' );
				},
				10,
				2
			);
		}

		if ( ! empty( $s['wpforms'] ) && function_exists( 'wpforms' ) ) {
			add_action(
				'wpforms_display_submit_before',
				static function ( $form_data ) use ( $m ): void {
					if ( ! is_array( $form_data ) || empty( $form_data['id'] ) ) {
						return;
					}
					$m->renderWidget( 'wpforms_' . (string) absint( $form_data['id'] ), $m->disableSubmitPair( 'turnstileWPFCallback', '.wpforms-submit' ) );
				},
				10,
				1
			);
			add_filter(
				'wpforms_process_initial_errors',
				static function ( array $errors, array $form_data ) use ( $m ): array {
					$form_id = isset( $form_data['id'] ) ? absint( $form_data['id'] ) : 0;
					if ( $form_id <= 0 ) {
						return $errors;
					}
					if ( ! $m->verifyCaptchaToken( $m->postedCaptchaToken( $form_data ), 'wpforms_' . (string) $form_data['id'] ) ) {
						if ( ! isset( $errors[ $form_id ] ) || ! is_array( $errors[ $form_id ] ) ) {
							$errors[ $form_id ] = [];
						}
						$errors[ $form_id ]['footer'] = $m->captchaErrorMessage( __( 'Please complete the security verification.', 'wppowersuite-lite' ), 'wpforms' );
					}
					return $errors;
				},
				10,
				2
			);
		}

		if ( ! empty( $s['formidable'] ) && class_exists( 'FrmFormsController' ) ) {
			add_filter(
				'frm_submit_button_html',
				static function ( $button, $args ) use ( $m ) {
					$form = ( is_array( $args ) && ! empty( $args['form'] ) && is_object( $args['form'] ) ) ? $args['form'] : null;
					if ( $form === null || ! isset( $form->id ) ) {
						return (string) $button;
					}
					ob_start();
					$m->renderWidget(
						'formidable_' . $form->id,
						$m->disableSubmitPair( 'turnstileFormidableCallback', '.frm_forms .frm_button_submit' )
					);
					return (string) ob_get_clean() . $button;
				},
				10,
				2
			);
			add_filter(
				'frm_validate_entry',
				static function ( array $errors, array $values ) use ( $m ): array {
					$form_id = isset( $values['form_id'] ) ? absint( $values['form_id'] ) : 0;
					if ( $form_id <= 0 ) {
						return $errors;
					}
					if ( ! $m->verifySubmittedToken( 'formidable_' . $form_id ) ) {
						$errors['turnstile'] = $m->captchaErrorMessage( __( 'Please complete the security verification.', 'wppowersuite-lite' ), 'formidable' );
					}
					return $errors;
				},
				20,
				2
			);
		}

		if ( ! empty( $s['fluent_forms'] ) && defined( 'FLUENTFORM' ) ) {
			add_action( 'fluentform/render_item_submit_button', [ self::class, 'renderWidgetFluentForms' ], 5, 2 );
			add_filter( 'fluentform/validation_errors', [ self::class, 'validateFluentForms' ], 10, 4 );
			add_filter( 'fluentform/global_form_vars', [ self::class, 'injectFluentConversationalTurnstileVars' ], 20 );
			add_action( 'fluentform/conversational_enqueue_assets', [ self::class, 'enqueueTurnstileForFluentConversational' ], 10, 1 );
			add_action( 'fluentform/before_insert_submission', [ self::class, 'validateFluentFormsBeforeInsert' ], 10, 3 );
		}

		if ( ! empty( $s['basic_forms'] ) ) {
			add_action(
				'wpps_basic_forms_before_submit',
				static function ( int $form_id, array $settings ) use ( $m ): void {
					if ( ! CaptchaRuntime::basicFormsCaptchaActive( $settings ) ) {
						return;
					}
					if ( ( $settings['captcha_provider'] ?? '' ) !== 'turnstile' ) {
						return;
					}
					$fid  = (int) $form_id;
					$opts = [];
					if ( ! empty( $m->getCaptchaSettings()['disable_submit'] ) ) {
						$m->enqueueTurnstileScript();
						$cb  = 'wppsBfTsUnlock' . $fid;
						$sel = '.wpps-bf-form[data-form-id="' . $fid . '"] .wpps-bf-submit';
						wp_add_inline_script(
							Module::SCRIPT_HANDLE,
							'function ' . $cb . '(){document.querySelectorAll(' . wp_json_encode( $sel ) . ').forEach(function(el){el.style.setProperty("pointer-events","auto","important");el.style.setProperty("opacity","1","important");});}',
							'before'
						);
						$opts = $m->disableSubmitPair( $cb, $sel );
					}
					$m->renderWidget( 'basic_forms_' . $fid, $opts );
				},
				10,
				2
			);
			add_filter(
				'wpps_verify_basic_forms_captcha',
				static function ( bool $verified, int $form_id, array $settings ) use ( $m ): bool {
					if ( ! CaptchaRuntime::basicFormsCaptchaActive( $settings ) ) {
						return $verified;
					}
					if ( ( $settings['captcha_provider'] ?? '' ) !== 'turnstile' ) {
						return $verified;
					}
					return $m->verifySubmittedToken( 'basic_forms_' . $form_id );
				},
				10,
				3
			);
			add_action( 'admin_enqueue_scripts', [ $m, 'enqueueTurnstileInAdminForBasicForms' ], 5 );
		}

		if ( ! empty( $s['elementor_forms'] ) ) {
			if ( did_action( 'elementor/loaded' ) ) {
				self::registerElementorProFormsIntegration();
			} else {
				add_action( 'elementor/loaded', [ self::class, 'registerElementorProFormsIntegration' ] );
			}
		}
	}

	public static function renderWidgetFluentForms( $submit_field, $form = null ): void {
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		unset( $submit_field );
		$form_id = 'form';
		if ( is_object( $form ) && isset( $form->id ) ) {
			$form_id = (string) $form->id;
		}
		$m->renderWidget(
			'fluent_' . $form_id,
			$m->disableSubmitPair( 'turnstileFluentCallback', '.fluentform .ff-btn-submit' )
		);
	}

	/**
	 * Feed our Turnstile keys into Fluent conversational form JS.
	 *
	 * Conversational forms do not call `render_item_submit_button`. Their bundled JS
	 * only mints a token when `globalVars.form.turnstile.siteKey` is set — which is
	 * normally Fluent's own field. When our integration is on and the form has no
	 * Turnstile field of its own, inject ours so the built-in conversational path runs.
	 *
	 * @param array<string, mixed> $vars Localized conversational / form vars.
	 * @return array<string, mixed>
	 */
	public static function injectFluentConversationalTurnstileVars( array $vars ): array {
		$m = self::module();
		if ( $m === null ) {
			return $vars;
		}
		$settings = $m->getCaptchaSettings();
		$site_key = (string) ( $settings['site_key'] ?? '' );
		if ( $site_key === '' || empty( $settings['fluent_forms'] ) ) {
			return $vars;
		}
		if ( ! isset( $vars['form'] ) || ! is_array( $vars['form'] ) ) {
			return $vars;
		}
		if ( ! empty( $vars['form']['turnstile'] ) && is_array( $vars['form']['turnstile'] )
			&& ! empty( $vars['form']['turnstile']['siteKey'] ) ) {
			return $vars;
		}
		$vars['form']['turnstile'] = [
			'siteKey'    => $site_key,
			'appearance' => 'interaction-only',
		];
		return $vars;
	}

	/**
	 * @param mixed $form Form object from Fluent Forms.
	 */
	public static function enqueueTurnstileForFluentConversational( $form ): void {
		unset( $form );
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		$m->enqueueTurnstileScript();
	}

	/**
	 * @param array  $errors    Validation errors.
	 * @param array  $form_data Form data.
	 * @param object $form      Form object.
	 * @param array  $fields    Form fields.
	 * @return array
	 */
	public static function validateFluentForms( array $errors, array $form_data, $form, array $fields ): array {
		$m = self::module();
		if ( $m === null ) {
			return $errors;
		}
		unset( $fields );
		if ( self::isFluentConversationalForm( $form ) && self::fluentFormHasNativeTurnstile( $form ) ) {
			return $errors;
		}
		$form_id = ( is_object( $form ) && isset( $form->id ) ) ? $form->id : '0';
		$action  = self::isFluentConversationalForm( $form ) ? '' : ( 'fluent_' . $form_id );
		if ( ! $m->verifyCaptchaToken( $m->postedCaptchaToken( $form_data ), $action ) ) {
			$errors['turnstile'] = $m->captchaErrorMessage( __( 'Please complete the security verification.', 'wppowersuite-lite' ), 'fluent_forms' );
		}
		return $errors;
	}

	/**
	 * Final gate for Fluent submissions (covers conversational paths that skip
	 * `fluentform/validation_errors`).
	 *
	 * @param array  $insert_data Submission insert payload.
	 * @param array  $data        Raw submitted data.
	 * @param object $form        Form object.
	 */
	public static function validateFluentFormsBeforeInsert( $insert_data, $data, $form ): void {
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		unset( $insert_data );
		if ( ! self::isFluentConversationalForm( $form ) ) {
			return;
		}
		if ( self::fluentFormHasNativeTurnstile( $form ) ) {
			return;
		}
		$token_data = is_array( $data ) ? $data : null;
		if ( $m->verifyCaptchaToken( $m->postedCaptchaToken( $token_data ), '' ) ) {
			return;
		}
		wp_send_json(
			[
				'errors' => [
					'turnstile' => [
						$m->captchaErrorMessage( __( 'Please complete the security verification.', 'wppowersuite-lite' ), 'fluent_forms' ),
					],
				],
			],
			422
		);
	}

	public static function registerElementorProFormsIntegration(): void {
		if ( self::$elementor_forms_hooks_registered ) {
			return;
		}
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		$settings = $m->getCaptchaSettings();
		if ( empty( $settings['elementor_forms'] ) || ! did_action( 'elementor/loaded' ) || ! defined( 'ELEMENTOR_PRO_VERSION' ) ) {
			return;
		}
		self::$elementor_forms_hooks_registered = true;
		add_action( 'wp_enqueue_scripts', [ self::class, 'enqueueTurnstileForElementor' ] );
		add_action( 'wp_enqueue_scripts', [ self::class, 'printElementorInjectorScript' ], 31 );
		add_action( 'elementor_pro/forms/validation', [ self::class, 'validateElementorFormSubmission' ], 10, 2 );
	}

	public static function enqueueTurnstileForElementor(): void {
		$m = self::module();
		if ( $m === null || is_admin() || ! self::requestMayContainElementorForm() ) {
			return;
		}
		$m->enqueueTurnstileScript();
		if ( ! empty( $m->getCaptchaSettings()['disable_submit'] ) ) {
			$m->enqueueDisableSubmitScript();
		}
	}

	public static function printElementorInjectorScript(): void {
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		if ( ! wp_script_is( Module::SCRIPT_HANDLE, 'enqueued' ) && ! wp_script_is( Module::SCRIPT_HANDLE, 'done' ) ) {
			return;
		}
		$settings = $m->getCaptchaSettings();
		$site_key = isset( $settings['site_key'] ) ? (string) $settings['site_key'] : '';
		if ( $site_key === '' ) {
			return;
		}
		if ( SkipRules::shouldSkip( is_array( $settings['skip_rules'] ?? null ) ? $settings['skip_rules'] : [] ) ) {
			return;
		}
		$theme      = isset( $settings['theme'] ) ? (string) $settings['theme'] : 'auto';
		$api_size   = WidgetLayoutStyles::turnstileApiSize( $settings );
		$full_width = WidgetLayoutStyles::widgetFullWidthEnabled( $settings );
		$appearance = isset( $settings['widget_visible_appearance'] ) ? (string) $settings['widget_visible_appearance'] : 'interaction-only';
		if ( ! in_array( $appearance, [ 'always', 'interaction-only' ], true ) ) {
			$appearance = 'interaction-only';
		}
		$disable_submit       = ! empty( $settings['disable_submit'] );
		$elementor_unlock_raw = 'form.elementor-form button[type="submit"], form.elementor-form input[type="submit"], form.elementor-form .elementor-button[type="submit"]';
		$elementor_unlock_css = preg_replace( '/[^#\.a-zA-Z0-9 _\-:,]/', '', $elementor_unlock_raw );
		WidgetLayoutStyles::printFrontEndOnce();
		ob_start();
		?>
(function () {
		var SITE_KEY = <?php echo wp_json_encode( $site_key ); ?>;
	var THEME    = <?php echo wp_json_encode( $theme ); ?>;
	var APPEARANCE = <?php echo wp_json_encode( $appearance ); ?>;
	var DISABLE_SUBMIT = <?php echo $disable_submit ? 'true' : 'false'; ?>;

	function inject(form) {
		if (!form || form.dataset.wppsTurnstileInjected === '1') { return; }
		var submit = form.querySelector('button[type="submit"], input[type="submit"], .elementor-button[type="submit"]');
		if (!submit) { return; }
		var wrap = document.createElement('div');
		wrap.className = 'wpps-captcha-cushion wpps-elementor-turnstile elementor-field-group';
		if (<?php echo $full_width ? 'true' : 'false'; ?>) {
			wrap.classList.add('wpps-captcha-core-form-fit');
		}
		wrap.style.margin = '0 0 1.25em 0';
		wrap.style.maxWidth = '100%';
		wrap.style.boxSizing = 'border-box';
		wrap.style.display = 'block';
		wrap.style.textAlign = 'left';
		var widget = document.createElement('div');
		widget.className = 'cf-turnstile wpps-turnstile-widget';
		widget.setAttribute('data-sitekey', SITE_KEY);
		widget.setAttribute('data-theme', THEME);
		widget.setAttribute('data-size', <?php echo wp_json_encode( $api_size ); ?>);
		widget.setAttribute('data-appearance', APPEARANCE);
		if (DISABLE_SUBMIT) {
			widget.setAttribute('data-callback', 'turnstileElementorCallback');
		}
		var formIdInput = form.querySelector('input[name="form_id"]');
		var formAction = 'elementor_form';
		if (formIdInput && formIdInput.value) {
			formAction = 'elementor_' + String(formIdInput.value).replace(/[^a-zA-Z0-9_-]/g, '');
		}
		widget.setAttribute('data-action', formAction);
		wrap.appendChild(widget);
		submit.parentNode.insertBefore(wrap, submit);
		if (DISABLE_SUBMIT) {
			var css = <?php echo wp_json_encode( $elementor_unlock_css ); ?>;
			if (css) {
				var st = document.createElement('style');
				st.textContent = css + '{pointer-events:none!important;opacity:0.5!important;}';
				document.head.appendChild(st);
			}
		}
		form.dataset.wppsTurnstileInjected = '1';
		if (window.turnstile && typeof window.turnstile.render === 'function') {
			try { window.turnstile.render(widget); } catch (e) { /* already rendered */ }
		}
	}

	function scan() {
		document.querySelectorAll('form.elementor-form').forEach(inject);
	}

	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', scan);
	} else {
		scan();
	}

	if (window.jQuery) {
		window.jQuery(document).on('elementor/popup/show', scan);
	}
	window.addEventListener('load', scan);
})();
		<?php
		$js = (string) ob_get_clean();
		\WP_PowerSuite\Core\InlineAssets::script( 'wpps-turnstile-elementor-injector', $js, [ 'jquery' ] );
	}

	/**
	 * @param \ElementorPro\Modules\Forms\Classes\Form_Record|mixed $record
	 * @param \ElementorPro\Modules\Forms\Classes\Ajax_Handler|mixed $ajax_handler
	 */
	public static function validateElementorFormSubmission( $record, $ajax_handler ): void {
		$m = self::module();
		if ( $m === null ) {
			return;
		}
		$form_id = '';
		if ( is_object( $record ) && method_exists( $record, 'get_form_settings' ) ) {
			$fs = $record->get_form_settings();
			if ( is_array( $fs ) && isset( $fs['id'] ) ) {
				$form_id = (string) $fs['id'];
			}
		}
		if ( $form_id === '' && isset( $_POST['form_id'] ) ) {
			$form_id = sanitize_text_field( wp_unslash( (string) $_POST['form_id'] ) );
		}
		$action = $form_id !== '' ? 'elementor_' . preg_replace( '/[^a-zA-Z0-9_-]/', '', $form_id ) : '';

		if ( $m->verifySubmittedToken( $action ) ) {
			return;
		}
		$message = $m->captchaErrorMessage( __( 'Please complete the security verification.', 'wppowersuite-lite' ), 'elementor_forms' );
		if ( is_object( $ajax_handler ) && method_exists( $ajax_handler, 'add_error' ) ) {
			$ajax_handler->add_error( 'wpps_turnstile', $message );
		}
	}

	private static function isFluentConversationalForm( $form ): bool {
		$form_id = self::fluentFormId( $form );
		if ( $form_id <= 0 ) {
			return false;
		}
		if ( class_exists( '\FluentForm\App\Helpers\Helper' )
			&& method_exists( '\FluentForm\App\Helpers\Helper', 'isConversionForm' ) ) {
			return (bool) \FluentForm\App\Helpers\Helper::isConversionForm( $form_id );
		}
		return false;
	}

	private static function fluentFormId( $form ): int {
		if ( is_object( $form ) && isset( $form->id ) ) {
			return (int) $form->id;
		}
		if ( is_numeric( $form ) ) {
			return (int) $form;
		}
		return 0;
	}

	private static function fluentFormHasNativeTurnstile( $form ): bool {
		$form_id = self::fluentFormId( $form );
		if ( $form_id <= 0 || ! function_exists( 'wpFluent' ) ) {
			return false;
		}
		try {
			$row = wpFluent()->table( 'fluentform_forms' )->find( $form_id );
		} catch ( \Throwable $e ) {
			return false;
		}
		if ( ! $row || empty( $row->form_fields ) ) {
			return false;
		}
		$fields = is_string( $row->form_fields ) ? json_decode( $row->form_fields, true ) : null;
		if ( ! is_array( $fields ) ) {
			return false;
		}
		$encoded = wp_json_encode( $fields );
		return is_string( $encoded ) && str_contains( $encoded, '"element":"turnstile"' );
	}

	private static function requestMayContainElementorForm(): bool {
		if ( ! is_singular() ) {
			return false;
		}
		$post_id = get_queried_object_id();
		if ( $post_id <= 0 ) {
			return false;
		}
		$content = (string) get_post_field( 'post_content', $post_id );
		if ( strpos( $content, 'elementor-form' ) !== false || strpos( $content, '[elementor-template' ) !== false ) {
			return true;
		}
		$elementor_data = (string) get_post_meta( $post_id, '_elementor_data', true );
		return $elementor_data !== '' && ( strpos( $elementor_data, '"form_fields"' ) !== false || strpos( $elementor_data, '"form_name"' ) !== false );
	}

	private static function injectBeforeSubmit( string $html, string $inject ): string {
		if ( $inject === '' ) {
			return $html;
		}
		if ( preg_match( '/<input\b[^>]*\btype\s*=\s*["\']submit["\'][^>]*\/?>/i', $html ) ) {
			return (string) preg_replace(
				'/(<input\b[^>]*\btype\s*=\s*["\']submit["\'][^>]*\/?>)/i',
				$inject . '$1',
				$html,
				1
			);
		}
		if ( preg_match( '/<button\b[^>]*\btype\s*=\s*["\']submit["\'][^>]*>/i', $html ) ) {
			return (string) preg_replace(
				'/(<button\b[^>]*\btype\s*=\s*["\']submit["\'][^>]*>)/i',
				$inject . '$1',
				$html,
				1
			);
		}
		if ( stripos( $html, '</form>' ) !== false ) {
			return (string) preg_replace( '/(<\/form>)/i', $inject . '$1', $html, 1 );
		}
		return $html . $inject;
	}

	private static function module(): ?Module {
		return self::$module;
	}
}
