"""
Translation metrics for sign language to text evaluation.

Supports BLEU, ROUGE, and other standard MT metrics.
"""

import numpy as np


def translation_metrics(
    predictions: list[str],
    references: list[str],
    metrics: list[str] | None = None,
) -> dict[str, float]:
    """
    Compute translation metrics.

    Args:
        predictions: Model outputs (translated text)
        references: Ground truth translations
        metrics: Which metrics to compute. Default: all available.
                 Options: 'bleu1', 'bleu2', 'bleu3', 'bleu4', 'rouge_l'

    Returns:
        Dictionary of metric names to scores

    Example:
        >>> preds = ["hello world", "how are you"]
        >>> refs = ["hello there", "how are you doing"]
        >>> scores = translation_metrics(preds, refs)
        >>> print(f"BLEU-4: {scores['bleu4']:.2f}")
    """
    if len(predictions) != len(references):
        raise ValueError(
            f"Length mismatch: {len(predictions)} predictions vs {len(references)} references"
        )

    if metrics is None:
        metrics = ["bleu1", "bleu2", "bleu3", "bleu4", "rouge_l"]

    results = {}

    # BLEU scores
    bleu_metrics = [m for m in metrics if m.startswith("bleu")]
    if bleu_metrics:
        bleu_scores = _compute_bleu(predictions, references)
        for m in bleu_metrics:
            if m in bleu_scores:
                results[m] = bleu_scores[m]

    # ROUGE
    if "rouge_l" in metrics:
        results["rouge_l"] = rouge_score(predictions, references)

    return results


def bleu_score(
    predictions: list[str],
    references: list[str],
    n: int = 4,
    lowercase: bool = True,
) -> float:
    """
    Compute BLEU-n score.

    Args:
        predictions: Model outputs
        references: Ground truth
        n: N-gram order (1-4)
        lowercase: Whether to lowercase before comparison

    Returns:
        BLEU-n score (0-100)
    """
    try:
        import sacrebleu
    except ImportError:
        raise ImportError("sacrebleu required: pip install sltk[metrics]")

    if lowercase:
        predictions = [p.lower() for p in predictions]
        references = [r.lower() for r in references]

    # SacreBLEU expects references as list of reference documents
    # Each document is a list of all references for each sentence position
    # For single reference per sentence: [references] where references = [ref1, ref2, ...]
    bleu = sacrebleu.corpus_bleu(
        predictions,
        [references],
        smooth_method="exp",
        smooth_value=0.0,
    )

    if n == 4:
        return bleu.score
    elif 1 <= n <= len(bleu.precisions):
        return bleu.precisions[n - 1]
    else:
        raise ValueError(f"Invalid n-gram order: {n}")


def rouge_score(
    predictions: list[str],
    references: list[str],
    variant: str = "rougeL",
) -> float:
    """
    Compute ROUGE score.

    Args:
        predictions: Model outputs
        references: Ground truth
        variant: ROUGE variant ('rouge1', 'rouge2', 'rougeL')

    Returns:
        ROUGE F1 score (0-1)
    """
    try:
        from rouge_score import rouge_scorer
    except ImportError:
        raise ImportError("rouge-score required: pip install sltk[metrics]")

    scorer = rouge_scorer.RougeScorer([variant], use_stemmer=True)

    scores = []
    for pred, ref in zip(predictions, references):
        score = scorer.score(ref, pred)
        scores.append(score[variant].fmeasure)

    return float(np.mean(scores))


def _compute_bleu(
    predictions: list[str],
    references: list[str],
) -> dict[str, float]:
    """Compute BLEU scores for all n-gram orders."""
    try:
        import sacrebleu
    except ImportError:
        raise ImportError("sacrebleu required: pip install sltk[metrics]")

    # SacreBLEU expects references as list of reference documents
    # Each document is a list of all references for each sentence position
    # For single reference per sentence: [references] where references = [ref1, ref2, ...]
    bleu = sacrebleu.corpus_bleu(
        [p.lower() for p in predictions],
        [[r.lower() for r in references]],
        smooth_method="exp",
        smooth_value=0.0,
    )

    results = {"bleu4": bleu.score}
    for i, precision in enumerate(bleu.precisions[:4], 1):
        results[f"bleu{i}"] = precision

    return results


def wer(predictions: list[str], references: list[str]) -> float:
    """
    Compute Word Error Rate.

    Args:
        predictions: Model outputs
        references: Ground truth

    Returns:
        WER score (0-1, lower is better)
    """
    total_words = 0
    total_errors = 0

    for pred, ref in zip(predictions, references):
        pred_words = pred.lower().split()
        ref_words = ref.lower().split()

        # Simple Levenshtein distance
        errors = _levenshtein_distance(pred_words, ref_words)
        total_errors += errors
        total_words += len(ref_words)

    return total_errors / max(total_words, 1)


def _levenshtein_distance(s1: list[str], s2: list[str]) -> int:
    """Compute Levenshtein distance between two sequences."""
    if len(s1) < len(s2):
        return _levenshtein_distance(s2, s1)

    if len(s2) == 0:
        return len(s1)

    previous_row: list[int] = list(range(len(s2) + 1))
    for i, c1 in enumerate(s1):
        current_row = [i + 1]
        for j, c2 in enumerate(s2):
            insertions = previous_row[j + 1] + 1
            deletions = current_row[j] + 1
            substitutions = previous_row[j] + (c1 != c2)
            current_row.append(min(insertions, deletions, substitutions))
        previous_row = current_row

    return previous_row[-1]
