"""Rubric class for defining evaluation criteria."""

from typing import List, Dict, Any, Optional
from dataclasses import dataclass


@dataclass
class Rubric:
    """
    Defines evaluation criteria for judges.
    
    Specifies what to evaluate, how to score, and relative importance.
    
    Examples:
        >>> # Basic rubric
        >>> rubric = Rubric(
        ...     criteria=["correctness", "clarity"],
        ...     scale="0-1",
        ...     instructions="Be strict and fair"
        ... )
        
        >>> # With descriptions
        >>> rubric = Rubric(
        ...     criteria=["factual_accuracy", "tone"],
        ...     scale="0-1",
        ...     criteria_descriptions={
        ...         "factual_accuracy": "All facts must be verifiable and correct",
        ...         "tone": "Professional and empathetic, appropriate for customer support"
        ...     },
        ...     instructions="Penalize any factual errors heavily"
        ... )
        
        >>> # With weights (safety most important)
        >>> rubric = Rubric(
        ...     criteria=["safety", "helpfulness", "clarity"],
        ...     scale="0-1",
        ...     criteria_descriptions={
        ...         "safety": "Must not contain harmful or dangerous information",
        ...         "helpfulness": "Provides useful and actionable information",
        ...         "clarity": "Easy to understand and well-structured"
        ...     },
        ...     weights={"safety": 3.0, "helpfulness": 2.0, "clarity": 1.0},
        ...     instructions="Safety is critical, any safety violation should score 0"
        ... )

        **Note on Confidence:**
        Judges will return confidence scores for each criterion. However, LLM
        self-assessed confidence is often poorly calibrated. Use confidence as
        a rough signal rather than a precise probability. For better uncertainty
        estimates, use EnsembleJudge with multiple models.
    """
    # What to evaluate
    criteria: List[str]
    
    # How to score (e.g., "0-1", "1-5", "pass/fail")
    scale: str
    
    # Instructions for the judge
    instructions: str = "Evaluate objectively and thoroughly."
    
    # Optional descriptions for domain-specific criteria
    criteria_descriptions: Optional[Dict[str, str]] = None
    
    # Optional weights (relative importance - used by FRAMEWORK, not LLM)
    weights: Optional[Dict[str, float]] = None
    
    # Optional few-shot examples
    examples: Optional[List[Dict[str, Any]]] = None
    
    def __post_init__(self):
        """Validate rubric after initialization."""
        
        # Validate criteria not empty
        if not self.criteria:
            raise ValueError("Rubric must have at least one criterion")
        
        # Validate weights if provided
        if self.weights:
            # All weights must be positive
            if any(w <= 0 for w in self.weights.values()):
                raise ValueError("All weights must be positive")
            
            # All criteria must have weights
            if set(self.weights.keys()) != set(self.criteria):
                raise ValueError(
                    f"Must provide weights for all criteria. "
                    f"Criteria: {self.criteria}, "
                    f"Weights: {list(self.weights.keys())}"
                )
        
        # Validate descriptions if provided
        if self.criteria_descriptions:
            if set(self.criteria_descriptions.keys()) != set(self.criteria):
                raise ValueError(
                    f"Must provide descriptions for all criteria or none. "
                    f"Criteria: {self.criteria}, "
                    f"Descriptions: {list(self.criteria_descriptions.keys())}"
                )
    
    def to_prompt(self) -> str:
        """
        Render rubric as prompt text for LLM.
        
        NOTE: Weights are NOT included in prompt - they're used by framework
        to compute overall score after LLM returns per-criterion scores.
        
        Returns:
            Formatted rubric description for LLM
        """
        lines = []
        
        # Criteria with descriptions
        lines.append("CRITERIA:")
        for criterion in self.criteria:
            if self.criteria_descriptions:
                desc = self.criteria_descriptions[criterion]
                lines.append(f"  - {criterion}: {desc}")
            else:
                lines.append(f"  - {criterion}")
        
        lines.append(f"\nSCALE: {self.scale}")
        lines.append(f"INSTRUCTIONS: {self.instructions}")
        
        # NOTE: Weights NOT included - used by framework only
        
        if self.examples:
            lines.append(f"\nEXAMPLES: {len(self.examples)} provided")
        
        return "\n".join(lines)