<?php
declare(strict_types=1);

namespace WP_AUX\Services\AuxCF;

/**
 * Service for handling ACF field validation
 *
 * This service parses validation strings from field entities and applies
 * them using ACF's validation hooks.
 *
 * @since 1.0.0
 */
class ValidationService
{

  // Set to true to enable debug logging in wp-content/debug.log
  private static bool $debugValidation = false;

  /**
   * Cache of validation rules by field key
   *
   * @var array<string, string>
   */
  private static array $validation_rules_cache = [];

  /**
   * Initialize validation hooks
   *
   * @since 1.0.0
   */
  public static function prepareHooks(): void
  {
    // Initialize validation rules cache early
    add_action('init', [self::class, 'initializeValidationRules'], 1);

    // Hook into ACF validation
    add_filter('acf/validate_value', [self::class, 'validateField'], 10, 4);
  }

  /**
   * Initialize validation rules by scanning field entities
   *
   * @since 1.0.0
   */
  public static function initializeValidationRules(): void
  {
    // Scan for field entities with validation rules
    self::scanFieldEntities();

    // Debug: Log validation rules cache
    if (self::$debugValidation) {
      error_log('ACF Validation Rules Cache: ' . print_r(self::$validation_rules_cache, true));
    }
  }

  /**
   * Scan for field entities and extract validation rules
   *
   * @since 1.0.0
   */
  private static function scanFieldEntities(): void
  {
    // Get all field entity classes from the theme
    $field_entities = self::getFieldEntityClasses();

    if (self::$debugValidation) {
      error_log('ACF Validation: Found ' . count($field_entities) . ' field entities');
    }

    foreach ($field_entities as $entity_class) {
      if (!class_exists($entity_class)) {
        continue;
      }

      // Create a temporary instance to check for validation rules
      try {
        // Skip classes that are not field entities (like ConditionalLogicConditionEntity)
        if (strpos($entity_class, 'ConditionalLogic') !== false) {
          continue;
        }

        // Only instantiate classes that extend FieldEntity
        if (!is_subclass_of($entity_class, 'AuxCF\Entities\FieldEntity')) {
          continue;
        }

        $instance = new $entity_class();

        // Check if the entity has validation rules
        $validation_rules = $instance->getValidation();

        if (!empty($validation_rules) && is_array($validation_rules)) {
          // Get the field key using the same method as the field entity
          $field_key = $instance->getKey();

          if (!empty($field_key)) {
            // Process validation rules and extract custom messages
            $processed_validation = self::processValidationRules($validation_rules);

            self::$validation_rules_cache[$field_key] = $processed_validation;

            if (self::$debugValidation) {
              $rules_display = is_array($processed_validation['rules']) ? print_r($processed_validation['rules'], true) : $processed_validation['rules'];
              error_log("ACF Validation: Added rule for {$field_key}: {$rules_display}");
              if (isset($processed_validation['custom_messages']) && !empty($processed_validation['custom_messages'])) {
                error_log("ACF Validation: Custom messages for {$field_key}: " . print_r($processed_validation['custom_messages'], true));
              }
            }
          }
        }
      } catch (\Exception $e) {
        // Skip entities that can't be instantiated
        if (self::$debugValidation) {
          error_log("ACF Validation: Error instantiating {$entity_class}: " . $e->getMessage());
        }
        continue;
      }
    }
  }

  /**
   * Get field entity classes from the theme
   *
   * @return array<string> Array of field entity class names
   * @since 1.0.0
   */
  private static function getFieldEntityClasses(): array
  {
    $classes = [];

    // Scan for field entities in the theme
    $theme_dirs = [
      get_template_directory() . '/inc/WP_AUX/Entities/AuxCF/OptionsFields',
      get_template_directory() . '/inc/AuxCF/Entities/Field',
    ];

    if (is_child_theme()) {
      $child_dirs = [
        get_stylesheet_directory() . '/inc/WP_AUX/Entities/AuxCF/OptionsFields',
        get_stylesheet_directory() . '/inc/AuxCF/Entities/Field',
      ];
      $theme_dirs = array_merge($child_dirs, $theme_dirs);
    }

    if (self::$debugValidation) {
      error_log('ACF Validation: Scanning directories: ' . print_r($theme_dirs, true));
    }

    foreach ($theme_dirs as $dir) {
      if (!is_dir($dir)) {
        if (self::$debugValidation) {
          error_log("ACF Validation: Directory does not exist: {$dir}");
        }
        continue;
      }

      $files = glob($dir . '/*.php');

      if (self::$debugValidation) {
        error_log("ACF Validation: Found " . count($files) . " files in {$dir}");
      }

      foreach ($files as $file) {
        $class_name = self::getClassNameFromFile($file);

        if (self::$debugValidation) {
          error_log("ACF Validation: File {$file} -> Class {$class_name}");
        }

        if ($class_name && class_exists($class_name)) {
          $classes[] = $class_name;
          if (self::$debugValidation) {
            error_log("ACF Validation: Added class {$class_name}");
          }
        }
      }
    }

    return $classes;
  }

  /**
   * Extract class name from a PHP file
   *
   * @param string $file_path Path to the PHP file
   * @return string|null Class name or null if not found
   * @since 1.0.0
   */
  private static function getClassNameFromFile(string $file_path): ?string
  {
    $content = file_get_contents($file_path);

    if (!$content) {
      return null;
    }

    // Look for class declaration with namespace
    if (preg_match('/namespace\s+([^;]+);.*?class\s+(\w+)/s', $content, $matches)) {
      $namespace = trim($matches[1]);
      $class_name = $matches[2];
      return $namespace . '\\' . $class_name;
    }

    // Fallback: Look for class declaration without namespace
    if (preg_match('/class\s+(\w+)/', $content, $matches)) {
      return $matches[1];
    }

    return null;
  }

  /**
   * Validate a field value using the field's validation rules
   *
   * @param mixed $valid Current validation status
   * @param mixed $value The field value to validate
   * @param array $field The ACF field array
   * @param string $input_name The input name
   * @return mixed True if valid, error message string if invalid
   * @since 1.0.0
   */
  public static function validateField($valid, $value, $field, $input_name)
  {
    // If already invalid, return the existing error
    if ($valid !== true) {
      return $valid;
    }

    // Get the field key to find the corresponding field entity
    $field_key = $field['key'] ?? '';

    if (empty($field_key)) {
      return $valid;
    }

    // Ensure validation rules are loaded
    if (empty(self::$validation_rules_cache)) {
      self::initializeValidationRules();
    }

    // Get validation rules for this field
    $validation_rules = self::getValidationRulesForField($field_key);

    if (empty($validation_rules)) {
      if (self::$debugValidation) {
        error_log("ACF Validation: No rules found for {$field_key}");
      }
      return $valid;
    }

    // Check if field is required
    $is_required = self::isFieldRequired($validation_rules);

    // If field is not required and value is empty, skip validation
    if (!$is_required && self::isEmptyValue($value)) {
      if (self::$debugValidation) {
        error_log("ACF Validation: Skipping validation for non-required empty field {$field_key}");
      }
      return $valid;
    }

    // Debug: Log validation attempts
    if (self::$debugValidation) {
      error_log("ACF Validation: Field {$field_key} with value '{$value}'");
      error_log("ACF Validation: Available rules: " . print_r(self::$validation_rules_cache, true));
      error_log("ACF Validation: Rules found for {$field_key}: " . print_r($validation_rules, true));
    }

    // Apply validation rules
    return self::applyValidationRules($value, $validation_rules, $field);
  }

  /**
   * Get validation rules for a specific field
   *
   * @param string $field_key The ACF field key
   * @return array|null Validation rules array or null if not found
   * @since 1.0.0
   */
  private static function getValidationRulesForField(string $field_key): ?array
  {
    $field_data = self::$validation_rules_cache[$field_key] ?? null;

    if (is_array($field_data)) {
      return $field_data['rules'] ?? null;
    }

    // Backward compatibility for old cache format
    // If it's a string, convert it to array format for compatibility
    if (is_string($field_data)) {
      $rules = [];
      $rule_parts = explode('|', $field_data);

      foreach ($rule_parts as $rule) {
        $rule = trim($rule);
        if ($rule === 'required') {
          $rules['required'] = true;
        } elseif ($rule === 'email') {
          $rules['email'] = true;
        } elseif ($rule === 'url') {
          $rules['url'] = true;
        } elseif ($rule === 'numeric') {
          $rules['numeric'] = true;
        } elseif (preg_match('/^min:(\d+)$/', $rule, $matches)) {
          $rules['min'] = (int) $matches[1];
        } elseif (preg_match('/^max:(\d+)$/', $rule, $matches)) {
          $rules['max'] = (int) $matches[1];
        } elseif (strpos($rule, 'regex:') === 0) {
          $rules['regex'] = substr($rule, 6);
        }
      }

      return $rules;
    }

    return null;
  }

  /**
   * Apply validation rules to a field value
   *
   * @param mixed $value The value to validate
   * @param array $rules Validation rules array
   * @param array $field The ACF field array
   * @return mixed True if valid, error message string if invalid
   * @since 1.0.0
   */
  private static function applyValidationRules($value, array $rules, array $field): mixed
  {
    // Check if it's a required rule
    if (isset($rules['required']) && $rules['required']) {
      if (self::isEmptyValue($value)) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'required');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s is required.', 'aux-wordpress'), $field['label'] ?? 'This field')
          : sprintf('%s is required.', $field['label'] ?? 'This field');
        return $message;
      }
    }

    // Check if it's an email rule
    if (isset($rules['email']) && $rules['email'] && !empty($value)) {
      if (!is_email($value)) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'email');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s must be a valid email address.', 'aux-wordpress'), $field['label'] ?? 'This field')
          : sprintf('%s must be a valid email address.', $field['label'] ?? 'This field');
        return $message;
      }
    }

    // Check if it's a URL rule
    if (isset($rules['url']) && $rules['url'] && !empty($value)) {
      if (!filter_var($value, FILTER_VALIDATE_URL)) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'url');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s must be a valid URL.', 'aux-wordpress'), $field['label'] ?? 'This field')
          : sprintf('%s must be a valid URL.', $field['label'] ?? 'This field');
        return $message;
      }
    }

    // Check if it's a numeric rule
    if (isset($rules['numeric']) && $rules['numeric'] && !empty($value)) {
      if (!is_numeric($value)) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'numeric');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s must be a valid number.', 'aux-wordpress'), $field['label'] ?? 'This field')
          : sprintf('%s must be a valid number.', $field['title'] ?? 'This field');
        return $message;
      }
    }

    // Check if it's a min length rule
    if (isset($rules['min']) && !empty($value)) {
      $min_length = (int) $rules['min'];
      if (strlen($value) < $min_length) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'min');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s must be at least %d characters long.', 'aux-wordpress'), $field['label'] ?? 'This field', $min_length)
          : sprintf('%s must be at least %d characters long.', $field['label'] ?? 'This field', $min_length);
        return $message;
      }
    }

    // Check if it's a max length rule
    if (isset($rules['max']) && !empty($value)) {
      $max_length = (int) $rules['max'];
      if (strlen($value) > $max_length) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'max');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('%s must be no more than %d characters long.', 'aux-wordpress'), $field['label'] ?? 'This field', $max_length)
          : sprintf('%s must be no more than %d characters long.', $field['label'] ?? 'This field', $max_length);
        return $message;
      }
    }

    // Check if it's a regex rule
    if (isset($rules['regex']) && !empty($value)) {
      $pattern = $rules['regex'];
      $pattern = trim($pattern, '/'); // Remove leading/trailing slashes

      if (!preg_match("/^{$pattern}$/", $value)) {
        // Check for custom error message first
        $custom_message = self::getCustomErrorMessage($field, 'regex');
        if ($custom_message) {
          return $custom_message;
        }

        // Fall back to default message
        $message = function_exists('__')
          ? sprintf(__('The value does not match the required format for %s.', 'aux-wordpress'), $field['label'] ?? 'this field')
          : sprintf('The value does not match the required format for %s.', $field['label'] ?? 'this field');
        return $message;
      }
    }

    return true;
  }

  /**
   * Process validation rules from field entities
   * Stores validation rules in array format for efficient processing
   *
   * @param array $validation_rules The validation rules from the field entity
   * @return array Processed validation rules with rules array and custom_messages
   * @since 1.0.0
   */
  private static function processValidationRules(array $validation_rules): array
  {
    $custom_messages = $validation_rules['custom_messages'] ?? [];

    // Return the validation rules array directly with custom messages
    return [
      'rules' => $validation_rules,
      'custom_messages' => $custom_messages
    ];
  }

  /**
   * Check if a field is required based on validation rules
   *
   * @param array $validation_rules The validation rules array
   * @return bool True if field is required, false otherwise
   * @since 1.0.0
   */
  private static function isFieldRequired(array $validation_rules): bool
  {
    return isset($validation_rules['required']) && $validation_rules['required'];
  }

  /**
   * Check if a field value is considered empty
   *
   * @param mixed $value The value to check
   * @return bool True if value is empty, false otherwise
   * @since 1.0.0
   */
  private static function isEmptyValue($value): bool
  {
    // Check for null, empty string, or whitespace-only string
    if ($value === null || $value === '') {
      return true;
    }

    // Check for whitespace-only strings
    if (is_string($value) && trim($value) === '') {
      return true;
    }

    // Check for empty arrays
    if (is_array($value) && empty($value)) {
      return true;
    }

    // Note: We don't consider '0' as empty since it's a valid value
    // This matches the behavior in the existing required validation rule

    return false;
  }

  /**
   * Get custom error message for a specific validation rule
   *
   * @param array $field The ACF field array
   * @param string $rule_name The validation rule name
   * @return string|null Custom error message or null if not found
   * @since 1.0.0
   */
  private static function getCustomErrorMessage(array $field, string $rule_name): ?string
  {
    $field_key = $field['key'] ?? '';

    if (empty($field_key)) {
      return null;
    }

    $field_data = self::$validation_rules_cache[$field_key] ?? null;

    if (is_array($field_data) && isset($field_data['custom_messages'][$rule_name])) {
      return $field_data['custom_messages'][$rule_name];
    }

    return null;
  }
}
