"""Dialog for selecting vector layers and reference raster for SAM prompt building."""

from __future__ import annotations

from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
    QDialog,
    QDialogButtonBox,
    QComboBox,
    QListWidget,
    QVBoxLayout,
    QLabel,
    QWidget,
    QAbstractItemView,
    QSpinBox,
    QCheckBox,
    QDoubleSpinBox,
    QGridLayout,
    QGroupBox,
)
from qgis.core import QgsProject, QgsRasterLayer, QgsVectorLayer, QgsWkbTypes


class VectorPromptDialog(QDialog):
    def __init__(self, parent: QWidget | None = None, show_zoom: bool = False) -> None:
        super().__init__(parent)
        self.setWindowTitle("SAM Prompts from Vector Layers")
        self.setMinimumWidth(350)

        layout = QVBoxLayout()

        # --- Reference raster selection ---
        layout.addWidget(QLabel("Reference raster layer:"))
        self.raster_combo = QComboBox()
        self._populate_raster_layers()
        layout.addWidget(self.raster_combo)

        # --- Vector layer multi-select ---
        layout.addWidget(QLabel("Select vector layer(s) for prompts:"))
        self.vector_list = QListWidget()
        self.vector_list.setSelectionMode(
            QAbstractItemView.SelectionMode.ExtendedSelection
        )
        self._populate_vector_layers()
        layout.addWidget(self.vector_list)

        # Warning shown when too many polygon features are selected
        self._rect_warning = QLabel("")
        self._rect_warning.setStyleSheet("color: red")
        self._rect_warning.setWordWrap(True)
        self._rect_warning.hide()
        layout.addWidget(self._rect_warning)

        # Warning shown when a point layer has no 'label' field
        self._label_warning = QLabel("")
        self._label_warning.setStyleSheet("color: orange")
        self._label_warning.setWordWrap(True)
        self._label_warning.hide()
        layout.addWidget(self._label_warning)

        self.vector_list.itemSelectionChanged.connect(self._on_vector_selection_changed)

        # --- Zoom level (only for remote inference) ---
        self.zoom_spin = None
        self._bbox_check = None
        self._bbox_spins = {}
        if show_zoom:
            layout.addWidget(QLabel("Zoom level (higher = more detail, slower):"))
            self.zoom_spin = QSpinBox()
            self.zoom_spin.setRange(1, 20)
            self.zoom_spin.setValue(13)
            layout.addWidget(self.zoom_spin)

            # --- Custom bounding box ---
            self._bbox_check = QCheckBox("Use custom bounding box")
            layout.addWidget(self._bbox_check)

            bbox_group = QGroupBox()
            bbox_grid = QGridLayout()
            for row, (key, label, lo, hi) in enumerate(
                [
                    ("n", "North lat:", -90, 90),
                    ("s", "South lat:", -90, 90),
                    ("e", "East lng:", -180, 180),
                    ("w", "West lng:", -180, 180),
                ]
            ):
                bbox_grid.addWidget(QLabel(label), row, 0)
                spin = QDoubleSpinBox()
                spin.setRange(lo, hi)
                spin.setDecimals(6)
                spin.setEnabled(False)
                bbox_grid.addWidget(spin, row, 1)
                self._bbox_spins[key] = spin
            bbox_group.setLayout(bbox_grid)
            layout.addWidget(bbox_group)

            self._bbox_check.toggled.connect(self._on_bbox_toggled)

        # --- OK / Cancel ---
        self.button_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok  # pyright: ignore[reportCallIssue,reportArgumentType] — PyQt stubs return int for scoped enum |
            | QDialogButtonBox.StandardButton.Cancel
        )
        self._ok_button = self.button_box.button(QDialogButtonBox.StandardButton.Ok)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        layout.addWidget(self.button_box)

        self.setLayout(layout)

    def _populate_raster_layers(self) -> None:
        layers = QgsProject.instance().mapLayers().values()
        for layer in layers:
            if isinstance(layer, QgsRasterLayer):
                self.raster_combo.addItem(layer.name(), layer)

    def _populate_vector_layers(self) -> None:
        layers = QgsProject.instance().mapLayers().values()
        for layer in layers:
            if isinstance(layer, QgsVectorLayer):
                geom_type = layer.geometryType()
                # Only point and polygon layers (skip line, unknown)
                if geom_type in (
                    QgsWkbTypes.PointGeometry,
                    QgsWkbTypes.PolygonGeometry,
                ):
                    self.vector_list.addItem(layer.name())
                    # Store layer reference in the last added item
                    list_item = self.vector_list.item(self.vector_list.count() - 1)
                    list_item.setData(Qt.ItemDataRole.UserRole, layer)

    def selected_raster(self) -> QgsRasterLayer | None:
        """Return the selected QgsRasterLayer or None."""
        if self.raster_combo.count() == 0:
            return None
        return self.raster_combo.currentData()

    def selected_vector_layers(self) -> list[QgsVectorLayer]:
        """Return list of selected QgsVectorLayers."""
        return [
            item.data(Qt.ItemDataRole.UserRole)
            for item in self.vector_list.selectedItems()
        ]

    def set_default_zoom(self, zoom: int) -> None:
        """Set the zoom spinbox value. No-op if zoom spinbox is hidden."""
        if self.zoom_spin is not None:
            self.zoom_spin.setValue(zoom)

    def selected_zoom(self) -> int:
        """Return the selected zoom level, or 13 if zoom spinbox is hidden."""
        if self.zoom_spin is not None:
            return self.zoom_spin.value()
        return 13

    def set_custom_bbox(self, w: float, s: float, e: float, n: float) -> None:
        """Pre-fill the bbox spinboxes (e.g. from raster extent). No-op if hidden."""
        if not self._bbox_spins:
            return
        self._bbox_spins["n"].setValue(n)
        self._bbox_spins["s"].setValue(s)
        self._bbox_spins["e"].setValue(e)
        self._bbox_spins["w"].setValue(w)

    def custom_bbox(self) -> tuple[float, float, float, float] | None:
        """Return (w, s, e, n) if custom bbox is checked, else None."""
        if self._bbox_check is None or not self._bbox_check.isChecked():
            return None
        return (
            self._bbox_spins["w"].value(),
            self._bbox_spins["s"].value(),
            self._bbox_spins["e"].value(),
            self._bbox_spins["n"].value(),
        )

    def _on_bbox_toggled(self, checked: bool) -> None:
        for spin in self._bbox_spins.values():
            spin.setEnabled(checked)

    def _on_vector_selection_changed(self) -> None:
        """Validate selected layers: rectangle count and label field presence."""
        n_polygon_features = 0
        layers_missing_label = []

        for item in self.vector_list.selectedItems():
            layer = item.data(Qt.ItemDataRole.UserRole)
            if layer is None:
                continue
            if layer.geometryType() == QgsWkbTypes.PolygonGeometry:
                n_polygon_features += layer.featureCount()
            elif (
                layer.geometryType() == QgsWkbTypes.PointGeometry
                and layer.fields().indexOf("label") < 0
            ):
                layers_missing_label.append(layer.name())

        # Rectangle limit (blocks OK)
        if n_polygon_features > 1:
            self._rect_warning.setText(
                f"SAM2 supports at most 1 rectangle per prediction. "
                f"Selected layers contain {n_polygon_features} polygon features."
            )
            self._rect_warning.show()
            self._ok_button.setEnabled(False)
        else:
            self._rect_warning.hide()
            self._ok_button.setEnabled(True)

        # Missing label field (informational, does not block)
        if layers_missing_label:
            names = ", ".join(layers_missing_label)
            self._label_warning.setText(
                f"Layer(s) '{names}' have no 'label' field — all points "
                f"default to foreground. Add an integer 'label' field "
                f"(1=foreground, 0=background) to control FG/BG."
            )
            self._label_warning.show()
        else:
            self._label_warning.hide()
