"""ML model evaluation utilities."""

from __future__ import annotations

from typing import Any

import numpy as np
import pandas as pd
from sklearn.metrics import classification_report, confusion_matrix


def full_classification_report(
    model: Any,
    X_test: np.ndarray,
    y_test: np.ndarray,
    target_names: list[str] | None = None,
    output_dict: bool = False,
) -> str | dict:
    """Return precision, recall, F1, support per class. Wraps sklearn classification_report."""
    y_pred = model.predict(X_test)
    return classification_report(
        y_test,
        y_pred,
        target_names=target_names,
        output_dict=output_dict,
        zero_division=0,
    )


def error_analysis(
    model: Any,
    X_test: np.ndarray,
    y_test: np.ndarray,
    original_texts: list[str] | np.ndarray | None = None,
    top_n: int = 10,
    class_names: list[str] | None = None,
) -> pd.DataFrame:
    """Return a DataFrame of misclassified samples with true label, predicted label, and optional original text."""
    y_pred = model.predict(X_test)
    y_test = np.asarray(y_test).ravel()
    y_pred = np.asarray(y_pred).ravel()
    mask = y_test != y_pred
    indices = np.where(mask)[0][:top_n]
    rows = []
    for i in indices:
        true_lab = y_test[i]
        pred_lab = y_pred[i]
        if class_names:
            true_lab = class_names[true_lab] if true_lab < len(class_names) else str(true_lab)
            pred_lab = class_names[pred_lab] if pred_lab < len(class_names) else str(pred_lab)
        row = {"true_label": true_lab, "predicted_label": pred_lab}
        if original_texts is not None:
            texts = np.asarray(original_texts)
            row["text"] = texts[i] if i < len(texts) else ""
        rows.append(row)
    return pd.DataFrame(rows)
