"""
reviewer
収集した二値画像を閲覧・仕分けするレビュー GUI

raw/ のセッションを開き，画像を1枚ずつ確認して
confirmed/ に確定 or 削除する

キー操作:
    ←: 前の画像に戻る
    →/Enter: 現在のラベルで確定して次へ
    M: ラベルを反転（intersection ↔ normal）
    Delete/Backspace: 画像を削除して次へ
    Escape: 終了
"""

from pathlib import Path

import cv2
import numpy as np
from PySide6.QtCore import Qt
from PySide6.QtGui import QImage, QKeyEvent, QPixmap
from PySide6.QtWidgets import (
    QFileDialog,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from pc.data.collector import (
    CONFIRMED_DIR,
    LABEL_INTERSECTION,
    LABEL_NORMAL,
    RAW_DIR,
)

# 画像表示の拡大倍率
REVIEW_SCALE: int = 16


def _load_image(path: Path) -> np.ndarray:
    """画像ファイルを読み込む

    Args:
        path: 画像ファイルのパス

    Returns:
        グレースケール画像
    """
    img = cv2.imread(str(path), cv2.IMREAD_GRAYSCALE)
    if img is None:
        return np.zeros((30, 40), dtype=np.uint8)
    return img


def _count_confirmed() -> tuple[int, int]:
    """confirmed/ 内の画像数を集計する

    Returns:
        (intersection の枚数, normal の枚数)
    """
    n_int = len(
        list(
            (CONFIRMED_DIR / LABEL_INTERSECTION).glob(
                "*.png",
            ),
        )
    ) if (CONFIRMED_DIR / LABEL_INTERSECTION).is_dir() else 0
    n_norm = len(
        list(
            (CONFIRMED_DIR / LABEL_NORMAL).glob("*.png"),
        )
    ) if (CONFIRMED_DIR / LABEL_NORMAL).is_dir() else 0
    return n_int, n_norm


def _next_confirmed_index(label: str) -> int:
    """confirmed/ の次の連番を返す

    Args:
        label: ラベル名

    Returns:
        次の連番（1始まり）
    """
    label_dir = CONFIRMED_DIR / label
    if not label_dir.is_dir():
        return 1
    existing = list(label_dir.glob("*.png"))
    if not existing:
        return 1
    max_num = 0
    for p in existing:
        try:
            num = int(p.stem.split("_")[0])
            max_num = max(max_num, num)
        except ValueError:
            continue
    return max_num + 1


class _ImageEntry:
    """画像1枚の情報を保持する

    Attributes:
        path: ファイルパス
        label: 現在のラベル（intersection / normal）
    """

    def __init__(self, path: Path, label: str) -> None:
        self.path = path
        self.label = label


class ReviewWindow(QMainWindow):
    """データ仕分けウィンドウ"""

    def __init__(
        self,
        session_dir: Path | None = None,
    ) -> None:
        super().__init__()
        self._entries: list[_ImageEntry] = []
        self._index: int = 0

        self._setup_ui()

        if session_dir is not None:
            self._load_session(session_dir)

    def _setup_ui(self) -> None:
        """UI を構築する"""
        self.setWindowTitle("データ仕分け")

        central = QWidget()
        self.setCentralWidget(central)
        root = QVBoxLayout(central)

        # セッション選択
        top_bar = QHBoxLayout()
        self._open_btn = QPushButton("セッションを開く")
        self._open_btn.clicked.connect(self._on_open)
        top_bar.addWidget(self._open_btn)

        self._session_label = QLabel("未選択")
        self._session_label.setStyleSheet(
            "font-size: 13px; color: #888;"
        )
        top_bar.addWidget(self._session_label, stretch=1)
        root.addLayout(top_bar)

        # 画像表示
        self._image_label = QLabel(
            "セッションを選択してください",
        )
        self._image_label.setAlignment(
            Qt.AlignmentFlag.AlignCenter,
        )
        self._image_label.setMinimumSize(
            40 * REVIEW_SCALE, 30 * REVIEW_SCALE,
        )
        self._image_label.setStyleSheet(
            "background-color: #222;"
            " color: #aaa; font-size: 16px;"
        )
        root.addWidget(self._image_label)

        # ラベル・進捗表示
        self._info_label = QLabel("")
        self._info_label.setAlignment(
            Qt.AlignmentFlag.AlignCenter,
        )
        self._info_label.setStyleSheet(
            "font-size: 16px; font-family: monospace;"
            " padding: 6px;"
        )
        root.addWidget(self._info_label)

        # 操作ボタン
        btn_bar = QHBoxLayout()

        self._prev_btn = QPushButton("← 戻る (←)")
        self._prev_btn.clicked.connect(self._go_prev)
        btn_bar.addWidget(self._prev_btn)

        self._flip_btn = QPushButton("ラベル反転 (M)")
        self._flip_btn.clicked.connect(self._flip_label)
        btn_bar.addWidget(self._flip_btn)

        self._delete_btn = QPushButton("削除 (Del)")
        self._delete_btn.clicked.connect(
            self._delete_current,
        )
        btn_bar.addWidget(self._delete_btn)

        self._confirm_btn = QPushButton("確定 → (→)")
        self._confirm_btn.clicked.connect(
            self._confirm_current,
        )
        btn_bar.addWidget(self._confirm_btn)

        root.addLayout(btn_bar)

        # 集計表示（raw 残り + confirmed 累計）
        self._summary_label = QLabel("")
        self._summary_label.setAlignment(
            Qt.AlignmentFlag.AlignCenter,
        )
        self._summary_label.setStyleSheet(
            "font-size: 13px; font-family: monospace;"
            " color: #888; padding: 4px;"
        )
        root.addWidget(self._summary_label)

        # 操作ガイド
        guide = QLabel(
            "←: 戻る  →/Enter: 確定  "
            "M: ラベル反転  Del/BS: 削除  Esc: 終了"
        )
        guide.setAlignment(Qt.AlignmentFlag.AlignCenter)
        guide.setStyleSheet(
            "font-size: 12px; color: #666;"
        )
        root.addWidget(guide)

    # ── セッション読み込み ────────────────────────────────

    def _on_open(self) -> None:
        """セッション選択ダイアログを開く"""
        raw_dir = str(RAW_DIR)
        dir_path = QFileDialog.getExistingDirectory(
            self, "セッションディレクトリを選択",
            raw_dir,
        )
        if dir_path:
            self._load_session(Path(dir_path))

    def _load_session(self, session_dir: Path) -> None:
        """セッションの画像一覧を読み込む

        Args:
            session_dir: セッションディレクトリのパス
        """
        self._entries.clear()
        self._index = 0

        for label in (LABEL_INTERSECTION, LABEL_NORMAL):
            label_dir = session_dir / label
            if not label_dir.is_dir():
                continue
            for img_path in sorted(label_dir.glob("*.png")):
                self._entries.append(
                    _ImageEntry(img_path, label),
                )

        self._session_label.setText(
            f"セッション: {session_dir.name}"
        )
        self._update_display()

    # ── ナビゲーション ────────────────────────────────────

    def _go_prev(self) -> None:
        """前の画像に戻る"""
        if len(self._entries) == 0:
            return
        self._index = max(0, self._index - 1)
        self._update_display()

    # ── ラベル操作 ────────────────────────────────────────

    def _flip_label(self) -> None:
        """現在の画像のラベルを反転する（raw 内で移動）"""
        if len(self._entries) == 0:
            return

        entry = self._entries[self._index]
        new_label = (
            LABEL_NORMAL
            if entry.label == LABEL_INTERSECTION
            else LABEL_INTERSECTION
        )
        entry.label = new_label
        self._update_display()

    def _confirm_current(self) -> None:
        """現在の画像を confirmed/ に移動して次へ"""
        if len(self._entries) == 0:
            return

        entry = self._entries[self._index]
        label = entry.label

        # confirmed/ 内の保存先を確保
        dest_dir = CONFIRMED_DIR / label
        dest_dir.mkdir(parents=True, exist_ok=True)
        idx = _next_confirmed_index(label)
        dest_path = dest_dir / f"{idx:06d}.png"

        # raw → confirmed へ移動
        entry.path.rename(dest_path)
        self._entries.pop(self._index)

        if len(self._entries) == 0:
            self._index = 0
        elif self._index >= len(self._entries):
            self._index = len(self._entries) - 1

        self._update_display()

    def _delete_current(self) -> None:
        """現在の画像を削除して次へ"""
        if len(self._entries) == 0:
            return

        entry = self._entries[self._index]
        entry.path.unlink(missing_ok=True)
        self._entries.pop(self._index)

        if len(self._entries) == 0:
            self._index = 0
        elif self._index >= len(self._entries):
            self._index = len(self._entries) - 1

        self._update_display()

    # ── 表示更新 ──────────────────────────────────────────

    def _update_display(self) -> None:
        """画像・ラベル・進捗の表示を更新する"""
        n = len(self._entries)
        if n == 0:
            self._image_label.setText("画像がありません")
            self._info_label.setText("")
            self._update_summary()
            return

        entry = self._entries[self._index]

        # 画像表示
        img = _load_image(entry.path)
        h, w = img.shape[:2]
        qimg = QImage(
            img.data, w, h, w,
            QImage.Format.Format_Grayscale8,
        )
        pixmap = QPixmap.fromImage(qimg).scaled(
            w * REVIEW_SCALE,
            h * REVIEW_SCALE,
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.FastTransformation,
        )
        self._image_label.setPixmap(pixmap)

        # ラベル色分け
        if entry.label == LABEL_INTERSECTION:
            color = "#f44"
            label_text = "intersection"
        else:
            color = "#4a4"
            label_text = "normal"

        self._info_label.setText(
            f'<span style="color:{color};"'
            f' font-weight:bold;">'
            f"{label_text}</span>"
            f"    {self._index + 1} / {n}"
            f"    [{entry.path.name}]"
        )

        self._update_summary()

    def _update_summary(self) -> None:
        """集計表示を更新する"""
        # raw 残り
        n_raw_int = sum(
            1 for e in self._entries
            if e.label == LABEL_INTERSECTION
        )
        n_raw_norm = len(self._entries) - n_raw_int

        # confirmed 累計
        c_int, c_norm = _count_confirmed()

        self._summary_label.setText(
            f"[raw] int: {n_raw_int}  norm: {n_raw_norm}"
            f"    [confirmed] int: {c_int}"
            f"  norm: {c_norm}"
        )

    # ── キー操作 ──────────────────────────────────────────

    def keyPressEvent(self, event: QKeyEvent) -> None:
        """キー押下時の処理"""
        key = event.key()
        if key == Qt.Key.Key_Left:
            self._go_prev()
        elif key in (
            Qt.Key.Key_Right, Qt.Key.Key_Return,
        ):
            self._confirm_current()
        elif key == Qt.Key.Key_M:
            self._flip_label()
        elif key in (
            Qt.Key.Key_Delete, Qt.Key.Key_Backspace,
        ):
            self._delete_current()
        elif key == Qt.Key.Key_Escape:
            self.close()
        else:
            super().keyPressEvent(event)
