<?php
/**
 * Validation Engine for UltraGuard Pro Security Suite
 *
 * This file contains the ValidationEngine class which provides a comprehensive,
 * high-performance data validation system with enhanced security features.
 * The engine supports multiple validation rules, custom validators, caching,
 * and extensibility through WordPress hooks and filters.
 *
 * @package    UltraguardPro\core
 * @author     UltraGuard Pro Team <support@ultraguardpro.com>
 * @copyright  2024 UltraGuard Pro Security Solutions
 * @license    GPL-3.0-or-later
 * @version    1.0.0
 * @link       https://ultraguardpro.com/docs/validation
 */

namespace UltraguardPro\core;

defined( 'ABSPATH' ) || exit;

/**
 * Main Validation Engine class
 *
 * Provides a robust, extensible validation system with caching, performance
 * optimization, and comprehensive error handling. Implements the Singleton
 * pattern to ensure a single validation instance throughout the application.
 *
 * The engine supports built-in validation rules (email, URL, numeric ranges,
 * patterns, etc.), custom validators, recursive validation with depth control,
 * and both strict and lenient validation modes. Includes comprehensive
 * caching mechanisms to optimize performance for repeated validations.
 *
 * @package    UltraguardPro\view
 * @author     UltraGuard Pro Team
 * @version    1.0.0
 * @since      1.0.0
 * @see        \UltraguardPro\core\SanitizationEngine
 * @see        \UltraguardPro\core\SanitizationEngine
 *
 * @example
 * $engine = ValidationEngine::get_instance();
 * $data = ['email' => 'user@example.com', 'age' => 25];
 * $rules = ['email' => 'email', 'age' => ['number' => ['min' => 18, 'max' => 100]]];
 *
 * if ($engine->validate($data, $rules)) {
 *     // Data is valid
 * } else {
 *     $errors = $engine->get_errors();
 * }
 */
class ValidationEngine {

    /**
     * Singleton instance of the validation engine
     *
     * @var ValidationEngine|null
     * @access private
     * @static
     */
    private static $instance = null;

    /**
     * Collection of validation errors keyed by field name
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $errors = array();

    /**
     * Registry of custom validation callbacks
     *
     * @var array<string, callable>
     * @access private
     * @static
     */
    private static $validators = array();

    /**
     * Default validation rule configurations
     *
     * @var array<string, array>
     * @access private
     * @static
     */
    private static $default_rules = array();

    /**
     * Cache for validation results and intermediate data
     *
     * @var array<string, mixed>
     * @access private
     * @static
     */
    private static $validation_cache = array();

    /**
     * Precompiled regular expression patterns
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $patterns = array();

    /**
     * Cached error messages for validation rules
     *
     * @var array<string, string>
     * @access private
     * @static
     */
    private static $error_messages = array();

    /**
     * Default validation context parameters
     *
     * Defines the baseline configuration for validation operations including
     * recursion limits, bail behavior, and strictness mode.
     *
     * @var array<string, mixed>
     * @access private
     */
    private const DEFAULT_CONTEXT = array(
        'strict_mode'   => false,
        'bail'          => true,
        'recursive'     => true,
        'max_depth'     => 10,
        'current_depth' => 0,
        'field_prefix'  => '',
    );

    /**
     * Get the singleton instance of the validation engine
     *
     * Ensures only one instance of the ValidationEngine exists throughout
     * the application lifecycle. Initializes patterns and validators on
     * first instantiation.
     *
     * @return ValidationEngine  Singleton instance
     * @access public
     * @static
     *
     * @example
     * $engine = ValidationEngine::get_instance();
     */
    public static function get_instance(): self {
        if ( null === self::$instance ) {
            self::$instance = new self();
            self::init_patterns();
            self::init_default_validators();
        }
        return self::$instance;
    }

    /**
     * ValidationEngine constructor
     *
     * Private constructor to enforce Singleton pattern. Initializes default
     * validation rules if not already loaded.
     *
     * @return void
     * @access private
     */
    private function __construct() {
        if ( empty( self::$default_rules ) ) {
            self::init_default_rules();
        }
    }

    /**
     * Initialize regular expression patterns for validation
     *
     * Precompiles and caches common regex patterns to optimize pattern-based
     * validation operations. Patterns include alpha, alphanumeric, hexadecimal,
     * and CIDR notation validation.
     *
     * @return void
     * @access private
     * @static
     */
    private static function init_patterns(): void {
        if ( empty( self::$patterns ) ) {
            self::$patterns = array(
                'alpha'         => '/^[a-zA-Z]+$/',
                'alphanumeric'  => '/^[a-zA-Z0-9]+$/',
                'hex'           => '/^[a-fA-F0-9]+$/',
                'cidr'          => '/^([^\/]+)\/(\d+)$/',
            );
        }
    }

    /**
     * Initialize built-in validation callbacks
     *
     * Registers all default validation functions including email, URL, numeric,
     * string length, pattern matching, and specialized validators. Applies
     * WordPress filters to allow extension/modification of validators.
     *
     * @return void
     * @access private
     * @static
     */
    private static function init_default_validators(): void {
        if ( ! empty( self::$validators ) ) {
            return;
        }

        // Use the instance that was just created
        $instance = self::$instance;

        self::$validators = array(
            'required'        => function($value, $args, $context) use ($instance) {
                return $instance->validate_required($value, $args, $context);
            },
            'email'           => function($value, $args, $context) use ($instance) {
                return $instance->validate_email($value, $args, $context);
            },
            'url'             => function($value, $args, $context) use ($instance) {
                return $instance->validate_url($value, $args, $context);
            },
            'number'          => function($value, $args, $context) use ($instance) {
                return $instance->validate_number($value, $args, $context);
            },
            'min'             => function($value, $min, $context) use ($instance) {
                return $instance->validate_min($value, $min, $context);
            },
            'max'             => function($value, $max, $context) use ($instance) {
                return $instance->validate_max($value, $max, $context);
            },
            'min_length'      => function($value, $min, $context) use ($instance) {
                return $instance->validate_min_length($value, $min, $context);
            },
            'max_length'      => function($value, $max, $context) use ($instance) {
                return $instance->validate_max_length($value, $max, $context);
            },
            'pattern'         => function($value, $pattern, $context) use ($instance) {
                return $instance->validate_pattern($value, $pattern, $context);
            },
            'in'              => function($value, $options, $context) use ($instance) {
                return $instance->validate_in($value, $options, $context);
            },
            'ip_address'      => function($value, $args, $context) use ($instance) {
                return $instance->validate_ip_address($value, $args, $context);
            },
            'alpha'           => function($value, $args, $context) use ($instance) {
                return $instance->validate_alpha($value, $args, $context);
            },
            'alphanumeric'    => function($value, $args, $context) use ($instance) {
                return $instance->validate_alphanumeric($value, $args, $context);
            },
            'hex'             => function($value, $args, $context) use ($instance) {
                return $instance->validate_hex($value, $args, $context);
            },
            'bool'            => function($value, $args, $context) use ($instance) {
                return $instance->validate_bool($value, $args, $context);
            },
            'json'            => function($value, $args, $context) use ($instance) {
                return $instance->validate_json($value, $args, $context);
            },
            'date'            => function($value, $args, $context) use ($instance) {
                return $instance->validate_date($value, $args, $context);
            },
            'time'            => function($value, $args, $context) use ($instance) {
                return $instance->validate_time($value, $args, $context);
            },
            'datetime'        => function($value, $args, $context) use ($instance) {
                return $instance->validate_datetime($value, $args, $context);
            },
            'file_extension'  => function($value, $args, $context) use ($instance) {
                return $instance->validate_file_extension($value, $args, $context);
            },
            'mime_type'       => function($value, $args, $context) use ($instance) {
                return $instance->validate_mime_type($value, $args, $context);
            },
        );

        // Apply filters with caching
        $cache_key = 'validators_filtered';
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validators = apply_filters( 'ultraguard_validation_engine_validators', self::$validators );
            self::$validation_cache[ $cache_key ] = true;
        }
    }

    /**
     * Initialize default validation rule configurations
     *
     * Sets up predefined rule sets for common data types like strings,
     * emails, URLs, and numbers. These defaults provide sensible validation
     * constraints that can be overridden or extended.
     *
     * @return void
     * @access private
     * @static
     */
    private static function init_default_rules(): void {
        if ( ! empty( self::$default_rules ) ) {
            return;
        }

        self::$default_rules = array(
            'string' => array( 'min_length' => 0, 'max_length' => 255 ),
            'email'  => array( 'email' => true ),
            'url'    => array( 'url' => true ),
            'number' => array( 'number' => array( 'min' => 0, 'max' => 999999 ) ),
        );

        // Apply filters with caching
        $cache_key = 'default_rules_filtered';
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$default_rules = apply_filters( 'ultraguard_validation_engine_default_rules', self::$default_rules );
            self::$validation_cache[ $cache_key ] = true;
        }
    }

    /**
     * Validate data against specified rules with context
     *
     * Main validation method that processes data arrays according to validation
     * rules. Supports caching, recursion depth control, early bail on failure,
     * and hooks for pre/post validation processing.
     *
     * @param  array<string, mixed> $data     Data to validate (associative array)
     * @param  array<string, mixed> $rules    Validation rules for each field
     * @param  array<string, mixed> $context  Validation context and options
     * @return bool                           True if all validation passes, false otherwise
     * @access public
     *
     * @example
     * $data = ['username' => 'john_doe', 'email' => 'john@example.com'];
     * $rules = ['username' => ['required', 'min_length' => 3], 'email' => 'email'];
     * $context = ['strict_mode' => true, 'bail' => false];
     *
     * $isValid = $engine->validate($data, $rules, $context);
     */
    public function validate( array $data, array $rules, array $context = array() ): bool {
        // Early return for empty data
        if ( empty( $data ) && empty( $rules ) ) {
            return true;
        }

        // Cache key for this validation operation
        $cache_key = $this->generate_cache_key( $data, $rules, $context );

        if ( isset( self::$validation_cache[ $cache_key ] ) ) {
            return self::$validation_cache[ $cache_key ];
        }

        // Clear previous errors
        self::$errors = array();

        // Prepare context
        $context = $this->prepare_context( $context );

        // Check recursion depth
        if ( $context['current_depth'] >= $context['max_depth'] ) {
            $this->add_error( 'system', __( 'Maximum validation recursion depth exceeded', 'ultraguard-security-suite' ) );
            self::$validation_cache[ $cache_key ] = false;
            return false;
        }

        // Pre-validation hook with caching
        $hook_key = 'pre_validate_' . $cache_key;
        if ( ! isset( self::$validation_cache[ $hook_key ] ) ) {
            do_action( 'ultraguard_validation_engine_pre_validate', $data, $rules, $context );
            self::$validation_cache[ $hook_key ] = true;
        }

        // Validate fields
        foreach ( $rules as $field => $field_rules ) {
            $sanitized_field = sanitize_key( $field );
            $value = $data[ $sanitized_field ] ?? null;
            $field_rules = $this->get_field_rules( $sanitized_field, $field_rules, $context );

            $this->validate_field( $sanitized_field, $value, $field_rules, $context );

            // Bail early if configured
            if ( $context['bail'] && isset( self::$errors[ $sanitized_field ] ) ) {
                break;
            }
        }

        $validation_result = empty( self::$errors );

        // Post-validation hook with caching
        $hook_key = 'post_validate_' . $cache_key;
        if ( ! isset( self::$validation_cache[ $hook_key ] ) ) {
            do_action(
                'ultraguard_validation_engine_post_validate',
                $validation_result,
                $data,
                $rules,
                $context,
                self::$errors
            );
            self::$validation_cache[ $hook_key ] = true;
        }

        self::$validation_cache[ $cache_key ] = $validation_result;
        return $validation_result;
    }

    /**
     * Prepare validation context with default values and sanitization
     *
     * Merges provided context with defaults, ensures proper type casting,
     * and sanitizes all context values for security and consistency.
     *
     * @param  array<string, mixed> $context  User-provided context data
     * @return array<string, mixed>           Prepared and sanitized context
     * @access private
     */
    private function prepare_context( array $context ): array {
        $context = wp_parse_args( $context, self::DEFAULT_CONTEXT );

        // Type casting with validation
        $context['strict_mode']   = wp_validate_boolean( $context['strict_mode'] );
        $context['bail']          = wp_validate_boolean( $context['bail'] );
        $context['recursive']     = wp_validate_boolean( $context['recursive'] );
        $context['max_depth']     = absint( $context['max_depth'] );
        $context['current_depth'] = absint( $context['current_depth'] );
        $context['field_prefix']  = sanitize_text_field( $context['field_prefix'] );

        return $context;
    }

    /**
     * Retrieve and cache field-specific validation rules
     *
     * Processes field rules, ensures proper array format, and applies
     * filters to allow dynamic rule modification. Caches results for
     * performance optimization.
     *
     * @param  string               $field        Field name to retrieve rules for
     * @param  mixed                $field_rules  Raw field rules (array or single rule)
     * @param  array<string, mixed> $context      Validation context
     * @return array<string, mixed>               Processed field validation rules
     * @access private
     */
    private function get_field_rules( string $field, $field_rules, array $context ): array {
        // Ensure rules is an array
        if ( ! is_array( $field_rules ) ) {
            $field_rules = array( $field_rules => true );
        }

        // Cache filter result
        $cache_key = 'field_rules_' . $field . '_' . md5( serialize( $context ) );
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validation_cache[ $cache_key ] = apply_filters(
                'ultraguard_validation_engine_field_rules',
                $field_rules,
                $field,
                $context
            );
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate a single field against multiple rules
     *
     * Iterates through all validation rules for a field, applying each
     * validator. Supports early termination when bail mode is enabled
     * and a validation fails.
     *
     * @param  string               $field    Field name being validated
     * @param  mixed                $value    Field value to validate
     * @param  array<string, mixed> $rules    Validation rules for the field
     * @param  array<string, mixed> $context  Validation context
     * @return void
     * @access private
     */
    private function validate_field( string $field, $value, array $rules, array $context ): void {
        foreach ( $rules as $rule => $rule_value ) {
            if ( ! $this->validate_rule( $field, $value, $rule, $rule_value, $context ) ) {
                if ( $context['bail'] ) {
                    break;
                }
            }
        }
    }

    /**
     * Validate a single rule against a field value
     *
     * Core validation logic that dispatches to appropriate validator
     * functions. Handles rule parameter parsing, caching, error reporting,
     * and exception handling for validator callbacks.
     *
     * @param  string               $field      Field name being validated
     * @param  mixed                $value      Field value to validate
     * @param  string               $rule       Rule name identifier
     * @param  mixed                $rule_value Rule parameters or configuration
     * @param  array<string, mixed> $context    Validation context
     * @return bool                             True if rule validation passes, false otherwise
     * @access private
     */
    private function validate_rule( string $field, $value, string $rule, $rule_value, array $context ): bool {
        // Skip validation for empty values unless required
        if ( $rule !== 'required' && $this->is_value_empty( $value ) ) {
            return true;
        }

        // Handle rule with parameters
        $rule_name = is_array( $rule_value ) && isset( $rule_value['rule'] )
            ? $rule_value['rule']
            : $rule;

        $rule_args = is_array( $rule_value ) && isset( $rule_value['args'] )
            ? $rule_value['args']
            : $rule_value;

        $rule_name = sanitize_key( $rule_name );

        // Apply validator
        if ( isset( self::$validators[ $rule_name ] ) ) {
            $cache_key = $this->generate_rule_cache_key( $field, $value, $rule_name, $rule_args, $context );

            if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
                try {
                    $result = call_user_func( self::$validators[ $rule_name ], $value, $rule_args, $context );

                    if ( $result !== true ) {
                        $error_message = is_string( $result )
                            ? $result
                            : $this->get_default_error_message( $rule_name, $rule_args );

                        $this->add_error( $field, $error_message );
                        self::$validation_cache[ $cache_key ] = false;
                    } else {
                        self::$validation_cache[ $cache_key ] = true;
                    }
                } catch ( \Exception $e ) {
                    $error_message = sprintf(
                        /* translators: %1$s: field name, %2$s: validation error message. */
                        __( 'Validation error for %1$s: %2$s', 'ultraguard-security-suite' ),
                        $field,
                        $e->getMessage()
                    );

                    $this->add_error( $field, $error_message );

                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                        \UltraguardPro\core\Logger::error( 'Validation error: ' . $e->getMessage() );
                    }

                    self::$validation_cache[ $cache_key ] = false;
                }
            }

            return self::$validation_cache[ $cache_key ];
        }

        // Unknown rule
        if ( $context['strict_mode'] ) {
            /* translators: %s: string */
            $error_message = sprintf( __( 'Unknown validation rule: %s', 'ultraguard-security-suite' ), $rule_name );
            $this->add_error( $field, $error_message );
            return false;
        }

        return true;
    }

    /**
     * Determine if a value is considered empty for validation purposes
     *
     * Checks various value types (null, arrays, strings, other) to determine
     * if they should be considered empty. More comprehensive than PHP's
     * empty() function for validation contexts.
     *
     * @param  mixed $value  Value to check for emptiness
     * @return bool          True if value is empty, false otherwise
     * @access private
     */
    private function is_value_empty( $value ): bool {
        if ( is_null( $value ) ) {
            return true;
        }

        if ( is_array( $value ) ) {
            return empty( $value );
        }

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

        return empty( $value );
    }

    /**
     * Retrieve or generate default error message for a validation rule
     *
     * Provides localized default error messages for built-in validation
     * rules. Caches messages for performance and allows customization
     * through WordPress filters.
     *
     * @param  string $rule  Validation rule name
     * @param  mixed  $args  Rule arguments (used for message formatting)
     * @return string        Localized error message
     * @access private
     */
    private function get_default_error_message( string $rule, $args ): string {
        $cache_key = 'error_msg_' . $rule . '_' . md5( serialize( $args ) );

        if ( isset( self::$error_messages[ $cache_key ] ) ) {
            return self::$error_messages[ $cache_key ];
        }

        $messages = array(
            'required'      => __( 'This field is required', 'ultraguard-security-suite' ),
            'email'         => __( 'Please enter a valid email address', 'ultraguard-security-suite' ),
            'url'           => __( 'Please enter a valid URL', 'ultraguard-security-suite' ),
            'number'        => __( 'Please enter a valid number', 'ultraguard-security-suite' ),
            /* translators: %s: string */
            'min'           => sprintf( __( 'Value must be at least %s', 'ultraguard-security-suite' ), esc_html( $args ) ),
            /* translators: %s: string */
            'max'           => sprintf( __( 'Value must be at most %s', 'ultraguard-security-suite' ), esc_html( $args ) ),
            /* translators: %d: integer */
            'min_length'    => sprintf( __( 'Must be at least %d characters', 'ultraguard-security-suite' ), absint( $args ) ),
            /* translators: %d: integer */
            'max_length'    => sprintf( __( 'Must be at most %d characters', 'ultraguard-security-suite' ), absint( $args ) ),
            'pattern'       => __( 'Invalid format', 'ultraguard-security-suite' ),
            'in'            => __( 'Invalid value selected', 'ultraguard-security-suite' ),
            'alpha'         => __( 'Only letters are allowed', 'ultraguard-security-suite' ),
            'alphanumeric'  => __( 'Only letters and numbers are allowed', 'ultraguard-security-suite' ),
            'hex'           => __( 'Only hexadecimal characters are allowed', 'ultraguard-security-suite' ),
            'json'          => __( 'Invalid JSON format', 'ultraguard-security-suite' ),
            'date'          => __( 'Invalid date format', 'ultraguard-security-suite' ),
            'time'          => __( 'Invalid time format', 'ultraguard-security-suite' ),
            'datetime'      => __( 'Invalid date/time format', 'ultraguard-security-suite' ),
        );

        $message = $messages[ $rule ] ?? __( 'Validation failed', 'ultraguard-security-suite' );
        self::$error_messages[ $cache_key ] = $message;

        return $message;
    }

    // =========================================================================
    // VALIDATOR METHODS (INSTANCE METHODS)
    // =========================================================================

    /**
     * Validate that a value is present and not empty
     *
     * Checks if the provided value is not considered empty according to
     * the validation engine's emptiness criteria.
     *
     * @param  mixed                $value    Value to check for presence
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if value is not empty, false otherwise
     * @access protected
     */
    protected function validate_required( $value, $args = array(), array $context = array() ): bool {
        return ! $this->is_value_empty( $value );
    }

    /**
     * Validate email address format
     *
     * Uses WordPress's built-in is_email() function to validate email
     * addresses according to RFC standards.
     *
     * @param  mixed                $value    Value to validate as email
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid email format, false otherwise
     * @access protected
     */
    protected function validate_email( $value, $args = array(), array $context = array() ): bool {
        return is_string( $value ) && is_email( $value ) !== false;
    }

    /**
     * Validate URL format
     *
     * Uses PHP's filter_var() with FILTER_VALIDATE_URL to validate URLs.
     * Supports http, https, ftp, and other standard URL schemes.
     *
     * @param  mixed                $value    Value to validate as URL
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid URL format, false otherwise
     * @access protected
     */
    protected function validate_url( $value, $args = array(), array $context = array() ): bool {
        return is_string( $value ) && filter_var( $value, FILTER_VALIDATE_URL ) !== false;
    }

    /**
     * Validate numeric value with optional range constraints
     *
     * Checks if value is numeric and optionally validates against minimum
     * and maximum bounds. Supports both integers and floating-point numbers.
     *
     * @param  mixed                $value    Value to validate as number
     * @param  array<string, mixed> $args     Validation arguments with 'min' and 'max' keys
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid number within range, false otherwise
     * @access protected
     */
    protected function validate_number( $value, $args = array(), array $context = array() ) {
        if ( ! is_numeric( $value ) ) {
            return false;
        }

        $args = wp_parse_args( $args, array( 'min' => null, 'max' => null ) );
        $float_value = (float) $value;

        if ( $args['min'] !== null && $float_value < (float) $args['min'] ) {
            return false;
        }

        if ( $args['max'] !== null && $float_value > (float) $args['max'] ) {
            return false;
        }

        return true;
    }

    /**
     * Validate numeric minimum value constraint
     *
     * Ensures a numeric value is greater than or equal to the specified minimum.
     *
     * @param  mixed                $value    Numeric value to validate
     * @param  int|float            $min      Minimum allowed value
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if value >= min, false otherwise
     * @access protected
     */
    protected function validate_min( $value, $min, array $context = array() ): bool {
        return is_numeric( $value ) && (float) $value >= (float) $min;
    }

    /**
     * Validate numeric maximum value constraint
     *
     * Ensures a numeric value is less than or equal to the specified maximum.
     *
     * @param  mixed                $value    Numeric value to validate
     * @param  int|float            $max      Maximum allowed value
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if value <= max, false otherwise
     * @access protected
     */
    protected function validate_max( $value, $max, array $context = array() ): bool {
        return is_numeric( $value ) && (float) $value <= (float) $max;
    }

    /**
     * Validate minimum string length constraint
     *
     * Ensures a string (or stringable value) has at least the specified
     * number of characters.
     *
     * @param  mixed                $value    Value to check length of
     * @param  int                  $min      Minimum allowed length
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if length >= min, false otherwise
     * @access protected
     */
    protected function validate_min_length( $value, $min, array $context = array() ): bool {
        return is_scalar( $value ) && strlen( (string) $value ) >= absint( $min );
    }

    /**
     * Validate maximum string length constraint
     *
     * Ensures a string (or stringable value) has at most the specified
     * number of characters.
     *
     * @param  mixed                $value    Value to check length of
     * @param  int                  $max      Maximum allowed length
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if length <= max, false otherwise
     * @access protected
     */
    protected function validate_max_length( $value, $max, array $context = array() ): bool {
        return is_scalar( $value ) && strlen( (string) $value ) <= absint( $max );
    }

    /**
     * Validate value against regular expression pattern
     *
     * Performs regex pattern matching using preg_match(). Caches pattern
     * validation results for performance optimization with repeated values.
     *
     * @param  mixed                $value     String value to match against pattern
     * @param  string               $pattern   Regular expression pattern
     * @param  array<string, mixed> $context   Validation context
     * @return bool                            True if pattern matches, false otherwise
     * @access protected
     */
    protected function validate_pattern( $value, $pattern, array $context = array() ): bool {
        if ( ! is_string( $value ) || ! is_string( $pattern ) ) {
            return false;
        }

        // Cache pattern validation
        $cache_key = 'pattern_' . md5( $pattern . $value );
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validation_cache[ $cache_key ] = preg_match( $pattern, $value ) === 1;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate value is contained within allowed options
     *
     * Checks if a value (or all values in an array) exists in the provided
     * options array. Uses strict comparison (===) for type safety.
     *
     * @param  mixed                $value     Value or array of values to check
     * @param  array<int, mixed>    $options   Allowed values array
     * @param  array<string, mixed> $context   Validation context
     * @return bool                            True if value(s) in options, false otherwise
     * @access protected
     */
    protected function validate_in( $value, $options, array $context = array() ) {
        if ( ! is_array( $options ) ) {
            $options = array( $options );
        }

        if ( is_array( $value ) ) {
            foreach ( $value as $v ) {
                if ( ! in_array( $v, $options, true ) ) {
                    return false;
                }
            }
            return true;
        }

        return in_array( $value, $options, true );
    }

    /**
     * Validate string contains only alphabetic characters
     *
     * Uses cached regex pattern to validate that a string contains only
     * letters (a-zA-Z). Case-sensitive validation.
     *
     * @param  mixed                $value    Value to validate as alphabetic
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if only letters, false otherwise
     * @access protected
     */
    protected function validate_alpha( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        // Cache alpha validation
        $cache_key = 'alpha_' . $value;
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validation_cache[ $cache_key ] = preg_match( self::$patterns['alpha'], $value ) === 1;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate string contains only alphanumeric characters
     *
     * Uses cached regex pattern to validate that a string contains only
     * letters and numbers (a-zA-Z0-9).
     *
     * @param  mixed                $value    Value to validate as alphanumeric
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if only letters and numbers, false otherwise
     * @access protected
     */
    protected function validate_alphanumeric( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        // Cache alphanumeric validation
        $cache_key = 'alphanum_' . $value;
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validation_cache[ $cache_key ] = preg_match( self::$patterns['alphanumeric'], $value ) === 1;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate string contains only hexadecimal characters
     *
     * Uses cached regex pattern to validate that a string contains only
     * hexadecimal digits (0-9, a-f, A-F).
     *
     * @param  mixed                $value    Value to validate as hexadecimal
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if only hex characters, false otherwise
     * @access protected
     */
    protected function validate_hex( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        // Cache hex validation
        $cache_key = 'hex_' . $value;
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            self::$validation_cache[ $cache_key ] = preg_match( self::$patterns['hex'], $value ) === 1;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate value is boolean or boolean-like
     *
     * Accepts actual boolean values or common string representations
     * of boolean values ('true', 'false', '0', '1', 'yes', 'no').
     *
     * @param  mixed                $value    Value to validate as boolean
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if boolean or boolean-like, false otherwise
     * @access protected
     */
    protected function validate_bool( $value, $args = array(), array $context = array() ): bool {
        return is_bool( $value ) || in_array(
            $value,
            array( '0', '1', 0, 1, 'true', 'false', 'yes', 'no' ),
            true
        );
    }

    /**
     * Validate string contains valid JSON format
     *
     * Uses json_decode() to validate JSON syntax without actually
     * parsing the data. Caches validation results for performance.
     *
     * @param  mixed                $value    String to validate as JSON
     * @param  array<string, mixed> $args     Additional arguments (unused)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid JSON, false otherwise
     * @access protected
     */
    protected function validate_json( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        // Cache JSON validation
        $cache_key = 'json_' . md5( $value );
        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            json_decode( $value );
            self::$validation_cache[ $cache_key ] = json_last_error() === JSON_ERROR_NONE;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate string as date in specified format
     *
     * Uses DateTime::createFromFormat() to validate date strings against
     * a specified format. Ensures the date is valid and matches the format
     * exactly (prevents ambiguous dates like "2024-02-30").
     *
     * @param  mixed                $value    String to validate as date
     * @param  array<string, mixed> $args     Arguments with 'format' key (default: 'Y-m-d')
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid date in specified format, false otherwise
     * @access protected
     */
    protected function validate_date( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        $format = $args['format'] ?? 'Y-m-d';
        $date = \DateTime::createFromFormat( $format, $value );
        return $date && $date->format( $format ) === $value;
    }

    /**
     * Validate string as time in specified format
     *
     * Uses DateTime::createFromFormat() to validate time strings against
     * a specified format. Supports various time formats including 24-hour
     * and 12-hour notations.
     *
     * @param  mixed                $value    String to validate as time
     * @param  array<string, mixed> $args     Arguments with 'format' key (default: 'H:i:s')
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid time in specified format, false otherwise
     * @access protected
     */
    protected function validate_time( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        $format = $args['format'] ?? 'H:i:s';
        $time = \DateTime::createFromFormat( $format, $value );
        return $time && $time->format( $format ) === $value;
    }

    /**
     * Validate string as datetime in specified format
     *
     * Uses DateTime::createFromFormat() to validate datetime strings against
     * a specified format. Combines date and time validation in a single step.
     *
     * @param  mixed                $value    String to validate as datetime
     * @param  array<string, mixed> $args     Arguments with 'format' key (default: 'Y-m-d H:i:s')
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if valid datetime in specified format, false otherwise
     * @access protected
     */
    protected function validate_datetime( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_string( $value ) ) {
            return false;
        }

        $format = $args['format'] ?? 'Y-m-d H:i:s';
        $datetime = \DateTime::createFromFormat( $format, $value );
        return $datetime && $datetime->format( $format ) === $value;
    }

    /**
     * Validate file extension against allowed extensions
     *
     * Checks the file extension of an uploaded file array against a list
     * of allowed extensions. Case-insensitive comparison.
     *
     * @param  mixed                $value    File upload array with 'name' key
     * @param  array<string>|string $args     Allowed file extensions (array or string)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if file extension is allowed, false otherwise
     * @access protected
     */
    protected function validate_file_extension( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_array( $value ) || ! isset( $value['name'] ) ) {
            return false;
        }

        $allowed = is_array( $args ) ? $args : array( $args );
        $extension = strtolower( pathinfo( $value['name'], PATHINFO_EXTENSION ) );

        return in_array( $extension, $allowed, true );
    }

    /**
     * Validate file MIME type against allowed types
     *
     * Checks the MIME type of an uploaded file array against a list
     * of allowed MIME types. Uses the 'type' key from the file array.
     *
     * @param  mixed                $value    File upload array with 'type' key
     * @param  array<string>|string $args     Allowed MIME types (array or string)
     * @param  array<string, mixed> $context  Validation context
     * @return bool                           True if MIME type is allowed, false otherwise
     * @access protected
     */
    protected function validate_mime_type( $value, $args = array(), array $context = array() ): bool {
        if ( ! is_array( $value ) || ! isset( $value['type'] ) ) {
            return false;
        }

        $allowed = is_array( $args ) ? $args : array( $args );
        return in_array( $value['type'], $allowed, true );
    }

    /**
     * Validate IP address or CIDR range
     *
     * Validates single IP addresses or comma-separated lists of IPs/CIDR ranges.
     * Supports IPv4 and IPv6 addresses with optional CIDR notation validation.
     * Returns error message string on failure for detailed feedback.
     *
     * @param  mixed                $value    IP address(es) to validate (string or array)
     * @param  array<string, mixed> $args     Validation arguments with 'allow_range' and 'version' keys
     * @param  array<string, mixed> $context  Validation context
     * @return bool|string                     True if valid, error message string on failure
     * @access public
     */
    public function validate_ip_address( $value, $args = array(), array $context = array() ) {
        if ( $this->is_value_empty( $value ) ) {
            return true;
        }

        if ( ! is_string( $value ) ) {
            return __( 'Invalid IP address format', 'ultraguard-security-suite' );
        }

        $args = wp_parse_args( $args, array(
            'allow_range' => false,
            'version'     => 'both',
        ) );

        // Cache IP validation
        $cache_key = 'ip_' . md5( serialize( array( $value, $args ) ) );
        if ( isset( self::$validation_cache[ $cache_key ] ) ) {
            return self::$validation_cache[ $cache_key ];
        }

        $ips = array_map( 'trim', explode( ',', $value ) );

        foreach ( $ips as $ip ) {
            if ( $args['allow_range'] && strpos( $ip, '/' ) !== false ) {
                if ( ! $this->validate_cidr( $ip, $args['version'] ) ) {
                    self::$validation_cache[ $cache_key ] = sprintf(
                        /* translators: Placeholder values are inserted into this string. */
                        __( 'Invalid IP range: %s', 'ultraguard-security-suite' ),
                        esc_html( $ip )
                    );
                    return self::$validation_cache[ $cache_key ];
                }
            } else {
                if ( ! $this->validate_single_ip( $ip, $args['version'] ) ) {
                    self::$validation_cache[ $cache_key ] = sprintf(
                        /* translators: Placeholder values are inserted into this string. */
                        __( 'Invalid IP address: %s', 'ultraguard-security-suite' ),
                        esc_html( $ip )
                    );
                    return self::$validation_cache[ $cache_key ];
                }
            }
        }

        self::$validation_cache[ $cache_key ] = true;
        return true;
    }

    /**
     * Validate single IP address (IPv4 or IPv6)
     *
     * Uses filter_var() with FILTER_VALIDATE_IP flag to validate individual
     * IP addresses. Caches validation results for performance.
     *
     * @param  string $ip       IP address string to validate
     * @param  string $version  IP version: '4', '6', or 'both'
     * @return bool             True if valid IP address, false otherwise
     * @access protected
     */
    protected function validate_single_ip( string $ip, string $version ): bool {
        $cache_key = 'single_ip_' . $ip . '_' . $version;

        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            $flags = 0;

            if ( '4' === $version ) {
                $flags = FILTER_FLAG_IPV4;
            } elseif ( '6' === $version ) {
                $flags = FILTER_FLAG_IPV6;
            }

            self::$validation_cache[ $cache_key ] = filter_var( $ip, FILTER_VALIDATE_IP, $flags ) !== false;
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Validate CIDR notation IP range
     *
     * Validates CIDR notation strings (e.g., "192.168.1.0/24") including
     * both the IP address portion and the subnet mask range.
     *
     * @param  string $cidr     CIDR notation string to validate
     * @param  string $version  IP version: '4', '6', or 'both'
     * @return bool             True if valid CIDR notation, false otherwise
     * @access protected
     */
    protected function validate_cidr( string $cidr, string $version ): bool {
        $cache_key = 'cidr_' . $cidr . '_' . $version;

        if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
            if ( ! preg_match( self::$patterns['cidr'], $cidr, $matches ) ) {
                self::$validation_cache[ $cache_key ] = false;
                return false;
            }

            list( , $ip, $mask ) = $matches;

            if ( ! is_numeric( $mask ) ) {
                self::$validation_cache[ $cache_key ] = false;
                return false;
            }

            $mask = (int) $mask;

            if ( '4' === $version ) {
                self::$validation_cache[ $cache_key ] = $mask >= 0 && $mask <= 32 && $this->validate_single_ip( $ip, '4' );
            } elseif ( '6' === $version ) {
                self::$validation_cache[ $cache_key ] = $mask >= 0 && $mask <= 128 && $this->validate_single_ip( $ip, '6' );
            } else {
                self::$validation_cache[ $cache_key ] =
                    ( $mask >= 0 && $mask <= 32 && $this->validate_single_ip( $ip, '4' ) ) ||
                    ( $mask >= 0 && $mask <= 128 && $this->validate_single_ip( $ip, '6' ) );
            }
        }

        return self::$validation_cache[ $cache_key ];
    }

    /**
     * Add validation error to error collection
     *
     * Records validation errors with sanitized field names and messages.
     * Deduplicates errors for the same field and fires WordPress action
     * for error tracking/extensibility.
     *
     * @param  string $field    Field name that failed validation
     * @param  string $message  Error message describing validation failure
     * @return void
     * @access protected
     */
    protected function add_error( string $field, string $message ): void {
        $sanitized_field = sanitize_key( $field );
        $sanitized_message = wp_kses( $message, array() );

        // Deduplicate errors
        if ( ! isset( self::$errors[ $sanitized_field ] ) ) {
            self::$errors[ $sanitized_field ] = $sanitized_message;

            // Fire error action with caching
            $hook_key = 'error_added_' . $sanitized_field . '_' . md5( $sanitized_message );
            if ( ! isset( self::$validation_cache[ $hook_key ] ) ) {
                do_action( 'ultraguard_validation_engine_error_added', $sanitized_field, $sanitized_message );
                self::$validation_cache[ $hook_key ] = true;
            }
        }
    }

    /**
     * Retrieve all validation errors
     *
     * Returns associative array of field names to error messages for
     * all validation failures in the current session.
     *
     * @return array<string, string>  Validation errors array
     * @access public
     *
     * @example
     * $errors = $engine->get_errors();
     * foreach ($errors as $field => $message) {
     *     echo "Field '$field': $message\n";
     * }
     */
    public function get_errors(): array {
        return self::$errors;
    }

    /**
     * Check if any validation errors exist
     *
     * Quick check to determine if the current validation session
     * has recorded any errors.
     *
     * @return bool  True if errors exist, false otherwise
     * @access public
     */
    public function has_errors(): bool {
        return ! empty( self::$errors );
    }

    /**
     * Get the first validation error message
     *
     * Useful for single-error scenarios or when only the first
     * error is relevant. Returns null if no errors exist.
     *
     * @return string|null  First error message or null if no errors
     * @access public
     */
    public function get_first_error(): ?string {
        return ! empty( self::$errors ) ? reset( self::$errors ) : null;
    }

    /**
     * Get all error messages concatenated into a single string
     *
     * Useful for displaying all validation errors in a single
     * message block or logging purposes.
     *
     * @param  string $separator  String to separate error messages (default: newline)
     * @return string             Concatenated error messages string
     * @access public
     */
    public function get_errors_string( string $separator = "\n" ): string {
        return implode( $separator, self::$errors );
    }

    /**
     * Register custom validation callback
     *
     * Allows extension of the validation engine with custom validation
     * logic. Callbacks should return true on success or error message
     * string on failure.
     *
     * @param  string   $name      Validator name (will be sanitized to lowercase)
     * @param  callable $callback  Validation function accepting (value, args, context)
     * @return bool                True if validator registered successfully
     * @access public
     *
     * @example
     * $engine->register_validator('custom_rule', function($value, $args, $context) {
     *     return $value === 'expected' ? true : 'Value must be "expected"';
     * });
     */
    public function register_validator( string $name, callable $callback ): bool {
        if ( empty( $name ) ) {
            return false;
        }

        $sanitized_name = sanitize_key( $name );
        self::$validators[ $sanitized_name ] = $callback;

        // Clear cache to ensure new validator is used
        $this->clear_cache();

        // Fire registration action
        do_action( 'ultraguard_validation_engine_validator_registered', $sanitized_name, $callback );

        return true;
    }

    /**
     * Unregister custom validation callback
     *
     * Removes a previously registered custom validator from the
     * validation engine. Useful for temporary overrides or cleanup.
     *
     * @param  string $name  Validator name to remove
     * @return bool          True if validator was removed, false if not found
     * @access public
     */
    public function unregister_validator( string $name ): bool {
        if ( isset( self::$validators[ $name ] ) ) {
            unset( self::$validators[ $name ] );
            $this->clear_cache();
            return true;
        }
        return false;
    }

    /**
     * Clear all validation errors from current session
     *
     * Resets the error collection, typically called before starting
     * a new validation operation.
     *
     * @return void
     * @access public
     */
    public function clear_errors(): void {
        self::$errors = array();
    }

    /**
     * Quick single-value validation helper
     *
     * Static convenience method for simple one-off validations without
     * needing to set up full validation contexts.
     *
     * @param  mixed                $value  Value to validate
     * @param  string               $rule   Validation rule name
     * @param  array<string, mixed> $args   Rule arguments
     * @return bool                         True if validation passes, false otherwise
     * @access public
     * @static
     *
     * @example
     * if (ValidationEngine::quick_validate('test@example.com', 'email')) {
     *     echo 'Valid email';
     * }
     */
    public static function quick_validate( $value, string $rule = 'required', array $args = array() ): bool {
        $instance = self::get_instance();
        $context = array( 'strict_mode' => false );
        $sanitized_rule = sanitize_key( $rule );

        // Ensure validators are initialized
        if ( empty( self::$validators ) ) {
            self::init_default_validators();
        }

        if ( isset( self::$validators[ $sanitized_rule ] ) ) {
            $cache_key = 'quick_' . $sanitized_rule . '_' . md5( serialize( array( $value, $args ) ) );

            if ( ! isset( self::$validation_cache[ $cache_key ] ) ) {
                $result = call_user_func( self::$validators[ $sanitized_rule ], $value, $args, $context );
                self::$validation_cache[ $cache_key ] = $result === true;
            }

            return self::$validation_cache[ $cache_key ];
        }

        return false;
    }

    /**
     * Validate multiple values with the same rule
     *
     * Applies a single validation rule to multiple values and returns
     * individual results for each value.
     *
     * @param  array<string, mixed> $values  Values to validate (associative array)
     * @param  string               $rule    Validation rule name
     * @param  array<string, mixed> $args    Rule arguments
     * @return array<string, bool>           Validation results keyed by input keys
     * @access public
     * @static
     *
     * @example
     * $results = ValidationEngine::validate_multiple(
     *     ['email1' => 'test@example.com', 'email2' => 'invalid'],
     *     'email'
     * );
     * // Returns: ['email1' => true, 'email2' => false]
     */
    public static function validate_multiple( array $values, string $rule = 'required', array $args = array() ): array {
        $results = array();

        foreach ( $values as $key => $value ) {
            $results[ $key ] = self::quick_validate( $value, $rule, $args );
        }

        return $results;
    }

    /**
     * Generate cache key for validation operation
     *
     * Creates unique cache keys based on data, rules, and context to
     * enable result caching and performance optimization.
     *
     * @param  array<string, mixed> $data     Validation data
     * @param  array<string, mixed> $rules    Validation rules
     * @param  array<string, mixed> $context  Validation context
     * @return string                         Generated cache key
     * @access private
     */
    private function generate_cache_key( array $data, array $rules, array $context ): string {
        return 'validate_' . md5( serialize( array( $data, $rules, $context ) ) );
    }

    /**
     * Generate cache key for rule validation
     *
     * Creates unique cache keys for individual rule validations to
     * enable fine-grained caching at the rule level.
     *
     * @param  string               $field    Field name
     * @param  mixed                $value    Field value
     * @param  string               $rule     Rule name
     * @param  mixed                $args     Rule arguments
     * @param  array<string, mixed> $context  Validation context
     * @return string                         Generated cache key
     * @access private
     */
    private function generate_rule_cache_key( string $field, $value, string $rule, $args, array $context ): string {
        return 'rule_' . $field . '_' . $rule . '_' . md5( serialize( array( $value, $args, $context ) ) );
    }

    /**
     * Clear all validation caches
     *
     * Resets all internal caches including validation results, error
     * messages, and intermediate computations. Useful after changing
     * validation rules or validators.
     *
     * @return void
     * @access public
     */
    public function clear_cache(): void {
        self::$validation_cache = array();
        self::$error_messages = array();
    }

    /**
     * Get cache statistics for debugging and optimization
     *
     * Returns counts of cached items in various categories to help
     * with performance tuning and debugging cache behavior.
     *
     * @return array<string, int>  Cache statistics with category counts
     * @access public
     */
    public function get_cache_stats(): array {
        return array(
            'validation_cache' => count( self::$validation_cache ),
            'error_messages'   => count( self::$error_messages ),
            'validators_count' => count( self::$validators ),
            'errors_count'     => count( self::$errors ),
        );
    }

    /**
     * Prevent cloning of singleton instance
     *
     * @return void
     * @access private
     */
    private function __clone() {}

    /**
     * Prevent unserialization of singleton instance
     *
     * @return void
     * @access public
     * @throws \Exception  Always throws exception when called
     */
    public function __wakeup() {
        _doing_it_wrong( __FUNCTION__, esc_html__( 'ValidationEngine cannot be unserialized.', 'ultraguard-security-suite' ), '1.0.0' );
    }
}
