<?php
/**
 * Safe regex validation and matching for redirect rules.
 *
 * @package WP_PowerSuite\Core
 * @since 1.1.0
 */

namespace WP_PowerSuite\Core;

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

/**
 * Guards against invalid or expensive PCRE patterns.
 */
final class RegexGuard {

	private const MAX_PATTERN_LENGTH = 500;

	/**
	 * Whether a redirect regex pattern is syntactically valid and safe enough to run.
	 */
	public static function isSafePattern( string $pattern ): bool {
		if ( $pattern === '' || strlen( $pattern ) > self::MAX_PATTERN_LENGTH ) {
			return false;
		}

		if ( @preg_match( $pattern, '' ) === false ) {
			return false;
		}

		if ( self::hasNestedQuantifiers( $pattern ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Run preg_match when the pattern passes safety checks.
	 *
	 * @param array<int|string, string>|null $matches
	 */
	public static function match( string $pattern, string $subject, ?array &$matches = null ): int|false {
		if ( ! self::isSafePattern( $pattern ) ) {
			return false;
		}

		$local = [];
		$result = @preg_match( $pattern, $subject, $local );
		if ( $matches !== null ) {
			$matches = $local;
		}

		return $result;
	}

	/**
	 * Detect common catastrophic backtracking shapes.
	 */
	private static function hasNestedQuantifiers( string $pattern ): bool {
		return (bool) preg_match(
			'/(\([^)]*[+*][^)]*\)[+*?]|[+*]\s*[+*]|\(\.\*\)[+*?]|\(\.\+)[+*?]/',
			$pattern
		);
	}
}
