"""Model-version compare dialog (docs/provenance/00, Phase 3b).

Shows the structural diff between two versions of the same model (two stored
snapshots, or a stored snapshot vs the current on-disk file) using
``model_summary_diff`` — added/removed/renamed nodes, weight changes, and the
predicted field-mapping breakage that would result from switching versions.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional

from ..model_summary_diff import diff_model_summaries
from ..translator import tr
from ..qt_compat import (
    QDialog,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QTreeWidget,
    QTreeWidgetItem,
    QVBoxLayout,
    Qt,
)


class ModelVersionCompareDialog(QDialog):
    """Structural diff between two model summaries."""

    def __init__(
        self,
        *,
        label_old: str,
        summary_old: Dict[str, Any],
        label_new: str,
        summary_new: Dict[str, Any],
        field_mappings: Optional[List[Any]] = None,
        parent=None,
    ):
        super().__init__(parent)
        self.setWindowTitle(tr("provenance.compare_versions_title"))
        self.resize(760, 620)
        self._diff = diff_model_summaries(
            summary_old, summary_new, field_mappings=field_mappings
        )
        self._label_old = label_old
        self._label_new = label_new
        self._build()

    # ------------------------------------------------------------------
    def _build(self) -> None:
        layout = QVBoxLayout()
        self.setLayout(layout)

        kind = self._diff.get("model_kind") or "?"
        header = QLabel(
            f"<b>{self._label_old}</b>  →  <b>{self._label_new}</b><br>"
            f"<span style='color:#666;'>model kind: {kind}</span>"
        )
        header.setTextFormat(Qt.TextFormat.RichText)
        header.setWordWrap(True)
        layout.addWidget(header)

        if self._diff.get("name_changed"):
            name_label = QLabel(
                f"Model name changed: '{self._diff.get('name_old')}' → '{self._diff.get('name_new')}'"
            )
            name_label.setWordWrap(True)
            layout.addWidget(name_label)

        breakage = self._diff.get("mapping_breakage") or []
        if breakage:
            lines = ", ".join(
                f"{b['source_column']} → {b['target_variable']}" for b in breakage
            )
            banner = QLabel(
                f"⚠ {len(breakage)} field mapping(s) would break (target no longer exists): {lines}"
            )
            banner.setWordWrap(True)
            banner.setStyleSheet(
                "color: #92400e; background-color: #fef3c7; padding: 6px; border-radius: 4px;"
            )
            layout.addWidget(banner)

        if not self._diff.get("has_changes"):
            ok = QLabel("✓ No structural differences between these two model versions.")
            ok.setStyleSheet("color: #166534; padding: 6px;")
            layout.addWidget(ok)

        tree = QTreeWidget()
        tree.setHeaderLabels(["Change", "Detail"])
        tree.setColumnWidth(0, 320)
        layout.addWidget(tree, 1)
        self._populate_tree(tree)
        tree.expandAll()

        row = QHBoxLayout()
        row.addStretch()
        close_button = QPushButton(tr("provenance.btn_close"))
        close_button.clicked.connect(self.close)
        row.addWidget(close_button)
        layout.addLayout(row)

    # ------------------------------------------------------------------
    def _populate_tree(self, tree: QTreeWidget) -> None:
        for section in self._diff.get("sections", []):
            added = section["added"]
            removed = section["removed"]
            renamed = section["renamed"]
            changed = section["changed"]
            total = len(added) + len(removed) + len(renamed) + len(changed)
            if total == 0:
                continue
            label = section["label"]
            section_node = QTreeWidgetItem([
                f"{label}",
                f"+{len(added)} added, −{len(removed)} removed, "
                f"{len(renamed)} renamed, {len(changed)} changed",
            ])
            tree.addTopLevelItem(section_node)

            self._add_category(section_node, "Added", [self._node_name(n) for n in added])
            self._add_category(section_node, "Removed", [self._node_name(n) for n in removed])
            self._add_category(
                section_node,
                "Renamed",
                [f"{r['old_name']} → {r['new_name']}" for r in renamed],
            )
            self._add_category(
                section_node,
                "Changed",
                [self._format_changed(c) for c in changed],
            )

        weight_changes = self._diff.get("weight_changes") or []
        if weight_changes:
            weights_node = QTreeWidgetItem(["Weights", f"{len(weight_changes)} changed"])
            tree.addTopLevelItem(weights_node)
            for change in weight_changes:
                weights_node.addChild(
                    QTreeWidgetItem([change["key"], f"{change['old']} → {change['new']}"])
                )

        breakage = self._diff.get("mapping_breakage") or []
        if breakage:
            breakage_node = QTreeWidgetItem(["Mapping breakage", f"{len(breakage)} mapping(s)"])
            tree.addTopLevelItem(breakage_node)
            for item in breakage:
                breakage_node.addChild(
                    QTreeWidgetItem([item["target_variable"], f"from column '{item['source_column']}'"])
                )

    @staticmethod
    def _add_category(parent: QTreeWidgetItem, label: str, values: List[str]) -> None:
        if not values:
            return
        category = QTreeWidgetItem([f"{label} ({len(values)})", ""])
        parent.addChild(category)
        for value in values:
            category.addChild(QTreeWidgetItem(["", value]))

    @staticmethod
    def _node_name(node: Dict[str, Any]) -> str:
        name = node.get("name") or node.get("id") or ""
        node_id = node.get("hblock_id")
        if node_id is not None:
            return f"{name}  (#{node_id})"
        return str(name)

    @staticmethod
    def _format_changed(change: Dict[str, Any]) -> str:
        parts = []
        for attr, delta in (change.get("attrs") or {}).items():
            parts.append(f"{attr}: {delta.get('old')} → {delta.get('new')}")
        return f"{change.get('name', change.get('id'))}: " + "; ".join(parts)
