import re
import json
from dataclasses import dataclass
from typing import Callable, List, Dict, Any


@dataclass
class EvalExample:
    input: str
    expected: str


def exact_match_metric(prediction: str, expected: str) -> float:
    return float(prediction.strip().lower() == expected.strip().lower())


def contains_metric(prediction: str, expected: str) -> float:
    return float(expected.strip().lower() in prediction.lower())


def evaluate_prompt_template(
    template: str,
    examples: List[EvalExample],
    model_fn: Callable[[str], str],
    metric: Callable[[str, str], float] = exact_match_metric,
) -> Dict[str, Any]:
    scores = []
    for ex in examples:
        prompt = template.format(input=ex.input)
        prediction = model_fn(prompt)
        scores.append(metric(prediction, ex.expected))
    return {
        "mean_score": sum(scores) / len(scores),
        "n_examples": len(examples),
        "scores": scores,
    }


def validate_json_schema(text: str, required_keys: List[str]) -> bool:
    """A minimal structured-output validator: does the model's
    output parse as JSON and contain every required key?"""
    try:
        obj = json.loads(text)
    except json.JSONDecodeError:
        return False
    return all(key in obj for key in required_keys)
