<?php
namespace PrimeLink\Helpers;

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

/**
 * Validation helper using Symfony Validator if available, with fallback to native PHP/WooCommerce.
 */
final class ValidatorHelper {

    /**
     * Check if Symfony Validator is available (prefixed or normal).
     *
     * @return bool
     */
    private static function has_symfony_validator() {
        // Symfony Validator 6.x requires PHP 8.1+
        if ( version_compare( PHP_VERSION, '8.1.0', '<' ) ) {
            return false;
        }
        
        return class_exists( '\PrimeLink\Vendor\Symfony\Component\Validator\Validation' ) ||
               class_exists( '\Symfony\Component\Validator\Validation' );
    }

    /**
     * Get Symfony Validator instance.
     *
     * @return object|null ValidatorInterface instance or null.
     */
    private static function get_validator() {
        if ( class_exists( '\PrimeLink\Vendor\Symfony\Component\Validator\Validation' ) ) {
            return \PrimeLink\Vendor\Symfony\Component\Validator\Validation::createValidator();
        } elseif ( class_exists( '\Symfony\Component\Validator\Validation' ) ) {
            return \Symfony\Component\Validator\Validation::createValidator();
        }
        return null;
    }

    /**
     * Get Symfony constraint classes.
     *
     * @return array{Email: string, NotBlank: string, Type: string, Range: string, Length: string, Url: string, PositiveOrZero: string}|null
     */
    private static function get_constraint_classes() {
        $prefix = '';
        if ( class_exists( '\PrimeLink\Vendor\Symfony\Component\Validator\Constraints\Email' ) ) {
            $prefix = '\PrimeLink\Vendor\Symfony\Component\Validator\Constraints\\';
        } elseif ( class_exists( '\Symfony\Component\Validator\Constraints\Email' ) ) {
            $prefix = '\Symfony\Component\Validator\Constraints\\';
        } else {
            return null;
        }

        return array(
            'Email'            => $prefix . 'Email',
            'NotBlank'         => $prefix . 'NotBlank',
            'Type'             => $prefix . 'Type',
            'Range'            => $prefix . 'Range',
            'Length'           => $prefix . 'Length',
            'Url'              => $prefix . 'Url',
            'PositiveOrZero'   => $prefix . 'PositiveOrZero',
        );
    }

    /**
     * Validate email address.
     *
     * @param string $email Email to validate.
     * @return bool True if valid.
     */
    public static function is_email( $email ) {
        if ( empty( $email ) ) {
            return false;
        }

        if ( self::has_symfony_validator() ) {
            $validator = self::get_validator();
            $constraints = self::get_constraint_classes();
            
            if ( $validator && $constraints ) {
                $violations = $validator->validate( $email, new $constraints['Email']() );
                return count( $violations ) === 0;
            }
        }

        // Fallback to native PHP filter (works without WooCommerce)
        return filter_var( $email, FILTER_VALIDATE_EMAIL ) !== false;
    }

    /**
     * Validate that value is not blank.
     *
     * @param mixed $value Value to validate.
     * @return bool True if not blank.
     */
    public static function is_not_blank( $value ) {
        if ( self::has_symfony_validator() ) {
            $validator = self::get_validator();
            $constraints = self::get_constraint_classes();
            
            if ( $validator && $constraints ) {
                $violations = $validator->validate( $value, new $constraints['NotBlank']() );
                return count( $violations ) === 0;
            }
        }

        // Fallback: check if value is not empty (after trimming strings)
        if ( is_string( $value ) ) {
            return trim( $value ) !== '';
        }
        return ! empty( $value );
    }

    /**
     * Validate numeric value (integer or float).
     *
     * @param mixed $value Value to validate.
     * @param string $type 'int' or 'float'.
     * @return bool True if valid.
     */
    public static function is_numeric( $value, $type = 'float' ) {
        // First check if it's a numeric string or number
        if ( ! is_numeric( $value ) ) {
            return false;
        }

        // Convert to number for type checking
        if ( $type === 'int' ) {
            // Check if it's an integer (no decimal part)
            return ( (int) $value == $value );
        }

        // For float, just check if it's numeric (already done above)
        return true;
    }

    /**
     * Validate that numeric value is within range.
     *
     * @param mixed $value Value to validate.
     * @param float|null $min Minimum value (null = no minimum).
     * @param float|null $max Maximum value (null = no maximum).
     * @return bool True if valid.
     */
    public static function is_in_range( $value, $min = null, $max = null ) {
        if ( ! self::is_numeric( $value ) ) {
            return false;
        }

        $num_value = floatval( $value );

        if ( self::has_symfony_validator() ) {
            $validator = self::get_validator();
            $constraints = self::get_constraint_classes();
            
            if ( $validator && $constraints ) {
                $range_options = array();
                if ( $min !== null ) {
                    $range_options['min'] = $min;
                }
                if ( $max !== null ) {
                    $range_options['max'] = $max;
                }
                $range_constraint = new $constraints['Range']( $range_options );
                $violations = $validator->validate( $num_value, $range_constraint );
                return count( $violations ) === 0;
            }
        }

        // Fallback: manual range check
        if ( $min !== null && $num_value < $min ) {
            return false;
        }
        if ( $max !== null && $num_value > $max ) {
            return false;
        }
        return true;
    }

    /**
     * Validate that value is positive or zero.
     *
     * @param mixed $value Value to validate.
     * @return bool True if valid.
     */
    public static function is_positive_or_zero( $value ) {
        return self::is_in_range( $value, 0, null );
    }

    /**
     * Validate string length.
     *
     * @param string $value String to validate.
     * @param int|null $min Minimum length (null = no minimum).
     * @param int|null $max Maximum length (null = no maximum).
     * @return bool True if valid.
     */
    public static function is_length_valid( $value, $min = null, $max = null ) {
        if ( ! is_string( $value ) ) {
            return false;
        }

        $length = mb_strlen( $value );

        if ( self::has_symfony_validator() ) {
            $validator = self::get_validator();
            $constraints = self::get_constraint_classes();
            
            if ( $validator && $constraints ) {
                $length_options = array();
                if ( $min !== null ) {
                    $length_options['min'] = $min;
                }
                if ( $max !== null ) {
                    $length_options['max'] = $max;
                }
                $length_constraint = new $constraints['Length']( $length_options );
                $violations = $validator->validate( $value, $length_constraint );
                return count( $violations ) === 0;
            }
        }

        // Fallback: manual length check
        if ( $min !== null && $length < $min ) {
            return false;
        }
        if ( $max !== null && $length > $max ) {
            return false;
        }
        return true;
    }

    /**
     * Validate URL.
     *
     * @param string $url URL to validate.
     * @return bool True if valid.
     */
    public static function is_url( $url ) {
        if ( empty( $url ) ) {
            return false;
        }

        if ( self::has_symfony_validator() ) {
            $validator = self::get_validator();
            $constraints = self::get_constraint_classes();
            
            if ( $validator && $constraints ) {
                $violations = $validator->validate( $url, new $constraints['Url']() );
                return count( $violations ) === 0;
            }
        }

        // Fallback to WooCommerce function
        return filter_var( $url, FILTER_VALIDATE_URL ) !== false;
    }

    /**
     * Validate multiple constraints on a value.
     *
     * @param mixed $value Value to validate.
     * @param array $constraints Array of constraint names and options.
     *   Example: array(
     *     'NotBlank' => true,
     *     'Email' => true,
     *     'Length' => array('min' => 5, 'max' => 100),
     *   )
     * @return array{valid: bool, errors: string[]} Validation result.
     */
    public static function validate( $value, array $constraints ) {
        $errors = array();

        foreach ( $constraints as $constraint_name => $constraint_options ) {
            $is_valid = false;

            switch ( $constraint_name ) {
                case 'NotBlank':
                    $is_valid = self::is_not_blank( $value );
                    if ( ! $is_valid ) {
                        $errors[] = 'Value cannot be blank.';
                    }
                    break;

                case 'Email':
                    $is_valid = self::is_email( $value );
                    if ( ! $is_valid ) {
                        $errors[] = 'Invalid email format.';
                    }
                    break;

                case 'Numeric':
                    $type = is_array( $constraint_options ) && isset( $constraint_options['type'] ) 
                        ? $constraint_options['type'] 
                        : 'float';
                    $is_valid = self::is_numeric( $value, $type );
                    if ( ! $is_valid ) {
                        $errors[] = 'Value must be a valid number.';
                    }
                    break;

                case 'Range':
                    $min = is_array( $constraint_options ) && isset( $constraint_options['min'] ) 
                        ? $constraint_options['min'] 
                        : null;
                    $max = is_array( $constraint_options ) && isset( $constraint_options['max'] ) 
                        ? $constraint_options['max'] 
                        : null;
                    $is_valid = self::is_in_range( $value, $min, $max );
                    if ( ! $is_valid ) {
                        $min_msg = $min !== null ? " (min: {$min})" : '';
                        $max_msg = $max !== null ? " (max: {$max})" : '';
                        $errors[] = "Value out of range{$min_msg}{$max_msg}.";
                    }
                    break;

                case 'PositiveOrZero':
                    $is_valid = self::is_positive_or_zero( $value );
                    if ( ! $is_valid ) {
                        $errors[] = 'Value must be positive or zero.';
                    }
                    break;

                case 'Length':
                    $min = is_array( $constraint_options ) && isset( $constraint_options['min'] ) 
                        ? $constraint_options['min'] 
                        : null;
                    $max = is_array( $constraint_options ) && isset( $constraint_options['max'] ) 
                        ? $constraint_options['max'] 
                        : null;
                    $is_valid = self::is_length_valid( $value, $min, $max );
                    if ( ! $is_valid ) {
                        $min_msg = $min !== null ? " (min: {$min})" : '';
                        $max_msg = $max !== null ? " (max: {$max})" : '';
                        $errors[] = "Invalid length{$min_msg}{$max_msg}.";
                    }
                    break;

                case 'Url':
                    $is_valid = self::is_url( $value );
                    if ( ! $is_valid ) {
                        $errors[] = 'Invalid URL format.';
                    }
                    break;
            }
        }

        return array(
            'valid'  => empty( $errors ),
            'errors' => $errors,
        );
    }
}

