# -*- coding: utf-8 -*-
"""Camera Trap Grid Design — Dialog.

Provides a QGIS dialog for designing camera trap placement grids
with 4 strategies: Regular Grid, Intensive Grid, Radial Rings,
and Targeted Point placement.

Author: Loc Vu Trung tong hop tu dam me
License: GPL-3.0
"""

import os

from qgis.core import (
    QgsProject, QgsRectangle, QgsVectorLayer, QgsMapLayerProxyModel,
    QgsWkbTypes, QgsPointXY
)
from qgis.gui import QgsMapLayerComboBox, QgsMapToolEmitPoint, QgsRubberBand
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QFont
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QGridLayout,
    QLabel, QPushButton, QComboBox, QDoubleSpinBox,
    QSpinBox, QGroupBox, QTabWidget, QWidget,
    QFileDialog, QMessageBox, QFrame, QCheckBox,
    QRadioButton, QButtonGroup
)


class CameraTrapDialog(QDialog):
    """Camera Trap Grid Design dialog.

    Supports 4 placement modes:
    1. Regular Grid — systematic grid over polygon / rectangle
    2. Intensive — main + sub cameras at each grid node
    3. Radial Rings — concentric rings from a detection point
    4. Targeted — single point with satellite cameras
    """

    EXPORT_FORMATS = {
        "Shapefile (*.shp)": ("ESRI Shapefile", ".shp"),
        "GeoJSON (*.geojson)": ("GeoJSON", ".geojson"),
        "KML (*.kml)": ("KML", ".kml"),
        "CSV (*.csv)": ("CSV", ".csv"),
        "GeoPackage (*.gpkg)": ("GPKG", ".gpkg"),
    }

    def __init__(self, iface, lang="vi", parent=None):
        super().__init__(parent)
        self.iface = iface
        self.lang = lang
        self.plugin_dir = os.path.dirname(
            os.path.dirname(os.path.dirname(__file__)))
        self._result_layer = None
        self._prev_map_tool = None
        self._drawn_extent = None
        self._setup_ui()
        self._connect_smart_signals()

    # ------------------------------------------------------------------
    # UI Construction
    # ------------------------------------------------------------------

    def _setup_ui(self):
        """Build the dialog interface."""
        self.setWindowTitle(self._tr("Camera Trap Grid Design",
                                     "Thiết lập lưới bẫy ảnh"))
        self.setMinimumSize(680, 550)
        self.resize(720, 600)
        self.setSizeGripEnabled(True)

        main = QVBoxLayout(self)

        # Header
        header = self._build_header()
        main.addWidget(header)

        # Tabs for 4 modes
        self.tabs = QTabWidget()
        self.tabs.addTab(self._build_tab_grid(), "🔲 " + self._tr(
            "Regular Grid", "Lưới ô vuông"))
        self.tabs.addTab(self._build_tab_intensive(), "📷 " + self._tr(
            "Intensive", "Bẫy ảnh chuyên sâu"))
        self.tabs.addTab(self._build_tab_radial(), "🎯 " + self._tr(
            "Radial Rings", "Vành nón từ tâm"))
        self.tabs.addTab(self._build_tab_targeted(), "📍 " + self._tr(
            "Targeted", "Chủ đích"))
        main.addWidget(self.tabs)

        # Extent source
        ext_grp = QGroupBox(self._tr("Extent Source", "Nguồn phạm vi"))
        ext_ly = QGridLayout()

        self.rb_layer = QRadioButton(
            self._tr("From polygon layer (extent)",
                     "Từ layer polygon (phạm vi)"))
        self.rb_selected = QRadioButton(
            self._tr("Selected polygons only",
                     "Chỉ polygon đang chọn"))
        self.rb_canvas = QRadioButton(
            self._tr("Current map canvas",
                     "Khung bản đồ hiện tại"))
        self.rb_layer.setChecked(True)

        self.rb_group = QButtonGroup(self)
        self.rb_group.addButton(self.rb_layer, 0)
        self.rb_group.addButton(self.rb_selected, 1)
        self.rb_group.addButton(self.rb_canvas, 2)

        ext_ly.addWidget(self.rb_layer, 0, 0)
        ext_ly.addWidget(self.rb_selected, 0, 1)
        ext_ly.addWidget(self.rb_canvas, 0, 2)

        self.cmb_layer = QgsMapLayerComboBox()
        self.cmb_layer.setFilters(QgsMapLayerProxyModel.Filter.PolygonLayer)
        ext_ly.addWidget(QLabel(self._tr("Layer:", "Layer:")), 1, 0)
        ext_ly.addWidget(self.cmb_layer, 1, 1, 1, 2)

        self.chk_clip = QCheckBox(
            self._tr("Clip grid to polygon boundary",
                     "Cắt lưới theo ranh polygon"))
        self.chk_clip.setChecked(True)
        ext_ly.addWidget(self.chk_clip, 2, 0, 1, 2)

        # Draw rectangle on map button
        self.btn_draw_rect = QPushButton(
            "✏ " + self._tr("Draw area on map",
                            "Vẽ vùng trên bản đồ"))
        self.btn_draw_rect.setStyleSheet("""
            QPushButton {
                background: #FFF8E1; border: 1px solid #FFA000;
                border-radius: 4px; padding: 4px 8px;
            }
            QPushButton:hover { background: #FFECB3
            }
        """)
        self.btn_draw_rect.clicked.connect(self._draw_rect_on_map)
        ext_ly.addWidget(self.btn_draw_rect, 2, 2)

        ext_grp.setLayout(ext_ly)
        main.addWidget(ext_grp)

        # Export section
        exp_grp = QGroupBox(self._tr("Export", "Xuất dữ liệu"))
        exp_ly = QHBoxLayout()

        self.cmb_format = QComboBox()
        for fmt in self.EXPORT_FORMATS:
            self.cmb_format.addItem(fmt)
        exp_ly.addWidget(QLabel(self._tr("Format:", "Định dạng:")))
        exp_ly.addWidget(self.cmb_format, 1)

        self.chk_add_map = QCheckBox(
            self._tr("Add to map", "Thêm vào bản đồ"))
        self.chk_add_map.setChecked(True)
        exp_ly.addWidget(self.chk_add_map)

        exp_grp.setLayout(exp_ly)
        main.addWidget(exp_grp)

        # Action buttons
        btn_ly = QHBoxLayout()

        self.btn_preview = QPushButton(
            "👁 " + self._tr("Preview", "Xem trước"))
        self.btn_preview.setMinimumHeight(36)
        self.btn_preview.setStyleSheet("""
            QPushButton {
                background: #E3F2FD; border: 2px solid #1976D2;
                border-radius: 6px; font-weight: bold;
                padding: 6px 16px;
            }
            QPushButton:hover { background: #BBDEFB
            }
        """)
        self.btn_preview.clicked.connect(self._on_preview)
        btn_ly.addWidget(self.btn_preview)

        self.btn_export = QPushButton(
            "💾 " + self._tr("Export", "Xuất file"))
        self.btn_export.setMinimumHeight(36)
        self.btn_export.setStyleSheet("""
            QPushButton {
                background: #E8F5E9; border: 2px solid #4CAF50;
                border-radius: 6px; font-weight: bold;
                padding: 6px 16px;
            }
            QPushButton:hover { background: #C8E6C9
            }
        """)
        self.btn_export.clicked.connect(self._on_export)
        btn_ly.addWidget(self.btn_export)

        self.btn_help = QPushButton(
            "❓ " + self._tr("Science & Help", "Lý thuyết & Hướng dẫn"))
        self.btn_help.setMinimumHeight(36)
        self.btn_help.setStyleSheet("""
            QPushButton {
                background: #F3E5F5; border: 2px solid #9C27B0;
                border-radius: 6px; font-weight: bold;
                padding: 6px 16px;
            }
            QPushButton:hover { background: #E1BEE7
            }
        """)
        self.btn_help.clicked.connect(self._on_help)
        btn_ly.addWidget(self.btn_help)

        main.addLayout(btn_ly)

        # Status
        self.lbl_status = QLabel("")
        self.lbl_status.setStyleSheet(
            "color: #555; font-size: 11px; margin-top: 4px;")
        main.addWidget(self.lbl_status)

    def _build_header(self):
        """Build the green header bar."""
        header = QFrame()
        header.setStyleSheet("""
            QFrame {
                background: qlineargradient(
                    x1:0, y1:0, x2:1, y2:0,
                    stop:0 #1B5E20, stop:1 #388E3C);
                border-radius: 6px; padding: 8px;
            }
        """)
        h_ly = QHBoxLayout(header)
        lbl = QLabel(self._tr(
            "📷 Camera Trap Grid Design",
            "📷 Thiết lập lưới bẫy ảnh"))
        lbl.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
        lbl.setStyleSheet("color: white;")
        h_ly.addWidget(lbl)

        desc = QLabel(self._tr(
            "4 placement strategies • Export SHP / KML / CSV / GeoJSON",
            "4 chiến lược đặt bẫy • Xuất SHP / KML / CSV / GeoJSON"))
        desc.setStyleSheet("color: rgba(255,255,255,0.8); font-size: 11px;")
        h_ly.addWidget(desc, 1)
        return header

    # ------ Tab 1: Regular Grid ------
    def _build_tab_grid(self):
        """Regular grid placement tab."""
        w = QWidget()
        ly = QGridLayout(w)

        ly.addWidget(QLabel(self._tr(
            "Grid spacing X (m):", "Khoảng cách lưới X (m):")), 0, 0)
        self.spn_grid_x = QDoubleSpinBox()
        self.spn_grid_x.setRange(10, 50000)
        self.spn_grid_x.setValue(500)
        self.spn_grid_x.setSuffix(" m")
        self.spn_grid_x.setDecimals(0)
        ly.addWidget(self.spn_grid_x, 0, 1)

        ly.addWidget(QLabel(self._tr(
            "Grid spacing Y (m):", "Khoảng cách lưới Y (m):")), 1, 0)
        self.spn_grid_y = QDoubleSpinBox()
        self.spn_grid_y.setRange(10, 50000)
        self.spn_grid_y.setValue(500)
        self.spn_grid_y.setSuffix(" m")
        self.spn_grid_y.setDecimals(0)
        ly.addWidget(self.spn_grid_y, 1, 1)

        # Info
        info = QLabel(self._tr(
            "💡 Creates a regular grid of trap points within the selected extent.\n"
            "Typical spacing: 500m (large mammals), 200m (small mammals).",
            "💡 Tạo lưới ô vuông các điểm đặt bẫy ảnh trong phạm vi.\n"
            "Khoảng cách thông dụng: 500m (thú lớn), 200m (thú nhỏ)."))
        info.setStyleSheet("color: #1565C0; font-size: 11px; margin-top: 8px;")
        info.setWordWrap(True)
        ly.addWidget(info, 2, 0, 1, 2)
        ly.setRowStretch(3, 1)
        return w

    # ------ Tab 2: Intensive Grid ------
    def _build_tab_intensive(self):
        """Intensive grid with sub-cameras tab."""
        w = QWidget()
        ly = QGridLayout(w)

        ly.addWidget(QLabel(self._tr(
            "Main grid X (m):", "Lưới chính X (m):")), 0, 0)
        self.spn_int_x = QDoubleSpinBox()
        self.spn_int_x.setRange(10, 50000)
        self.spn_int_x.setValue(1000)
        self.spn_int_x.setSuffix(" m")
        self.spn_int_x.setDecimals(0)
        ly.addWidget(self.spn_int_x, 0, 1)

        ly.addWidget(QLabel(self._tr(
            "Main grid Y (m):", "Lưới chính Y (m):")), 1, 0)
        self.spn_int_y = QDoubleSpinBox()
        self.spn_int_y.setRange(10, 50000)
        self.spn_int_y.setValue(1000)
        self.spn_int_y.setSuffix(" m")
        self.spn_int_y.setDecimals(0)
        ly.addWidget(self.spn_int_y, 1, 1)

        ly.addWidget(QLabel(self._tr(
            "Sub camera radius (m):", "Bán kính bẫy phụ (m):")), 2, 0)
        self.spn_sub_r = QDoubleSpinBox()
        self.spn_sub_r.setRange(5, 5000)
        self.spn_sub_r.setValue(100)
        self.spn_sub_r.setSuffix(" m")
        self.spn_sub_r.setDecimals(0)
        ly.addWidget(self.spn_sub_r, 2, 1)

        ly.addWidget(QLabel(self._tr(
            "Sub cameras per node:", "Số bẫy phụ / nút:")), 3, 0)
        self.cmb_sub_n = QComboBox()
        self.cmb_sub_n.addItems(["4 (N / E / S / W)", "8 (cardinal + ordinal)"])
        ly.addWidget(self.cmb_sub_n, 3, 1)

        info = QLabel(self._tr(
            "💡 Each grid node gets a main camera + satellite sub-cameras.\n"
            "Sub cameras are placed at the configured radius in cardinal directions.",
            "💡 Mỗi nút lưới đặt 1 bẫy chính + bẫy phụ vệ tinh.\n"
            "Bẫy phụ cách bẫy chính theo bán kính đã thiết lập."))
        info.setStyleSheet("color: #1565C0; font-size: 11px; margin-top: 8px;")
        info.setWordWrap(True)
        ly.addWidget(info, 4, 0, 1, 2)
        ly.setRowStretch(5, 1)
        return w

    # ------ Tab 3: Radial Rings ------
    def _build_tab_radial(self):
        """Concentric radial rings from a detection point."""
        w = QWidget()
        ly = QGridLayout(w)

        ly.addWidget(QLabel(self._tr(
            "Center X:", "Tọa độ X tâm:")), 0, 0)
        self.spn_rad_x = QDoubleSpinBox()
        self.spn_rad_x.setRange(-99999999, 99999999)
        self.spn_rad_x.setDecimals(6)
        self.spn_rad_x.setValue(0)
        ly.addWidget(self.spn_rad_x, 0, 1)

        self.btn_pick_center = QPushButton(
            "🖱 " + self._tr("Pick from map", "Chọn từ bản đồ"))
        self.btn_pick_center.clicked.connect(self._pick_center_from_map)
        ly.addWidget(self.btn_pick_center, 0, 2)

        ly.addWidget(QLabel(self._tr(
            "Center Y:", "Tọa độ Y tâm:")), 1, 0)
        self.spn_rad_y = QDoubleSpinBox()
        self.spn_rad_y.setRange(-99999999, 99999999)
        self.spn_rad_y.setDecimals(6)
        self.spn_rad_y.setValue(0)
        ly.addWidget(self.spn_rad_y, 1, 1)

        ly.addWidget(QLabel(self._tr(
            "Ring spacing (m):", "Khoảng cách vành (m):")), 2, 0)
        self.spn_ring_space = QDoubleSpinBox()
        self.spn_ring_space.setRange(10, 10000)
        self.spn_ring_space.setValue(200)
        self.spn_ring_space.setSuffix(" m")
        self.spn_ring_space.setDecimals(0)
        ly.addWidget(self.spn_ring_space, 2, 1)

        ly.addWidget(QLabel(self._tr(
            "Number of rings:", "Số vành:")), 3, 0)
        self.spn_ring_count = QSpinBox()
        self.spn_ring_count.setRange(1, 20)
        self.spn_ring_count.setValue(3)
        ly.addWidget(self.spn_ring_count, 3, 1)

        ly.addWidget(QLabel(self._tr(
            "Cameras per ring:", "Bẫy / vành:")), 4, 0)
        self.spn_ring_pts = QSpinBox()
        self.spn_ring_pts.setRange(4, 36)
        self.spn_ring_pts.setValue(8)
        ly.addWidget(self.spn_ring_pts, 4, 1)

        info = QLabel(self._tr(
            "💡 Deploy cameras in concentric rings from an animal detection point.\n"
            "Useful for intensive monitoring after initial sighting / detection.",
            "💡 Đặt bẫy ảnh theo vành nón đồng tâm từ điểm phát hiện con vật.\n"
            "Hữu ích khi cần giám sát chuyên sâu sau phát hiện ban đầu."))
        info.setStyleSheet("color: #1565C0; font-size: 11px; margin-top: 8px;")
        info.setWordWrap(True)
        ly.addWidget(info, 5, 0, 1, 3)
        ly.setRowStretch(6, 1)
        return w

    # ------ Tab 4: Targeted ------
    def _build_tab_targeted(self):
        """Single targeted point with satellite cameras."""
        w = QWidget()
        ly = QGridLayout(w)

        ly.addWidget(QLabel(self._tr(
            "Target X:", "Tọa độ X mục tiêu:")), 0, 0)
        self.spn_tgt_x = QDoubleSpinBox()
        self.spn_tgt_x.setRange(-99999999, 99999999)
        self.spn_tgt_x.setDecimals(6)
        ly.addWidget(self.spn_tgt_x, 0, 1)

        self.btn_pick_target = QPushButton(
            "🖱 " + self._tr("Pick from map", "Chọn từ bản đồ"))
        self.btn_pick_target.clicked.connect(self._pick_target_from_map)
        ly.addWidget(self.btn_pick_target, 0, 2)

        ly.addWidget(QLabel(self._tr(
            "Target Y:", "Tọa độ Y mục tiêu:")), 1, 0)
        self.spn_tgt_y = QDoubleSpinBox()
        self.spn_tgt_y.setRange(-99999999, 99999999)
        self.spn_tgt_y.setDecimals(6)
        ly.addWidget(self.spn_tgt_y, 1, 1)

        ly.addWidget(QLabel(self._tr(
            "Satellite radius (m):", "Bán kính vệ tinh (m):")), 2, 0)
        self.spn_tgt_r = QDoubleSpinBox()
        self.spn_tgt_r.setRange(5, 5000)
        self.spn_tgt_r.setValue(100)
        self.spn_tgt_r.setSuffix(" m")
        self.spn_tgt_r.setDecimals(0)
        ly.addWidget(self.spn_tgt_r, 2, 1)

        ly.addWidget(QLabel(self._tr(
            "Number of satellites:", "Số bẫy vệ tinh:")), 3, 0)
        self.spn_tgt_n = QSpinBox()
        self.spn_tgt_n.setRange(2, 16)
        self.spn_tgt_n.setValue(4)
        ly.addWidget(self.spn_tgt_n, 3, 1)

        info = QLabel(self._tr(
            "💡 Place cameras around a specific location (e.g. salt lick, "
            "watering hole, wildlife corridor).\n"
            "Satellites are evenly distributed around the center at the given radius.",
            "💡 Đặt bẫy quanh vị trí cụ thể (bãi muối, nguồn nước, "
            "hành lang sinh thái).\n"
            "Bẫy vệ tinh phân bố đều quanh tâm theo bán kính thiết lập."))
        info.setStyleSheet("color: #1565C0; font-size: 11px; margin-top: 8px;")
        info.setWordWrap(True)
        ly.addWidget(info, 4, 0, 1, 3)
        ly.setRowStretch(5, 1)
        return w

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------

    def _on_preview(self):
        """Generate and preview grid on map."""
        try:
            features, fields = self._generate_current()
            features = self._clip_features(features, fields)
        except Exception as e:
            QMessageBox.warning(self, "Error", str(e))
            return

        if not features:
            QMessageBox.information(
                self, "Info",
                self._tr("No points generated. Check parameters.",
                         "Không tạo được điểm. Kiểm tra tham số."))
            return

        # Create memory layer for preview
        crs = self._get_crs()
        layer = QgsVectorLayer(
            f"Point?crs={crs.authid()}", "Camera Traps (preview)", "memory")
        prov = layer.dataProvider()
        prov.addAttributes(fields)
        layer.updateFields()
        prov.addFeatures(features)
        layer.updateExtents()

        # Remove old preview
        if self._result_layer:
            try:
                QgsProject.instance().removeMapLayer(self._result_layer.id())
            except Exception:  # nosec B110
                pass  # nosec B110

        QgsProject.instance().addMapLayer(layer)
        self._result_layer = layer
        self.iface.mapCanvas().setExtent(layer.extent())
        self.iface.mapCanvas().refresh()

        total = len(features)
        main_c = sum(1 for f in features
                     if f["trap_type"] in ("primary", "main",
                                           "center", "target"))
        sub_c = total - main_c
        self.lbl_status.setText(self._tr(
            f"✅ Preview: {total} traps ({main_c} main + {sub_c} sub)",
            f"✅ Xem trước: {total} bẫy ({main_c} chính + {sub_c} phụ)"))

    def _on_export(self):
        """Export grid to file."""
        try:
            features, fields = self._generate_current()
            features = self._clip_features(features, fields)
        except Exception as e:
            QMessageBox.warning(self, "Error", str(e))
            return

        if not features:
            QMessageBox.information(
                self, "Info",
                self._tr("No points to export.",
                         "Không có điểm để xuất."))
            return

        fmt_text = self.cmb_format.currentText()
        driver, ext = self.EXPORT_FORMATS[fmt_text]

        path, _ = QFileDialog.getSaveFileName(
            self,
            self._tr("Save Camera Traps", "Lưu bẫy ảnh"),
            os.path.expanduser(f"~/camera_traps{ext}"),
            fmt_text)

        if not path:
            return

        from .grid_generator import export_features
        crs = self._get_crs()
        result = export_features(features, fields, crs, path, driver)

        if result:
            self.lbl_status.setText(self._tr(
                f"✅ Exported {len(features)} traps → {os.path.basename(path)}",
                f"✅ Đã xuất {len(features)} bẫy → {os.path.basename(path)}"))

            if self.chk_add_map.isChecked() and driver != "CSV":
                layer = QgsVectorLayer(path, os.path.basename(path), "ogr")
                if layer.isValid():
                    QgsProject.instance().addMapLayer(layer)
        else:
            QMessageBox.warning(
                self, "Error",
                self._tr("Export failed!", "Xuất file thất bại!"))

    # ------------------------------------------------------------------
    # Generation Dispatcher
    # ------------------------------------------------------------------

    def _generate_current(self):
        """Generate features based on current tab selection."""
        from .grid_generator import (
            generate_regular_grid, generate_intensive_grid,
            generate_radial_rings, generate_targeted_point
        )

        tab = self.tabs.currentIndex()

        if tab == 0:  # Regular Grid
            extent = self._get_extent()
            sx = self._meters_to_map_units(self.spn_grid_x.value(), extent)
            sy = self._meters_to_map_units(self.spn_grid_y.value(), extent)
            return generate_regular_grid(extent, sx, sy)

        elif tab == 1:  # Intensive
            extent = self._get_extent()
            sx = self._meters_to_map_units(self.spn_int_x.value(), extent)
            sy = self._meters_to_map_units(self.spn_int_y.value(), extent)
            sr = self._meters_to_map_units(self.spn_sub_r.value(), extent)
            sub_n = 4 if self.cmb_sub_n.currentIndex() == 0 else 8
            return generate_intensive_grid(extent, sx, sy, sr, sub_n)

        elif tab == 2:  # Radial Rings
            cx = self.spn_rad_x.value()
            cy = self.spn_rad_y.value()
            rs = self._meters_to_map_units_at(
                self.spn_ring_space.value(), cy)
            return generate_radial_rings(
                cx, cy, rs,
                self.spn_ring_count.value(),
                self.spn_ring_pts.value())

        elif tab == 3:  # Targeted
            cx = self.spn_tgt_x.value()
            cy = self.spn_tgt_y.value()
            sr = self._meters_to_map_units_at(
                self.spn_tgt_r.value(), cy)
            return generate_targeted_point(cx, cy, sr, self.spn_tgt_n.value())

        return [], None

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    def _meters_to_map_units(self, meters, extent):
        """Convert meters to map units, auto-detecting geographic CRS.

        If CRS is geographic (degrees), converts using center latitude.
        If CRS is projected (meters), returns as-is.
        """
        crs = self._get_crs()
        if not crs.isGeographic():
            return meters  # Already in meters
        # Convert: 1 degree latitude ≈ 111320 m
        import math
        center_lat = (extent.yMinimum() + extent.yMaximum()) / 2.0
        _ = 111320.0
        meters_per_deg_lon = 111320.0 * math.cos(math.radians(center_lat))
        return meters / meters_per_deg_lon  # Use lon scale for X / Y average

    def _meters_to_map_units_at(self, meters, lat):
        """Convert meters to map units at a specific latitude."""
        crs = self._get_crs()
        if not crs.isGeographic():
            return meters
        import math
        meters_per_deg = 111320.0 * math.cos(math.radians(lat))
        if meters_per_deg < 1:
            meters_per_deg = 111320.0  # Fallback for poles
        return meters / meters_per_deg

    def _get_extent(self):
        """Get extent from selected source."""
        if self.rb_canvas.isChecked():
            if self._drawn_extent and not self._drawn_extent.isEmpty():
                return self._drawn_extent
            return self.iface.mapCanvas().extent()

        layer = self.cmb_layer.currentLayer()
        if not layer:
            return self.iface.mapCanvas().extent()

        if self.rb_selected.isChecked() and layer.selectedFeatureCount() > 0:
            # Use bounding box of selected features
            from qgis.core import QgsGeometry
            combined = QgsGeometry.unaryUnion(  # noqa: F811
                [f.geometry() for f in layer.selectedFeatures()])
            return combined.boundingBox()

        return layer.extent()

    def _get_clip_geometry(self):
        """Get polygon geometry for clipping (or None)."""
        if not self.chk_clip.isChecked():
            return None

        layer = self.cmb_layer.currentLayer()
        if not layer:
            return None

        from qgis.core import QgsGeometry
        geoms = []  # noqa: F811

        if self.rb_selected.isChecked() and layer.selectedFeatureCount() > 0:
            geoms = [f.geometry() for f in layer.selectedFeatures()
                     if f.geometry() and not f.geometry().isEmpty()]
        elif not self.rb_canvas.isChecked():
            # rb_layer or fallback: use all features
            geoms = [f.geometry() for f in layer.getFeatures()
                     if f.geometry() and not f.geometry().isEmpty()]

        if not geoms:
            return None

        combined = QgsGeometry.unaryUnion(geoms)
        if combined and not combined.isEmpty() and not combined.isGeosValid():
            combined = combined.makeValid()
        return combined

    def _clip_features(self, features, fields):
        """Clip features to polygon boundary if enabled."""
        clip_geom = self._get_clip_geometry()
        if not clip_geom or clip_geom.isEmpty():
            return features

        clipped = []
        for feat in features:
            pt_geom = feat.geometry()
            if clip_geom.intersects(pt_geom):
                clipped.append(feat)
        return clipped

    def _get_crs(self):
        """Get CRS from current context."""
        if self.rb_layer.isChecked() or self.rb_selected.isChecked():
            layer = self.cmb_layer.currentLayer()
            if layer:
                return layer.crs()
        return QgsProject.instance().crs()

    def _pick_center_from_map(self):
        """Pick radial center from map canvas center."""
        center = self.iface.mapCanvas().center()
        self.spn_rad_x.setValue(center.x())
        self.spn_rad_y.setValue(center.y())
        self.lbl_status.setText(self._tr(
            f"📍 Center: {center.x():.2f}, {center.y():.2f}",
            f"📍 Tâm: {center.x():.2f}, {center.y():.2f}"))

    def _pick_target_from_map(self):
        """Pick target from map canvas center."""
        center = self.iface.mapCanvas().center()
        self.spn_tgt_x.setValue(center.x())
        self.spn_tgt_y.setValue(center.y())
        self.lbl_status.setText(self._tr(
            f"📍 Target: {center.x():.2f}, {center.y():.2f}",
            f"📍 Mục tiêu: {center.x():.2f}, {center.y():.2f}"))

    def _on_help(self):
        """Open the scientific help HTML file."""
        help_dir = os.path.join(os.path.dirname(__file__), 'help')
        html_file = os.path.join(help_dir, '01_CameraTrap_Science.html')
        if os.path.isfile(html_file):
            import webbrowser
            webbrowser.open(f'file:///{html_file}')
        else:
            QMessageBox.information(
                self, 'Help',
                self._tr('Help file not found.',
                         'Không tìm thấy file hướng dẫn.'))

    # ------------------------------------------------------------------
    # Smart UI Signals
    # ------------------------------------------------------------------

    def _connect_smart_signals(self):
        """Connect signals for smart auto-toggle behavior."""
        self.rb_group.buttonClicked.connect(self._on_extent_source_changed)
        self.tabs.currentChanged.connect(self._on_tab_changed)
        # Initial state
        self._on_extent_source_changed()
        self._on_tab_changed(0)

    def _on_extent_source_changed(self, btn=None):
        """Auto-toggle layer combo and clip based on extent source."""
        use_layer = self.rb_layer.isChecked() or self.rb_selected.isChecked()
        self.cmb_layer.setEnabled(use_layer)
        self.chk_clip.setEnabled(use_layer)
        if use_layer:
            self.chk_clip.setChecked(True)
        else:
            self.chk_clip.setChecked(False)

    def _on_tab_changed(self, index):
        """Auto-toggle extent controls based on active tab."""
        # Radial (2) and Targeted (3) don't need extent/layer/clip
        is_grid_mode = index in (0, 1)
        _ = index == 2
        is_targeted = index == 3

        # Show/hide extent group controls
        self.rb_layer.setEnabled(is_grid_mode)
        self.rb_selected.setEnabled(is_grid_mode)
        self.rb_canvas.setEnabled(is_grid_mode)
        self.cmb_layer.setEnabled(
            is_grid_mode and (self.rb_layer.isChecked()
                              or self.rb_selected.isChecked()))  # noqa: W503
        self.chk_clip.setEnabled(
            is_grid_mode and (self.rb_layer.isChecked() or  # noqa: W504
                              self.rb_selected.isChecked()))
        self.btn_draw_rect.setEnabled(is_grid_mode or is_targeted)

    # ------------------------------------------------------------------
    # Map Interaction Tools
    # ------------------------------------------------------------------

    def _draw_rect_on_map(self):
        """Let user draw a rectangle on the map canvas."""
        self.hide()
        canvas = self.iface.mapCanvas()
        self._prev_map_tool = canvas.mapTool()
        self._rubber = QgsRubberBand(canvas, QgsWkbTypes.GeometryType.PolygonGeometry)
        self._rubber.setColor(Qt.GlobalColor.red)
        self._rubber.setWidth(2)

        self._draw_tool = _RectangleMapTool(canvas, self._rubber)
        self._draw_tool.rectangleDrawn.connect(self._on_rect_drawn)
        canvas.setMapTool(self._draw_tool)
        self.iface.messageBar().pushInfo(
            "Camera Trap",
            self._tr("Draw a rectangle on the map. Right-click to cancel.",
                     "Vẽ hình chữ nhật trên bản đồ. Chuột phải để hủy."))

    def _on_rect_drawn(self, rect):
        """Handle rectangle drawn on map."""
        canvas = self.iface.mapCanvas()
        # Clean up
        if hasattr(self, '_rubber'):
            canvas.scene().removeItem(self._rubber)
            self._rubber = None
        if self._prev_map_tool:
            canvas.setMapTool(self._prev_map_tool)

        if rect and not rect.isEmpty():
            self._drawn_extent = rect
            self.rb_canvas.setChecked(True)
            self._on_extent_source_changed()
            self.lbl_status.setText(self._tr(
                f"✅ Area drawn: {rect.width():.4f} × {rect.height():.4f}",
                f"✅ Đã vẽ vùng: {rect.width():.4f} × {rect.height():.4f}"))

        self.show()
        self.raise_()
        self.activateWindow()

    def _pick_center_from_map(self):  # noqa: F811
        """Let user click a point on the map for radial center."""  # noqa: F811
        self.hide()
        canvas = self.iface.mapCanvas()
        self._prev_map_tool = canvas.mapTool()
        self._pick_tool = QgsMapToolEmitPoint(canvas)
        self._pick_target_field = 'radial'
        self._pick_tool.canvasClicked.connect(self._on_point_picked)
        canvas.setMapTool(self._pick_tool)
        self.iface.messageBar().pushInfo(
            "Camera Trap",
            self._tr("Click a point on the map for ring center.",
                     "Bấm 1 điểm trên bản đồ làm tâm vành nón."))

    def _pick_target_from_map(self):  # noqa: F811
        """Let user click a point on the map for targeted placement."""  # noqa: F811
        self.hide()
        canvas = self.iface.mapCanvas()
        self._prev_map_tool = canvas.mapTool()
        self._pick_tool = QgsMapToolEmitPoint(canvas)
        self._pick_target_field = 'targeted'
        self._pick_tool.canvasClicked.connect(self._on_point_picked)
        canvas.setMapTool(self._pick_tool)
        self.iface.messageBar().pushInfo(
            "Camera Trap",
            self._tr("Click a point on the map for target location.",
                     "Bấm 1 điểm trên bản đồ làm vị trí mục tiêu."))

    def _on_point_picked(self, point, button):
        """Handle point picked on map."""
        canvas = self.iface.mapCanvas()
        if self._prev_map_tool:
            canvas.setMapTool(self._prev_map_tool)

        if self._pick_target_field == 'radial':
            self.spn_rad_x.setValue(point.x())
            self.spn_rad_y.setValue(point.y())
            self.lbl_status.setText(self._tr(
                f"📍 Center: {point.x():.6f}, {point.y():.6f}",
                f"📍 Tâm: {point.x():.6f}, {point.y():.6f}"))
        else:
            self.spn_tgt_x.setValue(point.x())
            self.spn_tgt_y.setValue(point.y())
            self.lbl_status.setText(self._tr(
                f"📍 Target: {point.x():.6f}, {point.y():.6f}",
                f"📍 Mục tiêu: {point.x():.6f}, {point.y():.6f}"))

        self.show()
        self.raise_()
        self.activateWindow()

    def _tr(self, en, vi):
        """Return translated string."""
        return vi if self.lang == 'vi' else en


# ======================================================================
# Map Tool: Rectangle Drawing  # noqa: E402
# ======================================================================  # noqa: E402

from qgis.PyQt.QtCore import pyqtSignal  # noqa: E402
from qgis.gui import QgsMapTool  # noqa: E402


class _RectangleMapTool(QgsMapTool):
    """Map tool for drawing a rectangle by clicking two corners."""

    rectangleDrawn = pyqtSignal(QgsRectangle)

    def __init__(self, canvas, rubber_band):
        super().__init__(canvas)
        self._rubber = rubber_band
        self._start_point = None

    def canvasPressEvent(self, event):
        """First click = start corner."""
        if event.button() == Qt.MouseButton.RightButton:
            # Cancel
            self.rectangleDrawn.emit(QgsRectangle())
            return
        self._start_point = self.toMapCoordinates(event.pos())

    def canvasMoveEvent(self, event):
        """Update rubber band while dragging."""
        if not self._start_point:
            return
        end = self.toMapCoordinates(event.pos())
        rect = QgsRectangle(self._start_point, end)
        self._rubber.reset(QgsWkbTypes.GeometryType.PolygonGeometry)
        self._rubber.addPoint(QgsPointXY(rect.xMinimum(), rect.yMinimum()))
        self._rubber.addPoint(QgsPointXY(rect.xMinimum(), rect.yMaximum()))
        self._rubber.addPoint(QgsPointXY(rect.xMaximum(), rect.yMaximum()))
        self._rubber.addPoint(QgsPointXY(rect.xMaximum(), rect.yMinimum()))
        self._rubber.addPoint(QgsPointXY(rect.xMinimum(), rect.yMinimum()))

    def canvasReleaseEvent(self, event):
        """Second release = end corner → emit rectangle."""
        if not self._start_point or event.button() == Qt.MouseButton.RightButton:
            return
        end = self.toMapCoordinates(event.pos())
        rect = QgsRectangle(self._start_point, end)
        self._start_point = None
        self.rectangleDrawn.emit(rect)
