<?php
/**
 * Extra integrations (Gravity, Jetpack, bbPress, BuddyPress, UM, MemberPress, EDD, PMP, MC4WP)
 * plus optimizer compatibility and resource hints — mirrors Cloudflare Turnstile coverage.
 *
 * @package GoogleRecaptcha
 */

namespace GoogleRecaptcha;

use WP_PowerSuite\Captcha\Shared\CaptchaRuntime;

final class ExtendedIntegrations {

	private static bool $booted = false;

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

		$s = $m->getCaptchaSettings();

		if ( ! empty( $s['perf_compat'] ) ) {
			self::perf_compat();
		}
		if ( ! empty( $s['resource_hints'] ) && CaptchaRuntime::hasAnyFrontendProtection( $s ) ) {
			add_filter( 'wp_resource_hints', [ self::class, 'resource_hints' ], 10, 2 );
			if ( CaptchaRuntime::needsWpLoginScreenAssets( $s ) ) {
				add_action( 'login_head', [ self::class, 'login_preconnect' ], 1 );
			}
		}

		if ( ! empty( $s['blocksy_compat'] ) && CaptchaRuntime::needsWpLoginScreenAssets( $s ) ) {
			add_action(
				'wp_enqueue_scripts',
				static function () use ( $s ) {
					if ( ! CaptchaRuntime::needsWpLoginScreenAssets( $s ) ) {
						return;
					}
					$t = wp_get_theme();
					if ( ! $t || $t->get( 'TextDomain' ) !== 'blocksy' ) {
						return;
					}
					if ( ! defined( 'WP_POWERSUITE_PLUGIN_URL' ) ) {
						return;
					}
					wp_enqueue_script(
						'wpps-grecaptcha-blocksy',
						WP_POWERSUITE_PLUGIN_URL . 'modules/google-recaptcha/assets/blocksy-compat.js',
						[],
						Module::MODULE_VERSION,
						true
					);
				},
				25
			);
		}

		if ( ! empty( $s['integration_gravity'] ) && class_exists( 'GFForms' ) ) {
			add_filter(
				'gform_submit_button',
				static function ( $button, $form ) use ( $m ) {
					if ( ! is_array( $form ) || empty( $form['id'] ) ) {
						return $button;
					}
					ob_start();
					$m->renderWidget( 'gravity_' . absint( $form['id'] ), $m->unlockSubmitOpts( 'wppsGrGravityCallback', '.gform_button' ) );
					return (string) ob_get_clean() . $button;
				},
				10,
				2
			);
			add_filter(
				'gform_validation',
				static function ( $result ) use ( $m ) {
					if ( empty( $result['form']['id'] ) || ! is_array( $result ) ) {
						return $result;
					}
					$form_id = absint( $result['form']['id'] );
					// Multi-page: only enforce on the final submit. The target page must be
					// resolved the way Gravity Forms resolves it, never from the raw POST field.
					if ( ! self::gravityIsFinalSubmit( $result['form'], $form_id ) ) {
						return $result;
					}
					if ( ! $m->verifySubmittedToken( 'gravity_' . $form_id ) ) {
						$result['is_valid'] = false;
						if ( ! isset( $result['form']['validation_message'] ) || $result['form']['validation_message'] === '' ) {
							$result['form']['validation_message'] = __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' );
						}
					}
					return $result;
				},
				10
			);
		}

		if ( ! empty( $s['integration_jetpack'] ) ) {
			add_filter(
				'jetpack_contact_form_html',
				static function ( $html ) use ( $m ) {
					if ( ! is_string( $html ) || $html === '' ) {
						return $html;
					}
					ob_start();
					$m->renderWidget(
						'jetpack_contact',
						$m->unlockSubmitOpts(
							'wppsGrJetpackCallback',
							'.wp-block-jetpack-contact-form button[type="submit"], .wp-block-jetpack-contact-form button.wp-block-button__link'
						)
					);
					$inject = (string) ob_get_clean();

					return self::injectBeforeSubmit( $html, $inject );
				},
				10
			);
			add_filter(
				'jetpack_contact_form_is_spam',
				static function ( $spam ) use ( $m ) {
					if ( $spam ) {
						return $spam;
					}
					return ! $m->verifySubmittedToken( 'jetpack_contact' );
				},
				10
			);
		}

		if ( ! empty( $s['integration_bbpress'] ) ) {
			add_action(
				'bbp_theme_before_topic_form_submit_wrapper',
				static function () use ( $m ) {
					$m->renderWidget( 'bbpress_topic', $m->unlockSubmitOpts( 'wppsGrBBPressTopicCallback', '#bbp_topic_submit' ) );
				}
			);
			add_action(
				'bbp_theme_before_reply_form_submit_wrapper',
				static function () use ( $m ) {
					$m->renderWidget( 'bbpress_reply', $m->unlockSubmitOpts( 'wppsGrBBPressReplyCallback', '#bbp_reply_submit' ) );
				}
			);
			add_action(
				'bbp_new_topic_pre_extras',
				static function () use ( $m ) {
					if ( 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) ) {
						return;
					}
					if ( ! $m->verifySubmittedToken( 'bbpress_topic' ) ) {
						bbp_add_error( 'bbp_recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				}
			);
			add_action(
				'bbp_new_reply_pre_extras',
				static function () use ( $m ) {
					if ( 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) ) {
						return;
					}
					if ( ! $m->verifySubmittedToken( 'bbpress_reply' ) ) {
						bbp_add_error( 'bbp_recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				}
			);
		}

		if ( ! empty( $s['integration_buddypress'] ) && class_exists( 'BuddyPress' ) ) {
			add_action(
				'bp_before_registration_submit_buttons',
				static function () use ( $m ) {
					$m->renderWidget( 'buddypress_register', $m->unlockSubmitOpts( 'wppsGrBuddyPressCallback', '#buddypress #signup-form .submit' ) );
				}
			);
			add_action(
				'bp_signup_validate',
				static function () use ( $m ) {
					if ( 'POST' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) ) {
						return;
					}
					if ( ! $m->verifySubmittedToken( 'buddypress_register' ) ) {
						wp_die( esc_html__( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ), '', [ 'response' => 403, 'back_link' => true ] );
					}
				}
			);
		}

		if ( ! empty( $s['integration_ultimate_member'] ) && class_exists( 'UM' ) ) {
			add_action(
				'um_after_login_fields',
				static function () use ( $m ) {
					$m->renderWidget( 'ultimate_member_login', $m->unlockSubmitOpts( 'wppsGrUMCallback', '#um-submit-btn' ) );
				}
			);
			add_action(
				'um_after_register_fields',
				static function () use ( $m ) {
					$m->renderWidget( 'ultimate_member_register', $m->unlockSubmitOpts( 'wppsGrUMCallback', '#um-submit-btn' ) );
				}
			);
			add_action(
				'um_after_password_reset_fields',
				static function () use ( $m ) {
					$m->renderWidget( 'ultimate_member_reset', $m->unlockSubmitOpts( 'wppsGrUMCallback', '#um-submit-btn' ) );
				}
			);
			add_action(
				'um_submit_form_errors_hook_login',
				static function () use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'ultimate_member_login' ) ) {
						UM()->form()->add_error( 'recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				},
				20
			);
			add_action(
				'um_submit_form_errors_hook__registration',
				static function () use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'ultimate_member_register' ) ) {
						UM()->form()->add_error( 'recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				},
				20
			);
			add_action(
				'um_reset_password_errors_hook',
				static function () use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'ultimate_member_reset' ) ) {
						UM()->form()->add_error( 'recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				},
				20
			);
		}

		if ( ! empty( $s['integration_memberpress'] ) && class_exists( 'MeprAppCtrl' ) ) {
			add_action(
				'mepr-login-form-before-submit',
				static function () use ( $m ) {
					$m->renderWidget( 'memberpress_login', $m->unlockSubmitOpts( 'wppsGrMEPRCallback', '.mepr-submit' ) );
				}
			);
			add_action(
				'mepr-checkout-before-submit',
				static function () use ( $m ) {
					$m->renderWidget( 'memberpress_checkout', $m->unlockSubmitOpts( 'wppsGrMEPRCallback', '.mepr-submit' ) );
				}
			);
			add_filter(
				'mepr-validate-login',
				static function ( $errors ) use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'memberpress_login' ) ) {
						$errors[] = __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' );
					}
					return $errors;
				}
			);
			add_filter(
				'mepr-validate-signup',
				static function ( $errors ) use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'memberpress_checkout' ) ) {
						$errors[] = __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' );
					}
					return $errors;
				},
				20
			);
		}

		if ( ! empty( $s['integration_edd'] ) && function_exists( 'EDD' ) ) {
			add_action( 'edd_login_fields_after', static fn () => $m->renderWidget( 'edd_login', $m->unlockSubmitOpts( 'wppsGrEDDLoginCallback', '#edd_login_submit' ) ) );
			add_action( 'edd_register_form_fields_before_submit', static fn () => $m->renderWidget( 'edd_register', $m->unlockSubmitOpts( 'wppsGrEDDRegisterCallback', '#edd_register_form .edd-submit' ) ) );
			add_action( 'edd_purchase_form_before_submit', static fn () => $m->renderWidget( 'edd_checkout', $m->unlockSubmitOpts( 'wppsGrEDDCheckoutCallback', '.edd-submit' ) ) );
			add_filter(
				'authenticate',
				static function ( $user ) use ( $m ) {
					if ( isset( $_POST['edd_login_nonce'] ) && function_exists( 'edd_is_checkout' ) && ! edd_is_checkout() ) {
						if ( ! $m->verifySubmittedToken( 'edd_login' ) ) {
							return new \WP_Error( 'recaptcha_failed', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
						}
					}
					return $user;
				},
				21,
				1
			);
			add_action(
				'edd_process_register_form',
				static function () use ( $m ) {
					if ( function_exists( 'edd_is_checkout' ) && edd_is_checkout() ) {
						return;
					}
					if ( ! $m->verifySubmittedToken( 'edd_register' ) ) {
						edd_set_error( 'recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				}
			);
			add_action(
				'edd_pre_process_purchase',
				static function () use ( $m ) {
					if ( ! isset( $_POST['edd-process-checkout-nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['edd-process-checkout-nonce'] ) ), 'edd-process-checkout' ) ) {
						return;
					}
					if ( ! $m->verifySubmittedToken( 'edd_checkout' ) ) {
						edd_set_error( 'recaptcha', __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ) );
					}
				}
			);
		}

		if ( ! empty( $s['integration_pmp'] ) && defined( 'PMPRO_VERSION' ) ) {
			add_action( 'pmpro_checkout_before_submit_button', static fn () => $m->renderWidget( 'pmp', $m->unlockSubmitOpts( 'wppsGrPmpCallback', '.pmpro_btn-submit' ) ) );
			add_action(
				'pmpro_checkout_after_parameters_set',
				static function () use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'pmp' ) ) {
						pmpro_setMessage( __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' ), 'pmpro_error' );
					}
				}
			);
		}

		if ( ! empty( $s['integration_mc4wp'] ) && defined( 'MC4WP_VERSION' ) ) {
			add_filter(
				'mc4wp_form_content',
				static function ( $content ) use ( $m ) {
					if ( ! is_string( $content ) || $content === '' ) {
						return $content;
					}
					ob_start();
					$m->renderWidget( 'mc4wp', $m->unlockSubmitOpts( 'wppsGrMC4WPCallback', '.mc4wp-form-fields input[type=submit]' ) );
					$inject = (string) ob_get_clean();

					return self::injectBeforeSubmit( $content, $inject );
				},
				40
			);
			add_filter(
				'mc4wp_form_errors',
				static function ( $errors ) use ( $m ) {
					if ( ! $m->verifySubmittedToken( 'mc4wp' ) ) {
						$errors[] = __( 'Please complete the reCAPTCHA verification.', 'wp-powersuite' );
					}
					return $errors;
				},
				10,
				2
			);
		}
	}

	/**
	 * Whether this Gravity Forms request is the submit that saves the entry.
	 *
	 * Must mirror Gravity Forms' own resolution rather than reading
	 * `gform_target_page_number_{id}` directly: {@see \GFFormDisplay::get_target_page()}
	 * clamps any page number above the form's last page down to 0, which Gravity Forms
	 * treats as a final submit. Trusting the raw field therefore let a request post an
	 * out-of-range page number to skip verification while the entry was still saved.
	 *
	 * Fails closed (verify) whenever Gravity Forms' internals are unavailable.
	 *
	 * @param array<string, mixed> $form    Form object from the validation result.
	 * @param int                  $form_id Form ID.
	 */
	private static function gravityIsFinalSubmit( array $form, int $form_id ): bool {
		if (
			$form_id === 0
			|| ! class_exists( '\GFFormDisplay' )
			|| ! method_exists( '\GFFormDisplay', 'get_target_page' )
			|| ! method_exists( '\GFFormDisplay', 'get_source_page' )
		) {
			return true;
		}

		$source       = (int) \GFFormDisplay::get_source_page( $form_id );
		$field_values = isset( $_POST['gform_field_values'] ) ? wp_unslash( $_POST['gform_field_values'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Gravity Forms owns nonce handling for its own submission.

		return (int) \GFFormDisplay::get_target_page( $form, $source, $field_values ) === 0;
	}

	/**
	 * Insert reCAPTCHA markup immediately before the first submit control.
	 */
	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 perf_compat(): void {
		add_filter(
			'sgo_javascript_combine_exclude',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'wpps-google-recaptcha';
				$list[] = 'google-recaptcha';
				return $list;
			}
		);
		add_filter(
			'sgo_javascript_combine_excluded_external_paths',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'google.com/recaptcha';
				$list[] = 'gstatic.com/recaptcha';
				return $list;
			}
		);
		add_filter(
			'litespeed_optimize_js_excludes',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'recaptcha/api.js';
				$list[] = 'wpps-google-recaptcha';
				return $list;
			}
		);
		add_filter(
			'autoptimize_filter_js_exclude',
			static function ( $exclude ) {
				$p = 'google.com/recaptcha,gstatic.com/recaptcha,wpps-google-recaptcha';
				return $exclude ? $exclude . ',' . $p : $p;
			}
		);
		add_filter(
			'perfmatters_delay_js_exclusions',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'google.com/recaptcha/api.js';
				return $list;
			}
		);
		add_filter(
			'rocket_exclude_js',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'recaptcha';
				$list[] = 'wpps-google-recaptcha';
				return $list;
			}
		);
		add_filter(
			'rocket_delay_js_exclusions',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'recaptcha';
				return $list;
			}
		);
		add_filter(
			'rocket_minify_excluded_external_js',
			static function ( $list ) {
				$list   = is_array( $list ) ? $list : [];
				$list[] = 'google.com/recaptcha';
				$list[] = 'gstatic.com/recaptcha';
				return $list;
			}
		);
	}

	/**
	 * @param array<int, string|array<string, string>> $urls URLs.
	 * @return array<int, string|array<string, string>>
	 */
	public static function resource_hints( array $urls, string $relation_type ): array {
		if ( is_admin() ) {
			return $urls;
		}
		$origins = [ 'https://www.google.com', 'https://www.gstatic.com' ];
		if ( 'preconnect' === $relation_type ) {
			foreach ( $origins as $origin ) {
				$exists = false;
				foreach ( $urls as $hint ) {
					$href = is_array( $hint ) ? ( $hint['href'] ?? '' ) : $hint;
					if ( str_starts_with( (string) $href, $origin ) ) {
						$exists = true;
						break;
					}
				}
				if ( ! $exists ) {
					$urls[] = [ 'href' => $origin, 'crossorigin' => true ];
				}
			}
		}
		if ( 'dns-prefetch' === $relation_type ) {
			foreach ( $origins as $origin ) {
				if ( ! in_array( $origin, $urls, true ) ) {
					$urls[] = $origin;
				}
			}
		}
		return $urls;
	}

	public static function login_preconnect(): void {
		echo '<link rel="preconnect" href="' . esc_url( 'https://www.google.com' ) . '" crossorigin>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- safe URL.
		echo '<link rel="preconnect" href="' . esc_url( 'https://www.gstatic.com' ) . '" crossorigin>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- safe URL.
	}
}
