<?php

/**
 * Validation Class
 *
 * @package Giraforms
 */
// Exit if accessed directly.
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Class GiraForms_Validation
 */
class GiraForms_Validation {
    /**
     * Validate form data.
     *
     * @param array $data Form data.
     * @param array $fields Form fields configuration.
     * @param array $context Optional trusted validation context.
     * @return array|true Array of errors or true if valid.
     */
    public static function validate_form( $data, $fields, $context = array() ) {
        $errors = array();
        foreach ( $fields as $field ) {
            $field_name = ( isset( $field['name'] ) ? $field['name'] : '' );
            $field_type = ( isset( $field['type'] ) ? $field['type'] : '' );
            $field_label = ( isset( $field['label'] ) ? $field['label'] : $field_name );
            $is_required = isset( $field['required'] ) && $field['required'];
            $field_value = ( isset( $data[$field_name] ) ? $data[$field_name] : '' );
            // Required validation
            if ( $is_required && empty( $field_value ) ) {
                $errors[$field_name] = sprintf( 
                    /* translators: %s: field label */
                    __( 'The "%s" field is required.', 'giraforms' ),
                    $field_label
                 );
                continue;
            }
            // Skip validation if field is empty and not required
            if ( empty( $field_value ) && !$is_required ) {
                continue;
            }
            // Type-specific validation
            switch ( $field_type ) {
                case 'email':
                    if ( !self::validate_email( $field_value ) ) {
                        $errors[$field_name] = sprintf( 
                            /* translators: %s: field label */
                            __( 'The "%s" field must contain a valid email address.', 'giraforms' ),
                            $field_label
                         );
                    }
                    break;
                case 'phone':
                    if ( !self::validate_phone( $field_value ) ) {
                        $errors[$field_name] = sprintf( 
                            /* translators: %s: field label */
                            __( 'The "%s" field must contain a valid phone number.', 'giraforms' ),
                            $field_label
                         );
                    }
                    break;
                case 'url':
                    if ( !self::validate_url( $field_value ) ) {
                        $errors[$field_name] = sprintf( 
                            /* translators: %s: field label */
                            __( 'The "%s" field must contain a valid URL.', 'giraforms' ),
                            $field_label
                         );
                    }
                    break;
                case 'number':
                    if ( !self::validate_number( $field_value, $field ) ) {
                        $errors[$field_name] = sprintf( 
                            /* translators: %s: field label */
                            __( 'The "%s" field must contain a valid number.', 'giraforms' ),
                            $field_label
                         );
                    }
                    break;
                case 'date':
                case 'calendar':
                    $date_error = self::validate_date( $field_value, $field );
                    if ( $date_error !== true ) {
                        $errors[$field_name] = $date_error;
                    }
                    break;
            }
            // Custom validation patterns (with security checks)
            if ( isset( $field['pattern'] ) && !empty( $field['pattern'] ) ) {
                $pattern_result = self::safe_preg_match( $field['pattern'], $field_value );
                // Only apply validation if pattern is valid and doesn't match
                if ( $pattern_result === false ) {
                    // Invalid regex pattern - log error but don't block submission
                    // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                    error_log( 'GiraForms: Invalid regex pattern detected: ' . $field['pattern'] );
                } elseif ( $pattern_result === 0 ) {
                    // Valid pattern but no match
                    $custom_message = ( isset( $field['patternMessage'] ) ? $field['patternMessage'] : sprintf( 
                        /* translators: %s: field label */
                        __( 'The "%s" field does not match the expected format.', 'giraforms' ),
                        $field_label
                     ) );
                    $errors[$field_name] = $custom_message;
                }
            }
            // Min/max length validation
            if ( isset( $field['minLength'] ) && strlen( $field_value ) < $field['minLength'] ) {
                $errors[$field_name] = sprintf( 
                    /* translators: 1: field label, 2: minimum length */
                    __( 'The "%1$s" field must contain at least %2$d characters.', 'giraforms' ),
                    $field_label,
                    $field['minLength']
                 );
            }
            if ( isset( $field['maxLength'] ) && strlen( $field_value ) > $field['maxLength'] ) {
                $errors[$field_name] = sprintf( 
                    /* translators: 1: field label, 2: maximum length */
                    __( 'The "%1$s" field cannot exceed %2$d characters.', 'giraforms' ),
                    $field_label,
                    $field['maxLength']
                 );
            }
        }
        return ( empty( $errors ) ? true : $errors );
    }

    /**
     * Safely execute preg_match with error handling.
     * Protects against invalid regex patterns and ReDoS attacks.
     *
     * @param string $pattern Regex pattern (without delimiters).
     * @param string $subject String to match against.
     * @return int|false 1 if match, 0 if no match, false if invalid pattern.
     */
    private static function safe_preg_match( $pattern, $subject ) {
        // Limit pattern length to prevent complex patterns
        if ( strlen( $pattern ) > 500 ) {
            return false;
        }
        // Escape delimiter if present in pattern
        $escaped_pattern = str_replace( '/', '\\/', $pattern );
        // Set a reasonable time limit for regex execution (in case of ReDoS)
        $previous_limit = ini_get( 'pcre.backtrack_limit' );
        ini_set( 'pcre.backtrack_limit', 10000 );
        // phpcs:ignore WordPress.PHP.IniSet.Risky
        // Use error suppression and check for errors
        $result = @preg_match( '/' . $escaped_pattern . '/u', $subject );
        // Restore previous limit
        ini_set( 'pcre.backtrack_limit', $previous_limit );
        // phpcs:ignore WordPress.PHP.IniSet.Risky
        // Check if preg_match failed (invalid pattern)
        if ( $result === false || preg_last_error() !== PREG_NO_ERROR ) {
            return false;
        }
        return $result;
    }

    /**
     * Validate email address.
     *
     * @param string $email Email address.
     * @return bool
     */
    public static function validate_email( $email ) {
        return is_email( $email );
    }

    /**
     * Validate phone number.
     *
     * @param string $phone Phone number.
     * @return bool
     */
    public static function validate_phone( $phone ) {
        // Remove common formatting characters (spaces, dashes, parentheses, dots)
        $phone = preg_replace( '/[\\s\\-\\(\\)\\.]/', '', $phone );
        // Check if it contains only digits and optional + at the start
        // Allow 7-15 digits (more flexible for international numbers)
        return preg_match( '/^\\+?[0-9]{7,15}$/', $phone );
    }

    /**
     * Validate URL.
     *
     * @param string $url URL.
     * @return bool
     */
    public static function validate_url( $url ) {
        return filter_var( $url, FILTER_VALIDATE_URL ) !== false;
    }

    /**
     * Validate number.
     *
     * @param mixed $value Number value.
     * @param array $field Field configuration.
     * @return bool
     */
    public static function validate_number( $value, $field ) {
        if ( !is_numeric( $value ) ) {
            return false;
        }
        $value = floatval( $value );
        if ( isset( $field['min'] ) && $value < $field['min'] ) {
            return false;
        }
        if ( isset( $field['max'] ) && $value > $field['max'] ) {
            return false;
        }
        return true;
    }

    /**
     * Validate date field.
     *
     * @param string $date_value Date value (YYYY-MM-DD format).
     * @param array  $field Field configuration.
     * @return string|bool Error message or true if valid.
     */
    public static function validate_date( $date_value, $field ) {
        $field_label = ( isset( $field['label'] ) ? $field['label'] : __( 'Date', 'giraforms' ) );
        // Check if date format is valid (YYYY-MM-DD)
        if ( !preg_match( '/^\\d{4}-\\d{2}-\\d{2}$/', $date_value ) ) {
            return sprintf( 
                /* translators: %s: field label */
                __( 'The "%s" field must contain a valid date in YYYY-MM-DD format.', 'giraforms' ),
                $field_label
             );
        }
        // Parse date
        $date_parts = explode( '-', $date_value );
        $year = (int) $date_parts[0];
        $month = (int) $date_parts[1];
        $day = (int) $date_parts[2];
        // Check if date is valid
        if ( !checkdate( $month, $day, $year ) ) {
            return sprintf( 
                /* translators: %s: field label */
                __( 'The "%s" field contains an invalid date.', 'giraforms' ),
                $field_label
             );
        }
        $timestamp = strtotime( $date_value );
        // Validate minimum date
        if ( isset( $field['minDate'] ) && !empty( $field['minDate'] ) ) {
            $min_timestamp = strtotime( $field['minDate'] );
            if ( $timestamp < $min_timestamp ) {
                return sprintf( 
                    /* translators: 1: field label, 2: minimum date */
                    __( 'The "%1$s" field must be greater than or equal to %2$s.', 'giraforms' ),
                    $field_label,
                    date_i18n( get_option( 'date_format' ), $min_timestamp )
                 );
            }
        }
        // Validate maximum date
        if ( isset( $field['maxDate'] ) && !empty( $field['maxDate'] ) ) {
            $max_timestamp = strtotime( $field['maxDate'] );
            if ( $timestamp > $max_timestamp ) {
                return sprintf( 
                    /* translators: 1: field label, 2: maximum date */
                    __( 'The "%1$s" field must be less than or equal to %2$s.', 'giraforms' ),
                    $field_label,
                    date_i18n( get_option( 'date_format' ), $max_timestamp )
                 );
            }
        }
        // Validate disabled days (0 = Sunday, 6 = Saturday)
        if ( isset( $field['disabledDays'] ) && is_array( $field['disabledDays'] ) ) {
            $day_of_week = (int) gmdate( 'w', $timestamp );
            if ( in_array( $day_of_week, $field['disabledDays'], true ) ) {
                $day_names = array(
                    0 => __( 'Sunday', 'giraforms' ),
                    1 => __( 'Monday', 'giraforms' ),
                    2 => __( 'Tuesday', 'giraforms' ),
                    3 => __( 'Wednesday', 'giraforms' ),
                    4 => __( 'Thursday', 'giraforms' ),
                    5 => __( 'Friday', 'giraforms' ),
                    6 => __( 'Saturday', 'giraforms' ),
                );
                return sprintf( 
                    /* translators: 1: field label, 2: day name */
                    __( 'The "%1$s" field cannot be a %2$s.', 'giraforms' ),
                    $field_label,
                    $day_names[$day_of_week]
                 );
            }
        }
        // Validate disabled specific dates
        if ( isset( $field['disabledDates'] ) && is_array( $field['disabledDates'] ) ) {
            if ( in_array( $date_value, $field['disabledDates'], true ) ) {
                return sprintf( 
                    /* translators: 1: field label, 2: disabled date */
                    __( 'The "%1$s" field: the date %2$s is not allowed.', 'giraforms' ),
                    $field_label,
                    date_i18n( get_option( 'date_format' ), $timestamp )
                 );
            }
        }
        return true;
    }

    /**
     * Sanitize form data.
     *
     * @param array $data Form data.
     * @return array
     */
    public static function sanitize_form_data( $data ) {
        $sanitized = array();
        foreach ( $data as $key => $value ) {
            // Normalize array keys: strip [] suffix (multi-select, checkboxes).
            $key = preg_replace( '/\\[\\]$/', '', $key );
            if ( is_array( $value ) ) {
                $sanitized[$key] = array_map( 'sanitize_text_field', $value );
            } else {
                // Check if value is a base64 image (signature field)
                if ( is_string( $value ) && preg_match( '/^data:image\\/(png|jpg|jpeg|gif|webp);base64,/', $value ) ) {
                    // For signature fields, validate base64 format and keep it as-is
                    $sanitized[$key] = $value;
                } elseif ( strpos( $value, "\n" ) !== false ) {
                    $sanitized[$key] = sanitize_textarea_field( $value );
                } else {
                    $sanitized[$key] = sanitize_text_field( $value );
                }
            }
        }
        return $sanitized;
    }

}
