<?php
/**
 *
 * @copyright 2008 - https://www.clicshopping.org
 * @Brand : ClicShoppingAI(TM) at Inpi all right Reserved
 * @Licence GPL 2 & MIT
 * @Info : https://www.clicshopping.org/forum/trademark/
 *
 */

namespace ClicShopping\Apps\Configuration\ChatGpt\Classes\ClicShoppingAdmin;

use ClicShopping\OM\CLICSHOPPING;
use DateTime;
use ClicShopping\Apps\Configuration\ChatGpt\Classes\Security\SecurityLogger;

/**
 * LlmGuardrails
 *
 * Class for validating LLM responses with e-commerce-specific guardrails.
 * Implements validation heuristics, hallucination detection, and response quality assessment.
 */
class LlmGuardrails
{
  private const CONFIDENCE_THRESHOLD = 0.75;
  private const HALLUCINATION_THRESHOLD = 0.8; // a implementer
  private const MAX_RESPONSE_LENGTH = 8192;
  private const MIN_CONFIDENCE_SCORE = 0.6; // a implementer

  protected static ?SecurityLogger $securityLogger = null;
  // Patterns de détection d'hallucinations e-commerce
  //possible to create a specific suspicious pattern
  private const SUSPICIOUS_PATTERNS = [];



  /**
   * Initializes the security logger if not already done.
   *
   * This method ensures that the SecurityLogger instance is created only once,
   * following the singleton pattern. It is called before any logging operations.
   */
  private static function initLogger(): void
  {
    if (self::$securityLogger === null) {
      self::$securityLogger = new SecurityLogger();
    }
  }
  
  /**
   * Checks guardrails on the LLM response.
   *
   * Validates the generated answer from the AI against e-commerce-specific guardrails,
   * including structural, business content, hallucination, and numerical checks.
   *
   * @param string $question The question asked to the AI
   * @param string $result The response generated by the AI
   * @return array|string Validation results or error message
   */
  public static function checkGuardrails(string $question, string $result): array|string
  {
    self::initLogger();
    $guardrailsValidation = LlmGuardrails::GuardrailsResult($result);

    // Décision basée sur la validation
    switch ($guardrailsValidation['action']) {
      case 'block':
        if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
          self::$securityLogger->logSecurityEvent('Response blocked by guardrails: ' . json_encode($guardrailsValidation), 'warning');
        }

        return CLICSHOPPING::getDef('error_llm_guardrails_block');
      case 'manual_review':
        if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
          self::$securityLogger->logSecurityEvent('Response requires manual review: ' . json_encode($guardrailsValidation), 'warning');
        }

        $result = CLICSHOPPING::getDef('error_llm_guardrails_manual_review', ['result' => $result]);
        break;

      case 'allow_with_warning':
        $confidenceScore = round($guardrailsValidation['confidence_score'] * 100)  . '%*';
        $result = CLICSHOPPING::getDef('success_llm_guardrails_manual_confidence_score', ['result' => $result, 'confidence' => $confidenceScore]);
        break;
    }

    $evaluationResults = LlmGuardrails::evaluateLlmResponse($question, $result);

    return $evaluationResults;
  }


/**
 * Main guardrails method - Full validation of the LLM response.
 *
 * Performs a comprehensive validation of the AI-generated answer, including:
 * - Structural checks (length, encoding, malicious code, JSON structure)
 * - E-commerce business content validation (realistic metrics, percentages)
 * - Hallucination detection (suspicious patterns, future dates, impossible values)
 * - Numerical data validation (sales figures, percentages, currency amounts, math consistency)
 * - (Optional) Source and citation validation
 * - Global confidence score calculation
 * - Final decision (allow, block, manual review, warning) based on validation results
 *
 * @param string $result The AI-generated response to validate
 * @return array Validation results including scores and recommended action
 */
  public static function GuardrailsResult(string $result): array
  {
    self::initLogger();
    $validationResults = [];

    try {
      // 1. Validation structurelle
      $structuralValidation = self::validateStructure($result);
      $validationResults['structural'] = $structuralValidation;

      // 2. Validation du contenu métier
      $contentValidation = self::validateBusinessContent($result);
      $validationResults['content'] = $contentValidation;

      $hallucinationCheck = self::detectHallucinations($result);
      $validationResults['hallucination'] = $hallucinationCheck;

      // 4. Validation numérique pour les métriques BI
      $numericalValidation = self::validateNumericalData($result);
      $validationResults['numerical'] = $numericalValidation;

      // 5. Validation des sources et citations
     // $sourceValidation = self::validateSources($result);
    //  $validationResults['sources'] = $sourceValidation;

      // 6. Score de confiance global
      $confidenceScore = self::calculateConfidenceScore($validationResults);
      $validationResults['confidence_score'] = $confidenceScore;

      // 7. Décision finale
      $validationResults['is_valid'] = $confidenceScore >= self::CONFIDENCE_THRESHOLD;
      $validationResults['action'] = self::determineAction($confidenceScore, $validationResults);

      // Log pour debug
      if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
        self::$securityLogger->logSecurityEvent('Guardrails Validation: ' . json_encode($validationResults), 'info');
      }

      return $validationResults;

    } catch (Exception $e) {
      self::$securityLogger->logSecurityEvent('Guardrails Error: ' . $e->getMessage(), 'error');

      return [
        'error' => true,
        'message' =>  CLICSHOPPING::getDef('error_llm_guardrails_validation'),
        'is_valid' => false,
        'action' => 'block'
      ];
    }
  }

/**
 * Structural validation of the response.
 *
 * Checks the structure of the AI-generated response, including:
 * - Length constraints
 * - UTF-8 encoding validity
 * - Presence of content
 * - Absence of malicious code (e\.g\. scripts)
 * - Valid JSON structure if applicable
 *
 * @param string $result The AI-generated response to validate
 * @return array Validation results with individual checks and a global score
 */
  private static function validateStructure(string $result): array
  {
    $validation = [
      'length_valid' => strlen($result) <= self::MAX_RESPONSE_LENGTH && strlen($result) > 10,
      'encoding_valid' => mb_check_encoding($result, 'UTF-8'),
      'has_content' => !empty(trim($result)),
      'no_malicious_code' => !self::containsMaliciousCode($result),
      'json_structure' => self::validateJsonStructure($result)
    ];

    $validation['score'] = array_sum($validation) / count($validation);

    return $validation;
  }

 /**
  * Business Content Validation (e-commerce)
  *
  * Validates the business logic and domain-specific content of the AI-generated response.
  * Checks for realistic e-commerce metrics, valid percentages, and optionally product references,
  * currency formats, and temporal consistency.
  *
  * @param string $result The AI-generated response to validate
  * @return array Validation results with individual checks and a global score
  */
  private static function validateBusinessContent(string $result): array
  {
    $validation = [
      'realistic_metrics' => self::validateRealisticMetrics($result),
      'percentage_validity' => self::validatePercentages($result),
    ];

    $validation['score'] = array_sum($validation) / count($validation);

    return $validation;
  }


/**
 * Specific Hallucination Detection
 *
 * Detects suspicious patterns in the LLM response that may indicate hallucinations,
 * such as unrealistic sales figures, future dates, or impossible values.
 * Returns details about detected patterns, future dates, impossible values,
 * a reversed score, and a suspect flag.
 */
  private static function detectHallucinations(string $result): array
  {
    $suspiciousCount = 0;
    $detectedPatterns = [];

    foreach (self::SUSPICIOUS_PATTERNS as $pattern) {
      if (preg_match($pattern, $result, $matches)) {
        $suspiciousCount++;
        $detectedPatterns[] = $matches[0];
      }
    }

    // Vérification des dates impossibles
    $futureDates = self::detectFutureDates($result);
    $impossibleValues = self::detectImpossibleValues($result);

    return [
      'suspicious_patterns_count' => $suspiciousCount,
      'detected_patterns' => $detectedPatterns,
      'future_dates' => $futureDates,
      'impossible_values' => $impossibleValues,
      'score' => 1 - min(1, $suspiciousCount / 3), // Score inversé
      'is_suspect' => $suspiciousCount > 2 || !empty($futureDates) || !empty($impossibleValues)
    ];
  }

 /**
  * Numeric Data Validation
  *
  * Validates numerical data in the AI-generated response.
  * Extracts numbers from the text and checks for:
  * - Realistic sales figures
  * - Valid percentage ranges
  * - Mathematical consistency (e\.g\. totals vs subtotals)
  * - Plausible currency amounts
  *
  * @param string $result The AI-generated response to validate
  * @return array Validation results with individual checks and a global score
  */
  private static function validateNumericalData(string $result): array
  {
    // Extraction des nombres du texte
    preg_match_all('/\d+(?:[,.]\d+)*/', $result, $numbers);

    $validation = [
      'realistic_sales_figures' => self::validateSalesFigures($numbers[0] ?? []),
      'percentage_range' => self::validatePercentageRange($result),
      'mathematical_consistency' => self::validateMathConsistency($result),
      'currency_amounts' => self::validateCurrencyAmounts($result)
    ];

    $validation['score'] = array_sum($validation) / count($validation);
    return $validation;
  }

/**
 * Calculates the overall confidence score for the LLM response validation.
 *
 * Aggregates the scores from different validation categories (structural, content, hallucination, numerical, sources)
 * using predefined weights. Returns a float between 0.0 and 1.0 representing the global confidence in the response.
 *
 * @param array $validationResults Array of validation results for each category.
 * @return float Global confidence score (0.0 to 1.0).
 */
  private static function calculateConfidenceScore(array $validationResults): float
  {
    $weights = [
      'structural' => 0.2,
      'content' => 0.3,
      'hallucination' => 0.3,
      'numerical' => 0.15,
      'sources' => 0.05
    ];

    $totalScore = 0;
    foreach ($weights as $category => $weight) {
      if (isset($validationResults[$category]['score'])) {
        $totalScore += $validationResults[$category]['score'] * $weight;
      }
    }

    return min(1.0, max(0.0, $totalScore));
  }

  /**
   * Determines the action to take based on the confidence score.
   *
   * Returns one of: 'allow', 'allow_with_warning', 'manual_review', or 'block'
   * depending on the provided score and validation results.
   *
   * @param float $confidenceScore The global confidence score for the LLM response.
   * @param array $validationResults The array of validation results for each category.
   * @return string The recommended action.
   */
  private static function determineAction(float $confidenceScore, array $validationResults): string
  {
    if ($confidenceScore >= 0.9) {
      return 'allow';
    } elseif ($confidenceScore >= 0.7) {
      return 'allow_with_warning';
    } elseif ($confidenceScore >= 0.5) {
      return 'manual_review';
    } else {
      return 'block';
    }
  }

/**
 * Evaluates the LLM response for quality and relevance.
 *
 * Assesses the generated answer based on relevance to the question, business accuracy,
 * completeness, clarity, and optionally uses an LLM model for further evaluation.
 * Returns an array with individual scores, overall score, and improvement recommendations.
 *
 * @param string $question The question asked to the LLM.
 * @param string $result The response generated by the LLM.
 * @return array Evaluation results including scores and recommendations.
 */
  public static function evaluateLlmResponse(string $question, string $result): array
  {
    self::initLogger();
    $evaluationResults = [];

    try {
      // 1. Évaluation de la pertinence
      $relevanceScore = self::evaluateRelevance($question, $result);
      $evaluationResults['relevance'] = $relevanceScore;

      // 2. Évaluation de la précision métier
      $accuracyScore = self::evaluateBusinessAccuracy($question, $result);
      $evaluationResults['accuracy'] = $accuracyScore;

      // 3. Évaluation de la complétude
      $completenessScore = self::evaluateCompleteness($question, $result);
      $evaluationResults['completeness'] = $completenessScore;

      // 4. Évaluation de la clarté
      $clarityScore = self::evaluateClarity($result);
      $evaluationResults['clarity'] = $clarityScore;

      // Debug des scores individuels
      if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
        self::$securityLogger->logSecurityEvent("Individual Scores: Relevance=$relevanceScore, " . "Accuracy=$accuracyScore, Completeness=$completenessScore, Clarity=$clarityScore", 'info');
      }

      // 5. Utilisation du modèle d'évaluation si disponible
      if (str_starts_with(CLICSHOPPING_APP_CHATGPT_CH_MODEL, 'gpt') || str_starts_with(CLICSHOPPING_APP_CHATGPT_CH_MODEL, 'anth')) {
        $llmEvaluation = self::performLlmEvaluation($question, $result);
        $evaluationResults['llm_evaluation'] = $llmEvaluation;
      }

      // 6. Score global d'évaluation
      $overallScore = self::calculateOverallEvaluationScore($evaluationResults);
      $evaluationResults['overall_score'] = $overallScore;

      // 7. Recommandations d'amélioration
      $recommendations = self::generateRecommendations($evaluationResults);
      $evaluationResults['recommendations'] = $recommendations;

      // Sauvegarde pour analyse future
      self::saveEvaluationResults($question, $result, $evaluationResults);

      if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
        self::$securityLogger->logSecurityEvent('LLM Evaluation Results: '  . json_encode($evaluationResults), 'error');
      }

      return $evaluationResults;

    } catch (Exception $e) {
      self::$securityLogger->logSecurityEvent('Evaluation Error: '  . $e->getMessage(), 'error');

      return [
        'error' => true,
        'message' => CLICSHOPPING::getDef('error_llm_guardrails_evaluation')
      ];
    }
  }

/**
 * Evaluation using an LLM as a judge.
 *
 * This method uses a language model to assess the quality of the AI-generated response.
 * It builds an evaluation prompt based on predefined criteria, sends it to the LLM,
 * and parses the returned evaluation. The result typically includes scores and comments
 * about accuracy, reliability, relevance, and clarity.
 */
  private static function performLlmEvaluation(string $question, string $result): array
  {
    self::initLogger();
    $criteriaPrompt = self::getDefaultCriteriaEvaluatorPromptBuilder();
    $evaluationPrompt = $criteriaPrompt->getEvaluationPromptForQuestion($question, $result);

    if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
      self::$securityLogger->logSecurityEvent('LLM Evaluation Prompt: '  . $evaluationPrompt, 'error');
    }

    // Appel au modèle d'évaluation (implémentation selon votre architecture)
    try {
      $evaluationResponse = self::callEvaluationModel($evaluationPrompt);

      return self::parseLlmEvaluationResponse($evaluationResponse);
    } catch (Exception $e) {
      self::$securityLogger->logSecurityEvent('LLM Evaluation failed: '  . $e->getMessage(), 'error');

      return ['error' => 'LLM evaluation failed'];
    }
  }

 /**
  * Calls the internal LLM evaluation model with the provided prompt.
  *
  * This method sends the evaluation prompt to the LLM wrapper and returns the generated response as a string.
  * Used for automated assessment of AI-generated answers based on custom criteria.
  *
  * @param string $prompt The evaluation prompt to send to the LLM.
  * @return string The raw response from the LLM evaluation model.
  */
  private static function callEvaluationModel(string $prompt): string
  {
    self::initLogger();

    // Exemple d'appel à un wrapper interne LLM
    try {
      $chat = Gpt::getChat($prompt);
      $response = $chat->generateText($prompt);

      return trim($response);
    } catch (\Throwable $e) {
      self::$securityLogger->logSecurityEvent('LLM evaluation call failed: '  . $e->getMessage(), 'error');

      return '';
    }
  }

 /**
  * Parses the LLM evaluation response.
  *
  * Extracts and decodes a JSON block from the raw LLM response string.
  * Returns an associative array with evaluation scores and comments.
  *
  * @param string $response The raw response from the LLM evaluation model.
  * @return array Parsed evaluation data or default values if parsing fails.
  */
  private static function parseLlmEvaluationResponse(string $response): array
  {
    // Extraction brute du bloc JSON dans la réponse textuelle
    if (preg_match('/\{.*\}/s', $response, $matches)) {
      $json = $matches[0];
      $data = json_decode($json, true);
      if (is_array($data)) return $data;
    }

    return [
      'exactitude' => null,
      'fiabilité' => null,
      'pertinence' => null,
      'clarté' => null,
      'note_globale' => null,
      'commentaire' => CLICSHOPPING::getDef('error_llm_guardrails_invalid_format')
    ];
  }


 /**
  * Default evaluation prompt generator.
  *
  * Returns an anonymous class that builds an evaluation prompt for LLM assessment,
  * using the provided question and result. The prompt is used to instruct the LLM
  * to evaluate the quality and relevance of the AI-generated response.
  */
  private static function getDefaultCriteriaEvaluatorPromptBuilder(): object
  {
    return new class {
      public function getEvaluationPromptForQuestion(string $question, string $result): string
      {
        return CLICSHOPPING::getDef('llm_guardrails_prompt', ['result' => $result, 'question' => $question]);
      }
    };
  }


/**
 * Validates if the e-commerce metrics in the AI-generated response are realistic.
 *
 * Checks for suspicious growth percentages (e\.g\. excessive growth rates).
 * Returns true if metrics are within realistic bounds, false otherwise.
 *
 * @param string $result The AI-generated response to validate.
 * @return bool True if metrics are realistic, false otherwise.
 */
  private static function validateRealisticMetrics(string $result): bool
  {
    // Validation des métriques e-commerce réalistes
    // Ex: croissance > 1000% suspecte
    if (preg_match('/(\d+)%/', $result, $matches)) {
      $percentage = (int)$matches[1];

      return $percentage <= 500; // Croissance max réaliste
    }

    return true;
  }

  /**
   * Detects future dates in the AI-generated response.
   *
   * Scans the response for any references to future dates or time periods.
   * Returns an array of detected future dates or an empty array if none found.
   *
   * @param string $result The AI-generated response to scan for future dates.
   * @return array List of detected future dates.
   */
  private static function detectFutureDates(string $result): array
  {
    $futureDates = [];
    // Logique de détection des dates futures
    // Implémentation selon vos besoins spécifiques
    return $futureDates;
  }

  /**
   * Detects impossible values in the AI-generated response.
   *
   * Scans the response for any values that are unrealistic or impossible
   * in the context of e-commerce, such as percentages over 1000% or
   * other nonsensical numerical values.
   *
   * @param string $result The AI-generated response to scan for impossible values.
   * @return array List of detected impossible values.
   */
  private static function detectImpossibleValues(string $result): array
  {
    $impossibleValues = [];

    // Détection de valeurs impossibles (ex: pourcentages > 100% pour certains contextes)
    if (preg_match_all('/(\d+(?:\.\d+)?)%/', $result, $matches)) {
      foreach ($matches[1] as $value) {
        if ((float)$value > 1000) { // Pourcentage aberrant
          $impossibleValues[] = $value . '%';
        }
      }
    }

    return $impossibleValues;
  }

  /**
   * Saves the evaluation results for future analysis.
   *
   * Stores the evaluation results in a persistent storage (e\.g\. database, file, etc.)
   * for later review and analysis. This can help improve the LLM's performance over time.
   *
   * @param string $question The question asked to the LLM.
   * @param string $result The response generated by the LLM.
   * @param array $evaluation The evaluation results to save.
   */
  private static function saveEvaluationResults(string $question, string $result, array $evaluation): void
  {
    self::initLogger();

    // Sauvegarde des résultats d'évaluation pour analyse future // todo
    $data = [
      'timestamp' => date('Y-m-d H:i:s'),
      'question' => $question,
      'result' => $result,
      'evaluation' => $evaluation,
      'model' => CLICSHOPPING_APP_CHATGPT_CH_MODEL
    ];

    // Implémentation selon votre système de stockage
    if (CLICSHOPPING_APP_CHATGPT_CH_DEBUG === 'True') {
      self::$securityLogger->logSecurityEvent('Evaluation saved: '  .  json_encode($data), 'success');
    }
  }

 /**
  * Checks if the given text contains potentially malicious code.
  *
  * Scans for common attack vectors such as \<script\>, \<iframe\>, javascript: URLs, or onclick attributes.
  *
  * @param string $text The text to scan for malicious code.
  * @return bool True if malicious code is detected, false otherwise.
  */
  private static function containsMaliciousCode(string $text): bool
  {
    $patterns = ['/<script/', '/<iframe/', '/javascript:/', '/onclick=/'];

    foreach ($patterns as $pattern) {
      if (preg_match($pattern, $text)) return true;
    }

    return false;
  }

/**
 * Checks if the provided text is a valid JSON structure.
 *
 * @param string $text The text to validate as JSON.
 * @return bool True if the text contains valid JSON, false otherwise.
 */
  private static function validateJsonStructure(string $text): bool
  {
    // Si le texte contient du JSON, vérifier sa validité
    if (strpos($text, '{') !== false || strpos($text, '[') !== false) {
      return json_last_error() === JSON_ERROR_NONE;
    }

    return true; // Pas de JSON détecté
  }

  /**
   * Evaluates the relevance of the LLM response to the question.
   *
   * Compares the words in the question and the result, calculating a relevance score
   * based on the intersection of words. Returns a float between 0.0 and 1.0.
   *
   * @param string $question The question asked to the LLM.
   * @param string $result The response generated by the LLM.
   * @return float Relevance score (0.0 to 1.0).
   */
  private static function evaluateRelevance(string $question, string $result): float
  {
    $qWords = array_filter(preg_split('/\W+/u', mb_strtolower($question)));
    $rWords = array_filter(preg_split('/\W+/u', mb_strtolower($result)));

    if (empty($qWords) || empty($rWords)) return 0.0;

    $intersection = array_intersect($qWords, $rWords);
    $relevance = count($intersection) / count(array_unique($qWords));

    return min(1.0, max(0.0, $relevance));
  }

  /**
   * Evaluates the clarity of the LLM response.
   *
   * Checks for sentence structure, keyword presence, and overall readability.
   * Returns a float score between 0.0 and 1.0 based on these criteria.
   *
   * @param string $result The response generated by the LLM.
   * @return float Clarity score (0.0 to 1.0).
   */
  private static function evaluateCompleteness(string $question, string $result): float
  {
    $sentenceCount = preg_match_all('/[.!?]\s/u', $result);
    $keywordCount = preg_match_all('/\b(produit|prix|délai|livraison|stock)\b/ui', $result);

    $score = 0.2 * min(5, $sentenceCount) + 0.2 * min(5, $keywordCount);

    return min(1.0, max(0.0, $score));
  }

  /**
   * Evaluates the clarity of the LLM response.
   *
   * Analyzes sentence length and structure to determine clarity.
   * Returns a float score between 0.0 and 1.0 based on the analysis.
   *
   * @param string $text The text to evaluate for clarity.
   * @return float Clarity score (0.0 to 1.0).
   */
  private static function evaluateClarity(string $text): float
  {
    $sentences = preg_split('/[.!?]+/u', $text);
    $longSentences = array_filter($sentences, fn($s) => mb_strlen($s) > 200);

    $penalty = count($longSentences) / max(1, count($sentences));
    $score = 1.0 - $penalty;

    return min(1.0, max(0.0, $score));
  }

  /**
   * Validates percentages in the AI-generated response.
   *
   * Checks if all percentages are within a realistic range (0% to 500%).
   * Returns true if all percentages are valid, false otherwise.
   *
   * @param string $result The AI-generated response to validate.
   * @return bool True if all percentages are valid, false otherwise.
   */
  private static function validatePercentages(string $result): bool
  {
    preg_match_all('/(\d+(?:[.,]\d+)?)%/', $result, $matches);

    foreach ($matches[1] as $value) {
      $val = (float)str_replace(',', '.', $value);
      if ($val < 0 || $val > 500) return false;
    }

    return true;
  }

  /**
   * Validates sales figures in the AI-generated response.
   *
   * Checks if the sales figures are realistic and within acceptable limits.
   * Returns true if all figures are valid, false otherwise.
   *
   * @param array $numbers Array of sales figures extracted from the response.
   * @return bool True if all sales figures are valid, false otherwise.
   */
  private static function validateSalesFigures(array $numbers): bool
  {
    foreach ($numbers as $n) {
      $val = (float)str_replace([',', ' '], '', $n);
      if ($val > 10000000) return false;
    }

    return true;
  }

  /**
   * Validates percentage ranges in the AI-generated response.
   *
   * Checks if all percentages are within the range of 0% to 1000%.
   * Returns true if all percentages are valid, false otherwise.
   *
   * @param string $result The AI-generated response to validate.
   * @return bool True if all percentages are valid, false otherwise.
   */
  private static function validatePercentageRange(string $result): bool
  {
    preg_match_all('/(\d+(?:[.,]\d+)?)%/', $result, $matches);
    foreach ($matches[1] as $value) {
      $val = (float)str_replace(',', '.', $value);
      if ($val < 0 || $val > 1000) return false;
    }

    return true;
  }

  /**
   * Validates mathematical consistency in the AI-generated response.
   *
   * Checks if the totals and subtotals in the response are consistent.
   * Returns true if the math is consistent, false otherwise.
   *
   * @param string $result The AI-generated response to validate.
   * @return bool True if the math is consistent, false otherwise.
   */
  private static function validateMathConsistency(string $result): bool
  {
    // Heuristique simple : égalité entre sous-totaux et totaux
    // Exemple : "Total: 100€, Produit A: 60€, Produit B: 40€"
    if (preg_match_all('/(\d+(?:[.,]\d+)?)\s*(€|\$)?/', $result, $matches)) {
      $values = array_map(fn($v) => (float)str_replace(',', '.', $v), $matches[1]);

      if (count($values) >= 3) {
        $sum = array_sum(array_slice($values, 1));
        $delta = abs($values[0] - $sum);

        return $delta < 1.0;
      }
    }

    return true;
  }

  /**
   * Validates currency amounts in the AI-generated response.
   *
   * Checks if all currency amounts are within a realistic range (e\.g\. 0 to 1,000,000).
   * Returns true if all amounts are valid, false otherwise.
   *
   * @param string $result The AI-generated response to validate.
   * @return bool True if all currency amounts are valid, false otherwise.
   */
  private static function validateCurrencyAmounts(string $result): bool
  {
    preg_match_all('/[€$]\s*(\d+(?:[.,]\d+)?)/', $result, $matches);

    foreach ($matches[1] as $value) {
      $amount = (float)str_replace(',', '.', $value);
      if ($amount < 0 || $amount > 1000000) return false;
    }

    return true;
  }

  /**
   * Validates the attribution quality in the AI-generated response.
   *
   * Checks for the presence of sources, citations, and references.
   * Returns a float score between 0.0 and 1.0 based on the number of citations.
   *
   * @param string $result The AI-generated response to validate.
   * @return float Attribution score (0.0 to 1.0).
   */
  private static function validateAttribution(string $result): float
  {
    $citations = substr_count($result, 'source:') + substr_count($result, '(voir') + preg_match_all('/\[.*?\]/', $result);

    if ($citations === 0) return 0.0;

    return min(1.0, $citations / 3);
  }

  /**
   * Evaluates the business accuracy of the LLM response.
   *
   * Checks for unrealistic growth rates, fictitious sales, non-existent products,
   * and excessive monetary amounts. Returns a float score between 0.0 and 1.0.
   *
   * @param string $question The question asked to the LLM.
   * @param string $result The response generated by the LLM.
   * @return float Business accuracy score (0.0 to 1.0).
   */
  private static function evaluateBusinessAccuracy(string $question, string $result): float
  {
    $patterns = [
      '/croissance\s+de\s+\d{3,}\s*%/i',         // croissance absurde
      '/ventes?\s+fictives?/i',                  // hallucination explicite
      '/produit[s]?\s+inexistant[s]?/i',         // produit halluciné
      '/\b\d{4,}\s*(€|\$|euros|dollars)\b/i'     // montant excessif
    ];

    $penalties = 0;

    foreach ($patterns as $pattern) {
      if (preg_match($pattern, $result)) {
        $penalties += 1;
      }
    }

    $score = 1.0 - min(1.0, $penalties * 0.25);

    return max(0.0, $score);
  }

/**
   * Calculates the overall evaluation score based on individual scores.
   *
   * Aggregates the scores from relevance, accuracy, completeness, clarity,
   * and LLM evaluation using predefined weights. Returns a float score between 0.0 and 1.0.
   *
   * @param array $evaluationResults Array of evaluation results for each category.
   * @return float Overall evaluation score (0.0 to 1.0).
   */
  private static function calculateOverallEvaluationScore(array $evaluationResults): float
  {
    $weights = [
      'relevance' => 0.25,
      'accuracy' => 0.3,
      'completeness' => 0.2,
      'clarity' => 0.15,
      'llm_evaluation' => 0.1
    ];

    $total = 0;

    foreach ($weights as $k => $w) {
      if (isset($evaluationResults[$k]) && is_numeric($evaluationResults[$k])) {
        $total += $evaluationResults[$k] * $w;
      } elseif ($k === 'llm_evaluation' && isset($evaluationResults[$k]['scores'])) {
        $s = $evaluationResults[$k]['scores'];
        $mean = array_sum($s) / count($s);
        $total += ($mean / 5) * $w;
      }
    }

    return min(1.0, $total);
  }

  /**
   * Generates improvement recommendations based on evaluation results.
   *
   * Analyzes the evaluation results and returns an array of recommendations
   * for improving the LLM response quality, focusing on relevance, accuracy,
   * completeness, and clarity.
   *
   * @param array $evaluationResults The evaluation results from the LLM response.
   * @return array List of improvement recommendations.
   */
  private static function generateRecommendations(array $evaluationResults): array
  {
    $reco = [];
    if (($evaluationResults['relevance'] ?? 1) < 0.7) $reco[] = CLICSHOPPING::getDef('llm_guardrails_prompt_relevance');
    if (($evaluationResults['accuracy'] ?? 1) < 0.7) $reco[] = CLICSHOPPING::getDef('llm_guardrails_prompt_accuracy');
    if (($evaluationResults['completeness'] ?? 1) < 0.7) $reco[] = CLICSHOPPING::getDef('llm_guardrails_prompt_completeness');
    if (($evaluationResults['clarity'] ?? 1) < 0.7) $reco[] = CLICSHOPPING::getDef('llm_guardrails_prompt_clarity');

    return $reco;
  }


}
