<?php
/**
 * Shared JS-optimizer compatibility for captcha scripts.
 *
 * Page optimizers that combine, minify, defer, or delay JavaScript routinely
 * break captcha widgets: the provider script must run under its original URL
 * and timing or the widget never renders / never produces a token, and the
 * visitor is left with a verification error they cannot act on. Every major
 * captcha plugin ships these exclusions; modules opt in by calling
 * {@see self::protectScripts()} when they enqueue their provider script.
 *
 * Covers WP Rocket, LiteSpeed Cache, Autoptimize, SiteGround Optimizer and
 * Perfmatters. The filters are only registered once, no matter how many
 * captcha modules opt in, and are inert when no optimizer plugin is active
 * (unused filters cost nothing).
 *
 * @package WP_PowerSuite\Captcha\Shared
 * @since 1.1.0
 */

namespace WP_PowerSuite\Captcha\Shared;

final class OptimizerCompat {

	/** @var list<string> Substring patterns matched against script URLs/handles by the optimizers. */
	private static array $patterns = [];

	private static bool $hooked = false;

	/**
	 * Exclude scripts matching the given patterns from JS optimization.
	 *
	 * @param list<string> $patterns Substring patterns (URL fragments or handle names).
	 */
	public static function protectScripts( array $patterns ): void {
		self::$patterns = array_values( array_unique( array_merge( self::$patterns, $patterns ) ) );

		if ( self::$hooked ) {
			return;
		}
		self::$hooked = true;

		$append_to_list = static function ( $list ) {
			if ( ! is_array( $list ) ) {
				return $list;
			}

			return array_values( array_unique( array_merge( $list, self::$patterns ) ) );
		};

		$list_filters = [
			// SiteGround Optimizer.
			'sgo_javascript_combine_exclude',
			'sgo_javascript_combine_excluded_external_paths',
			// LiteSpeed Cache.
			'litespeed_optimize_js_excludes',
			// Perfmatters (Delay JS).
			'perfmatters_delay_js_exclusions',
			// WP Rocket (minify / combine / defer / delay).
			'rocket_minify_excluded_external_js',
			'rocket_minify_excluded_js',
			'rocket_delay_js_exclusions',
			'rocket_exclude_js',
			'rocket_defer_js_exclusions',
		];
		foreach ( $list_filters as $filter ) {
			add_filter( $filter, $append_to_list );
		}

		// Autoptimize passes its exclusions as a comma-separated string.
		add_filter(
			'autoptimize_filter_js_exclude',
			static function ( $exclude ) {
				$exclude = is_string( $exclude ) ? $exclude : '';
				foreach ( self::$patterns as $pattern ) {
					if ( ! str_contains( $exclude, $pattern ) ) {
						$exclude .= ( $exclude !== '' ? ',' : '' ) . $pattern;
					}
				}

				return $exclude;
			}
		);
	}
}
