"""交差検証の図（学習曲線・混同行列）.

`figures/` は回収対象であるため，集計値の図だけを描き，画像そのものは描かない（SPEC_01
「実験の出力」）．`data/survey_figures.py` と同じく pyplot を使わず `Figure` ＋
`FigureCanvasAgg` で描く．ラベルは日本語フォントの有無に左右されないよう英字で書く．
"""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from pathlib import Path

import numpy as np
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure

from tias_judge_model.data.classes import CLASS_SLUGS

LEARNING_CURVES_FILENAME = "learning_curves.png"
_DPI = 120


def confusion_matrix_filename(subset: str) -> str:
    """部分集合の混同行列の図のファイル名を返す（例: `confusion_matrix_all.png`）."""
    return f"confusion_matrix_{subset}.png"


def plot_learning_curves(history: Sequence[Mapping[str, float]], path: Path) -> None:
    """fold ごとの学習曲線（損失・検証 macro F1）を描いて PNG に保存する.

    Args:
        history: エポックごとの記録（`fold`・`epoch`・`train_loss`・`val_loss`・`val_macro_f1`）．
        path: 出力先の PNG パス．親ディレクトリは作成される．
    """
    folds = sorted({int(h["fold"]) for h in history})
    fig = Figure(figsize=(12, 5), dpi=_DPI)
    FigureCanvasAgg(fig)
    ax_loss, ax_f1 = fig.subplots(1, 2)
    for fold in folds:
        rows = [h for h in history if int(h["fold"]) == fold]
        epochs = [int(h["epoch"]) for h in rows]
        line = ax_loss.plot(epochs, [h["train_loss"] for h in rows], label=f"fold{fold} train")[0]
        ax_loss.plot(
            epochs,
            [h["val_loss"] for h in rows],
            linestyle="--",
            color=line.get_color(),
            label=f"fold{fold} val",
        )
        ax_f1.plot(epochs, [h["val_macro_f1"] for h in rows], label=f"fold{fold}")
    ax_loss.set_xlabel("epoch")
    ax_loss.set_ylabel("loss")
    ax_loss.set_title("loss (solid: train, dashed: val)")
    ax_loss.legend(fontsize=7)
    ax_f1.set_xlabel("epoch")
    ax_f1.set_ylabel("val macro F1")
    ax_f1.set_ylim(0.0, 1.0)
    ax_f1.set_title("validation macro F1")
    ax_f1.legend(fontsize=7)
    fig.tight_layout()
    path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(path)


def plot_confusion_matrix(matrix: Sequence[Sequence[int]], title: str, path: Path) -> None:
    """混同行列（件数と行方向の割合）を描いて PNG に保存する.

    Args:
        matrix: 5×5 の混同行列（行が正解・列が予測．クラス ID 順）．
        title: 図のタイトル．
        path: 出力先の PNG パス．親ディレクトリは作成される．
    """
    cm = np.asarray(matrix, dtype=np.float64)
    row_sums = cm.sum(axis=1, keepdims=True)
    ratio = np.divide(cm, row_sums, out=np.zeros_like(cm), where=row_sums > 0)
    fig = Figure(figsize=(7, 6), dpi=_DPI)
    FigureCanvasAgg(fig)
    ax = fig.subplots()
    image = ax.imshow(ratio, vmin=0.0, vmax=1.0, cmap="Blues")
    fig.colorbar(image, ax=ax, label="row ratio")
    n = len(CLASS_SLUGS)
    ax.set_xticks(range(n), CLASS_SLUGS, rotation=30)
    ax.set_yticks(range(n), CLASS_SLUGS)
    ax.set_xlabel("predicted")
    ax.set_ylabel("true")
    ax.set_title(title)
    for i in range(n):
        for j in range(n):
            color = "white" if ratio[i, j] > 0.5 else "black"
            ax.text(
                j,
                i,
                f"{int(cm[i, j])}\n{ratio[i, j]:.2f}",
                ha="center",
                va="center",
                color=color,
                fontsize=8,
            )
    fig.tight_layout()
    path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(path)
