# -*- coding: utf-8 -*-
"""
LVT4U — Flycam Photos: Main Dialog (3 Tabs).

Tabbed dialog for Import & View / Export / GeoView drone photos.

Author: Lộc Vũ Trung (LVT) / Slow Forest
License: GPL-3.0

Based on ImportPhotos plugin (GPL-2.0+) by KIOS Research Center.
"""

import os

from ..shared.qt_compat import QVariant
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QGridLayout,
    QTabWidget, QWidget, QLabel, QLineEdit, QPushButton,
    QFileDialog, QComboBox, QCheckBox, QProgressBar,
    QMessageBox, QGroupBox, QSizePolicy, QFrame, QSpinBox,
    QTextEdit)
from qgis.core import (
    QgsWkbTypes,
    QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
    QgsPointXY, QgsField, QgsFields, QgsVectorFileWriter,
    QgsCoordinateReferenceSystem, QgsCoordinateTransformContext,
    QgsTask, QgsApplication, QgsMapLayer)

from ..shared.i18n import tr
from ..shared.qt_compat import LayerType
from .exif_reader import read_photo_exif, scan_photos_in_folder


class FlycamDialog(QDialog):
    """Main Flycam Photos dialog with 3 tabs."""

    # Output format map: display_name -> (driver, extension)
    _FORMATS = {
        "GeoPackage (*.gpkg)": ("GPKG", ".gpkg"),
        "Shapefile (*.shp)": ("ESRI Shapefile", ".shp"),
        "GeoJSON (*.geojson)": ("GeoJSON", ".geojson"),
        "CSV (*.csv)": ("CSV", ".csv"),
        "KML (*.kml)": ("KML", ".kml"),
        "MapInfo TAB (*.tab)": ("MapInfo File", ".tab"),
    }

    def __init__(self, iface, parent=None):
        super().__init__(parent or iface.mainWindow())
        self.iface = iface
        self.setWindowTitle(tr("Flycam Photos"))
        self.setMinimumSize(650, 420)
        self.resize(750, 480)

        self._viewer = None
        self._click_tool = None
        self._imported_layer_name = None

        self._build_ui()

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

    def _build_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(6, 6, 6, 6)

        self.tabs = QTabWidget()
        self.tabs.addTab(self._build_import_tab(), tr("📥 Import & View"))
        self.tabs.addTab(self._build_export_tab(), tr("📤 Export"))
        self.tabs.addTab(self._build_geoview_tab(), tr("🛰️ GeoView"))
        layout.addWidget(self.tabs)

        # Bottom buttons
        btn_layout = QHBoxLayout()
        self._btn_help = QPushButton("❓ " + tr("Help"))
        self._btn_help.setMinimumHeight(32)
        self._btn_help.setStyleSheet(
            "QPushButton { background: #E8F5E9; border: 1px solid #4CAF50; "
            "border-radius: 4px; padding: 4px 14px; font-weight: bold; }"
            "QPushButton:hover { background: #C8E6C9; }"
        )
        self._btn_help.clicked.connect(self._show_help)
        btn_layout.addWidget(self._btn_help)
        btn_layout.addStretch()
        self._btn_close = QPushButton(tr("Close"))
        self._btn_close.clicked.connect(self.close)
        btn_layout.addWidget(self._btn_close)
        layout.addLayout(btn_layout)

    def _show_help(self):
        """Open help dialog for Flycam Photos module."""
        from ..shared.help_dialog import HelpDialog
        dlg = HelpDialog("flycam", parent=self)
        dlg.exec()

    # ---- Tab 1: Import ----

    def _build_import_tab(self):
        tab = QWidget()
        grid = QGridLayout(tab)
        grid.setSpacing(8)
        row = 0

        # Input folder
        grid.addWidget(QLabel(tr("Input folder:")), row, 0)
        self._inp_folder = QLineEdit()
        self._inp_folder.setPlaceholderText(tr("Select folder containing photos..."))
        grid.addWidget(self._inp_folder, row, 1)
        btn_browse_in = QPushButton(tr("Browse..."))
        btn_browse_in.clicked.connect(self._browse_input)
        grid.addWidget(btn_browse_in, row, 2)

        row += 1
        # Output file
        grid.addWidget(QLabel(tr("Output file:")), row, 0)
        self._inp_output = QLineEdit()
        self._inp_output.setPlaceholderText(tr("Output vector file path..."))
        grid.addWidget(self._inp_output, row, 1)
        btn_browse_out = QPushButton(tr("Browse..."))
        btn_browse_out.clicked.connect(self._browse_output)
        grid.addWidget(btn_browse_out, row, 2)

        row += 1
        # Format
        grid.addWidget(QLabel(tr("Format:")), row, 0)
        self._combo_format = QComboBox()
        self._combo_format.addItems(list(self._FORMATS.keys()))
        self._combo_format.currentTextChanged.connect(self._update_output_ext)
        grid.addWidget(self._combo_format, row, 1)

        row += 1
        # Relative root
        grid.addWidget(QLabel(tr("Relative root:")), row, 0)
        self._inp_relroot = QLineEdit()
        self._inp_relroot.setPlaceholderText(tr("Optional — for relative paths"))
        grid.addWidget(self._inp_relroot, row, 1)
        btn_rel = QPushButton(tr("Browse..."))
        btn_rel.clicked.connect(self._browse_relroot)
        grid.addWidget(btn_rel, row, 2)

        row += 1
        # Web root
        grid.addWidget(QLabel(tr("Web root:")), row, 0)
        self._inp_webroot = QLineEdit()
        self._inp_webroot.setPlaceholderText(tr("Optional — URL prefix for KML"))
        grid.addWidget(self._inp_webroot, row, 1)

        row += 1
        # Canvas extent checkbox
        self._chk_extent = QCheckBox(
            tr("Only import photos within current canvas extent"))
        grid.addWidget(self._chk_extent, row, 1)

        row += 1
        # Progress bar
        self._progress = QProgressBar()
        self._progress.setVisible(False)
        grid.addWidget(self._progress, row, 0, 1, 3)

        row += 1
        # Import button
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        self._btn_import = QPushButton(tr("🚀 Import Photos"))
        self._btn_import.setMinimumWidth(160)
        self._btn_import.clicked.connect(self._run_import)
        btn_layout.addWidget(self._btn_import)
        grid.addLayout(btn_layout, row, 0, 1, 3)

        # ---- Separator ----
        row += 1
        sep = QFrame()
        sep.setFrameShape(QFrame.Shape.HLine)
        sep.setFrameShadow(QFrame.Shadow.Sunken)
        grid.addWidget(sep, row, 0, 1, 3)

        # ---- Photo Layer Tools (View + Update) ----
        row += 1
        grp = QGroupBox(tr("Photo Layer Tools"))
        grp_layout = QVBoxLayout(grp)

        # Row 1: Layer combo + Refresh
        layer_row = QHBoxLayout()
        layer_row.addWidget(QLabel(tr("Layer:")))
        self._combo_view_layer = QComboBox()
        self._combo_view_layer.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        layer_row.addWidget(self._combo_view_layer)
        btn_refresh = QPushButton(tr("Refresh"))
        btn_refresh.clicked.connect(self._refresh_layer_combos)
        layer_row.addWidget(btn_refresh)
        grp_layout.addLayout(layer_row)

        # Row 2: Click + Update buttons
        btn_row = QHBoxLayout()
        self._btn_click = QPushButton(tr("🖱️ Click Photos on Map"))
        self._btn_click.setMinimumHeight(36)
        self._btn_click.clicked.connect(self._activate_click_tool)
        btn_row.addWidget(self._btn_click)
        self._btn_update = QPushButton(tr("🔄 Update Layer"))
        self._btn_update.setMinimumHeight(36)
        self._btn_update.clicked.connect(self._run_update)
        btn_row.addWidget(self._btn_update)
        grp_layout.addLayout(btn_row)

        grid.addWidget(grp, row, 0, 1, 3)

        grid.setRowStretch(row + 1, 1)
        return tab

    # ---- Tab 2: Export ----

    def _build_export_tab(self):
        tab = QWidget()
        layout = QVBoxLayout(tab)

        grp = QGroupBox(tr("Export Photos from Layer"))
        grp_layout = QGridLayout(grp)
        grp_layout.addWidget(QLabel(tr("Layer:")), 0, 0)
        self._combo_export_layer = QComboBox()
        self._combo_export_layer.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        grp_layout.addWidget(self._combo_export_layer, 0, 1)

        grp_layout.addWidget(QLabel(tr("Output folder:")), 1, 0)
        self._inp_export_folder = QLineEdit()
        grp_layout.addWidget(self._inp_export_folder, 1, 1)
        btn_exp_browse = QPushButton(tr("Browse..."))
        btn_exp_browse.clicked.connect(self._browse_export_folder)
        grp_layout.addWidget(btn_exp_browse, 1, 2)
        layout.addWidget(grp)

        self._btn_export = QPushButton(tr("📤 Export All Photos"))
        self._btn_export.setMinimumHeight(36)
        self._btn_export.clicked.connect(self._run_export)
        layout.addWidget(self._btn_export)
        layout.addStretch()
        return tab

    # ------------------------------------------------------------------
    # Browse Handlers
    # ------------------------------------------------------------------

    def _browse_input(self):
        folder = QFileDialog.getExistingDirectory(
            self, tr("Select photo folder"))
        if folder:
            self._inp_folder.setText(folder)
            if not self._inp_output.text():
                name = os.path.basename(folder)
                drv, ext = list(self._FORMATS.values())[0]
                self._inp_output.setText(
                    os.path.join(folder, f"{name}_photos{ext}"))

    def _browse_output(self):
        fmt_key = self._combo_format.currentText()
        _, ext = self._FORMATS.get(fmt_key, ("GPKG", ".gpkg"))
        path, _ = QFileDialog.getSaveFileName(
            self, tr("Save output file"), "", fmt_key)
        if path:
            if not path.lower().endswith(ext):
                path += ext
            self._inp_output.setText(path)

    def _browse_relroot(self):
        folder = QFileDialog.getExistingDirectory(
            self, tr("Select relative root folder"))
        if folder:
            self._inp_relroot.setText(folder)

    def _browse_export_folder(self):
        folder = QFileDialog.getExistingDirectory(
            self, tr("Select export folder"))
        if folder:
            self._inp_export_folder.setText(folder)

    def _update_output_ext(self, fmt_key):
        current = self._inp_output.text()
        if current:
            base, _ = os.path.splitext(current)
            _, ext = self._FORMATS.get(fmt_key, ("GPKG", ".gpkg"))
            self._inp_output.setText(base + ext)

    # ------------------------------------------------------------------
    # Layer Combo Management
    # ------------------------------------------------------------------

    def _refresh_layer_combos(self):
        """Populate layer combo boxes with current point layers."""
        for combo in (self._combo_view_layer,
                      self._combo_export_layer):
            combo.clear()

        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.type() == LayerType.Vector and lyr.geometryType() == 0:
                for combo in (self._combo_view_layer,
                              self._combo_export_layer):
                    combo.addItem(lyr.name())

    def showEvent(self, event):
        """Refresh combos when dialog is shown."""
        super().showEvent(event)
        self._refresh_layer_combos()

    # ------------------------------------------------------------------
    # Tab 1: Import Logic
    # ------------------------------------------------------------------

    def _run_import(self):
        """Execute the photo import process."""
        folder = self._inp_folder.text().strip()
        output = self._inp_output.text().strip()

        if not folder or not os.path.isdir(folder):
            QMessageBox.warning(self, tr("Error"),
                                tr("Please select a valid input folder."))
            return
        if not output:
            QMessageBox.warning(self, tr("Error"),
                                tr("Please specify an output file."))
            return

        # Scan photos
        photos = scan_photos_in_folder(folder)
        if not photos:
            QMessageBox.information(
                self, tr("Flycam Photos"),
                tr("No JPEG photos found in the selected folder."))
            return

        # Canvas extent filter
        extent = None
        if self._chk_extent.isChecked():
            extent = self.iface.mapCanvas().extent()

        # Get format info
        fmt_key = self._combo_format.currentText()
        driver, ext = self._FORMATS.get(fmt_key, ("GPKG", ".gpkg"))

        # Relative root
        rel_root = self._inp_relroot.text().strip() or None
        web_root = self._inp_webroot.text().strip() or None

        # Run import as QgsTask
        task = _ImportTask(
            photos, output, driver, rel_root, web_root,
            extent, self.iface, self
        )
        task.error_msg = ""
        task.taskCompleted.connect(
            lambda: self._on_import_done(output, True))
        task.taskTerminated.connect(
            lambda: self._capture_error_and_finish(task, output))

        self._btn_import.setEnabled(False)
        self._progress.setVisible(True)
        self._progress.setRange(0, 0)  # Indeterminate

        QgsApplication.taskManager().addTask(task)

    def _capture_error_and_finish(self, task, output_path):
        """Capture error message from task and delegate to done handler."""
        self._last_task_error = getattr(task, 'error_msg', '')
        self._on_import_done(output_path, False)

    def _on_import_done(self, output_path, success):
        """Handle import completion."""
        self._btn_import.setEnabled(True)
        self._progress.setVisible(False)

        if success and os.path.isfile(output_path):
            layer_name = os.path.splitext(
                os.path.basename(output_path))[0]
            layer = QgsVectorLayer(output_path, layer_name, "ogr")
            if layer.isValid():
                # Add MapTip for photo thumbnail on hover
                layer.setMapTipTemplate(
                    '<table>'
                    '<tr><th>[% "Name" %]</th></tr>'
                    '<tr><th><img src="file:///[% "Path" %]" '
                    'width="350" height="250"></th></tr>'
                    '</table>'
                )

                # Render photo thumbnails at each point
                self._apply_photo_renderer(layer)

                QgsProject.instance().addMapLayer(layer)
                self._imported_layer_name = layer_name
                self._refresh_layer_combos()
                self.iface.messageBar().pushSuccess(
                    "LVT4U — Flycam",
                    f"✅ Imported {layer.featureCount()} photos → {layer_name}"
                )
            else:
                self.iface.messageBar().pushWarning(
                    "LVT4U — Flycam",
                    "Output file created but layer is invalid."
                )
        elif not success:
            err = getattr(self, '_last_task_error', '')
            self.iface.messageBar().pushWarning(
                "LVT4U — Flycam",
                f"❌ Import failed. {err}" if err
                else "❌ Import failed. Check input folder and permissions."
            )

    # ------------------------------------------------------------------
    # Photo Thumbnail Renderer
    # ------------------------------------------------------------------

    @staticmethod
    def _apply_photo_renderer(layer):
        """Apply photo point style with camera icon and MapTip preview.

        Uses a QML style file (like ImportPhotos plugin) for reliable
        cross-version rendering. Falls back to simple marker if QML missing.
        """
        # Try to load QML style
        qml_path = os.path.join(
            os.path.dirname(__file__), "photos.qml")

        if os.path.isfile(qml_path):
            layer.loadNamedStyle(qml_path)
        else:
            # Fallback: camera-style simple marker
            from qgis.core import (
                QgsMarkerSymbol, QgsSingleSymbolRenderer)
            symbol = QgsMarkerSymbol.createSimple({
                'name': 'circle',
                'color': '30,120,220,200',
                'outline_color': '255,255,255,255',
                'outline_width': '0.6',
                'size': '3.5',
                'size_unit': 'MM',
            })
            layer.setRenderer(QgsSingleSymbolRenderer(symbol))

        layer.triggerRepaint()

    # ------------------------------------------------------------------
    # View Logic
    # ------------------------------------------------------------------

    def _activate_click_tool(self):
        """Activate the map click tool for viewing photos."""
        layer_name = self._combo_view_layer.currentText()
        if not layer_name:
            QMessageBox.warning(self, tr("Error"),
                                tr("Please select a photo layer."))
            return

        from .map_tool import FlycamClickTool
        from .viewer import PhotoWindow

        canvas = self.iface.mapCanvas()

        if self._click_tool is None:
            self._click_tool = FlycamClickTool(canvas, self.iface)

        self._click_tool.set_layer_name(layer_name)

        if self._viewer is None:
            self._viewer = PhotoWindow(self.iface.mainWindow(), self.iface)

        # Connect signal
        try:
            self._click_tool.photoClicked.disconnect()
        except Exception:  # nosec B110
            pass  # nosec B110
        self._click_tool.photoClicked.connect(self._on_photo_clicked)

        canvas.setMapTool(self._click_tool)
        self.iface.messageBar().pushInfo(
            "LVT4U — Flycam",
            tr("Click tool active — click on photo points in the map.")
        )

    def _on_photo_clicked(self, photo_path, attrs):
        """Handle photo click from map tool."""
        # Build photo list from layer
        layer_name = self._combo_view_layer.currentText()
        photo_list = []
        layers = QgsProject.instance().mapLayersByName(layer_name)
        if layers:
            for feat in layers[0].getFeatures():
                p = feat.attribute('Path')
                if p and os.path.isfile(str(p)):
                    photo_list.append(str(p))

        if self._viewer is None:
            from .viewer import PhotoWindow
            self._viewer = PhotoWindow(self.iface.mainWindow(), self.iface)

        self._viewer.show_photo(photo_path, attrs, photo_list)

    # ------------------------------------------------------------------
    # Update Logic
    # ------------------------------------------------------------------

    def _run_update(self):
        """Sync layer with photo folder — add new / remove missing."""
        layer_name = self._combo_view_layer.currentText()
        if not layer_name:
            QMessageBox.warning(self, tr("Error"),
                                tr("Please select a layer to update."))
            return

        layers = QgsProject.instance().mapLayersByName(layer_name)
        if not layers:
            return
        layer = layers[0]

        # Get existing paths
        existing_paths = set()
        for feat in layer.getFeatures():
            p = feat.attribute('Path')
            if p:
                existing_paths.add(str(p))

        if not existing_paths:
            QMessageBox.information(
                self, tr("Flycam Photos"),
                tr("Layer has no photo paths to sync."))
            return

        # Determine source folder from first photo path
        sample_path = next(iter(existing_paths))
        source_folder = os.path.dirname(sample_path)

        # Scan current photos
        current_photos = set(scan_photos_in_folder(source_folder))

        # Find new and removed
        new_photos = current_photos - existing_paths
        removed_photos = existing_paths - current_photos

        added = 0
        removed = 0

        layer.startEditing()

        # Remove features for missing photos
        if removed_photos:
            to_delete = []
            for feat in layer.getFeatures():
                p = feat.attribute('Path')
                if p and str(p) in removed_photos:
                    to_delete.append(feat.id())
            for fid in to_delete:
                layer.deleteFeature(fid)
                removed += 1

        # Add new photos
        for photo_path in sorted(new_photos):
            info = read_photo_exif(photo_path)
            if info is None:
                continue

            feat = QgsFeature(layer.fields())
            point = QgsPointXY(info['lon'], info['lat'])
            feat.setGeometry(QgsGeometry.fromPointXY(point))

            name = os.path.basename(photo_path)
            _set_attr(feat, layer, 'Name', name)
            _set_attr(feat, layer, 'Path', photo_path)
            _set_attr(feat, layer, 'Lon', info['lon'])
            _set_attr(feat, layer, 'Lat', info['lat'])
            _set_attr(feat, layer, 'Altitude', info['altitude'])
            _set_attr(feat, layer, 'Azimuth', info['azimuth'])
            _set_attr(feat, layer, 'Date', info['date'])
            _set_attr(feat, layer, 'Time', info['time'])
            _set_attr(feat, layer, 'Cam_Maker', info['cam_maker'])
            _set_attr(feat, layer, 'Cam_Model', info['cam_model'])

            layer.addFeature(feat)
            added += 1

        layer.commitChanges()

        self.iface.messageBar().pushSuccess(
            "LVT4U — Flycam",
            f"✅ Update: +{added} added, -{removed} removed"
        )

    # ------------------------------------------------------------------
    # Export Logic
    # ------------------------------------------------------------------

    def _run_export(self):
        """Export all photo files from layer to a folder."""
        import shutil

        layer_name = self._combo_export_layer.currentText()
        out_folder = self._inp_export_folder.text().strip()

        if not layer_name:
            QMessageBox.warning(self, tr("Error"),
                                tr("Please select a layer."))
            return
        if not out_folder:
            QMessageBox.warning(self, tr("Error"),
                                tr("Please select an output folder."))
            return

        os.makedirs(out_folder, exist_ok=True)

        layers = QgsProject.instance().mapLayersByName(layer_name)
        if not layers:
            return

        exported = 0
        skipped = 0
        for feat in layers[0].getFeatures():
            src = feat.attribute('Path')
            if not src or not os.path.isfile(str(src)):
                skipped += 1
                continue
            dst = os.path.join(out_folder, os.path.basename(str(src)))
            try:
                shutil.copy2(str(src), dst)
                exported += 1
            except Exception:
                skipped += 1

        self.iface.messageBar().pushSuccess(
            "LVT4U — Flycam",
            f"✅ Exported {exported} photos ({skipped} skipped) → {out_folder}"
        )

    # ==================================================================
    # Tab 3: GeoView — Drone Photo Viewer
    # ==================================================================

    def _build_geoview_tab(self):
        """Build the GeoView tab for loading drone photos as map layers."""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setSpacing(8)

        # --- Input group ---
        grp_input = QGroupBox(tr("Drone Photo Folder"))
        g = QGridLayout(grp_input)
        g.setSpacing(6)

        g.addWidget(QLabel(tr("Photo folder:")), 0, 0)
        self._gv_folder = QLineEdit()
        self._gv_folder.setPlaceholderText(tr("Select folder containing drone photos..."))
        g.addWidget(self._gv_folder, 0, 1)
        btn_browse = QPushButton(tr("Browse..."))
        btn_browse.clicked.connect(self._gv_browse_folder)
        g.addWidget(btn_browse, 0, 2)

        g.addWidget(QLabel(tr("Flight altitude (m):")), 1, 0)
        self._gv_altitude = QSpinBox()
        self._gv_altitude.setRange(0, 2000)
        self._gv_altitude.setValue(0)
        self._gv_altitude.setSpecialValueText(tr("Auto-detect"))
        self._gv_altitude.setToolTip(
            tr("Set 0 for auto-detection from EXIF.\n"
               "Or enter the actual flight altitude AGL (above ground)."))
        g.addWidget(self._gv_altitude, 1, 1, 1, 2)

        g.addWidget(QLabel(tr("Camera:")), 2, 0)
        self._gv_camera = QComboBox()
        self._gv_camera.addItem(tr("Auto-detect from EXIF"), "auto")
        # Add known camera profiles
        from .geoview_engine import SENSOR_PROFILES
        for model_id, (sw, fl, name) in SENSOR_PROFILES.items():
            self._gv_camera.addItem(
                f"{name} ({model_id}) — {sw}mm sensor", model_id)
        g.addWidget(self._gv_camera, 2, 1, 1, 2)

        self._gv_extent_check = QCheckBox(
            tr("Only photos within current map extent"))
        g.addWidget(self._gv_extent_check, 3, 0, 1, 3)

        layout.addWidget(grp_input)

        # --- Load button ---
        self._gv_btn_load = QPushButton("🛰️ " + tr("Load GeoView"))
        self._gv_btn_load.setMinimumHeight(40)
        self._gv_btn_load.setStyleSheet(
            "QPushButton { background: qlineargradient("
            "x1:0, y1:0, x2:0, y2:1, "
            "stop:0 #1565C0, stop:1 #0D47A1); "
            "color: white; border-radius: 6px; "
            "font-size: 14px; font-weight: bold; }"
            "QPushButton:hover { background: qlineargradient("
            "x1:0, y1:0, x2:0, y2:1, "
            "stop:0 #1976D2, stop:1 #1565C0); }"
            "QPushButton:disabled { background: #9E9E9E; }")
        self._gv_btn_load.clicked.connect(self._gv_run_load)
        layout.addWidget(self._gv_btn_load)

        # --- Progress ---
        self._gv_progress = QProgressBar()
        self._gv_progress.setVisible(False)
        layout.addWidget(self._gv_progress)

        # --- Status / Stats ---
        self._gv_status = QTextEdit()
        self._gv_status.setReadOnly(True)
        self._gv_status.setMaximumHeight(140)
        self._gv_status.setStyleSheet(
            "QTextEdit { background: #F5F5F5; border: 1px solid #E0E0E0; "
            "border-radius: 4px; font-family: Consolas, monospace; "
            "font-size: 12px; }")
        self._gv_status.setPlaceholderText(
            tr("Select a drone photo folder and click Load GeoView..."))
        layout.addWidget(self._gv_status)

        layout.addStretch()
        return tab

    def _gv_browse_folder(self):
        """Browse for drone photo folder."""
        folder = QFileDialog.getExistingDirectory(
            self, tr("Select Drone Photo Folder"),
            self._gv_folder.text() or os.path.expanduser("~"))
        if folder:
            self._gv_folder.setText(folder)
            # Count photos
            from .geoview_engine import scan_photo_folder
            photos = scan_photo_folder(folder)
            self._gv_status.setPlainText(
                f"📁 {folder}\n"
                f"📷 {len(photos)} photos found\n\n"
                f"Click 'Load GeoView' to display on map.")

    def _gv_run_load(self):
        """Run GeoView engine to load drone photos as map layers."""
        folder = self._gv_folder.text().strip()
        if not folder or not os.path.isdir(folder):
            QMessageBox.warning(
                self, tr("GeoView"),
                tr("Please select a valid drone photo folder."))
            return

        # Get settings
        alt = self._gv_altitude.value()  # 0 = auto
        extent_filter = None
        if self._gv_extent_check.isChecked():
            ext = self.iface.mapCanvas().extent()
            extent_filter = (ext.xMinimum(), ext.yMinimum(),
                             ext.xMaximum(), ext.yMaximum())

        # Disable UI during processing
        self._gv_btn_load.setEnabled(False)
        self._gv_progress.setVisible(True)
        self._gv_progress.setValue(0)
        self._gv_status.clear()

        from .geoview_engine import GeoViewEngine

        def on_progress(pct, msg):
            self._gv_progress.setValue(pct)
            self._gv_status.append(msg)
            QgsApplication.processEvents()

        try:
            engine = GeoViewEngine(
                photo_folder=folder,
                flight_altitude_agl=alt if alt > 0 else None,
                progress_callback=on_progress,
                extent_filter=extent_filter,
            )
            engine.run()

            # Load into QGIS layer group
            group = engine.load_to_qgis(
                group_name="📷 Drone Photos",
                max_visible=10,
            )

            # Zoom to group extent
            layers = [node.layer() for node in group.findLayers()
                      if node.layer() and node.layer().isValid()]
            if layers:
                combined = layers[0].extent()
                for lyr in layers[1:]:
                    combined.combineExtentWith(lyr.extent())
                self.iface.mapCanvas().setExtent(combined)
                self.iface.mapCanvas().refresh()

            # Show stats
            from .geoview_engine import calculate_gsd, get_sensor_profile
            gsd = 0
            if engine.photo_info:
                engine.photo_info[0]
                gps = {"model": "", "focal_length": None}
                sw, fl, _ = get_sensor_profile(gps.get("model", ""))
                gsd = calculate_gsd(engine.detected_agl, fl, sw, 4032) * 100

            stats = (
                f"\n{'=' * 50}\n"
                f"📊 GeoView Summary\n"
                f"{'=' * 50}\n"
                f"📷 Photos loaded: {len(engine.vrt_files)}\n"
                f"📹 Camera: {engine.detected_camera}\n"
                f"🏔️ Flight altitude: {engine.detected_agl:.0f}m AGL\n"
                f"📐 GSD: ~{gsd:.1f} cm/pixel\n"
                f"🗺️ Coverage: {engine.total_coverage_ha:.1f} ha\n"
                f"📁 VRT folder: .geoview_vrt/\n"
                f"{'=' * 50}\n"
                f"✅ Layer group '📷 Drone Photos' created!\n"
                f"   Toggle each photo ON/OFF in Layers panel."
            )
            self._gv_status.append(stats)

            self.iface.messageBar().pushSuccess(
                "GeoView",
                tr("{n} drone photos loaded as map layers.").format(
                    n=len(engine.vrt_files)))

        except RuntimeError as e:
            QMessageBox.critical(self, "GeoView", str(e))
        except Exception as e:
            QMessageBox.critical(
                self, "GeoView",
                f"Unexpected error:\n{e}")
        finally:
            self._gv_btn_load.setEnabled(True)
            self._gv_progress.setVisible(False)


# ======================================================================
# Helper: safe attribute setter
# ======================================================================

def _set_attr(feature, layer, field_name, value):
    """Set a feature attribute by field name if it exists."""
    idx = layer.fields().indexFromName(field_name)
    if idx >= 0:
        feature.setAttribute(idx, value)


# ======================================================================
# Background Import Task
# ======================================================================

class _ImportTask(QgsTask):
    """Background task for importing geotagged photos."""

    def __init__(self, photos, output_path, driver,
                 rel_root, web_root, extent, iface, dialog):
        super().__init__("Flycam Photo Import", QgsTask.Flag.CanCancel)
        self.photos = photos
        self.output_path = output_path
        self.driver = driver
        self.rel_root = rel_root
        self.web_root = web_root
        self.extent = extent
        self.iface = iface
        self.dialog = dialog

    def run(self):
        """Process photos and write output file."""
        try:
            return self._do_run()
        except Exception as exc:
            import traceback
            self.error_msg = f"{type(exc).__name__}: {exc}\n{traceback.format_exc()}"
            return False

    def _do_run(self):
        """Actual import logic."""
        # Define fields
        fields = QgsFields()
        fields.append(QgsField("ID", QVariant.Int))
        fields.append(QgsField("Name", QVariant.String, len=255))
        fields.append(QgsField("Date", QVariant.String, len=20))
        fields.append(QgsField("Time", QVariant.String, len=20))
        fields.append(QgsField("Lon", QVariant.Double))
        fields.append(QgsField("Lat", QVariant.Double))
        fields.append(QgsField("Altitude", QVariant.Double))
        fields.append(QgsField("North", QVariant.String, len=5))
        fields.append(QgsField("Azimuth", QVariant.Double))
        fields.append(QgsField("Cam_Maker", QVariant.String, len=100))
        fields.append(QgsField("Cam_Model", QVariant.String, len=100))
        fields.append(QgsField("Title", QVariant.String, len=255))
        fields.append(QgsField("Comment", QVariant.String, len=500))
        fields.append(QgsField("Path", QVariant.String, len=500))
        fields.append(QgsField("Thumb", QVariant.String, len=500))
        fields.append(QgsField("RelPath", QVariant.String, len=500))
        fields.append(QgsField("Timestamp", QVariant.String, len=30))

        crs = QgsCoordinateReferenceSystem("EPSG:4326")
        ctx = QgsCoordinateTransformContext()

        # Writer options
        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = self.driver
        opts.fileEncoding = "UTF-8"

        # Ensure output directory exists
        out_dir = os.path.dirname(self.output_path)
        if out_dir:
            os.makedirs(out_dir, exist_ok=True)

        writer = QgsVectorFileWriter.create(
            self.output_path, fields, QgsWkbTypes.Type.Point,
            crs, ctx, opts
        )

        if writer.hasError() != QgsVectorFileWriter.WriterError.NoError:
            self.error_msg = f"Writer error: {writer.errorMessage()}"
            del writer
            return False

        total = len(self.photos)
        count = 0
        skipped = 0

        for i, photo_path in enumerate(self.photos):
            if self.isCanceled():
                del writer
                return False

            self.setProgress((i + 1) / total * 100)

            info = read_photo_exif(photo_path)
            if info is None:
                skipped += 1
                continue

            # Canvas extent filter
            if self.extent:
                pt = QgsPointXY(info['lon'], info['lat'])
                if not self.extent.contains(pt):
                    skipped += 1
                    continue

            count += 1
            feat = QgsFeature()
            feat.setFields(fields)
            feat.setGeometry(
                QgsGeometry.fromPointXY(
                    QgsPointXY(info['lon'], info['lat'])))

            feat.setAttribute("ID", count)
            feat.setAttribute("Name", os.path.basename(photo_path))
            feat.setAttribute("Date", info['date'])
            feat.setAttribute("Time", info['time'])
            feat.setAttribute("Lon", info['lon'])
            feat.setAttribute("Lat", info['lat'])
            feat.setAttribute("Altitude", info['altitude'])
            feat.setAttribute("North", "N" if info['lat'] >= 0 else "S")
            feat.setAttribute("Azimuth", info['azimuth'])
            feat.setAttribute("Cam_Maker", info['cam_maker'])
            feat.setAttribute("Cam_Model", info['cam_model'])
            feat.setAttribute("Title", info['title'])
            feat.setAttribute("Comment", info['comment'])
            feat.setAttribute("Path", photo_path)

            # Generate thumbnail (128px) for map display
            thumb_path = self._make_thumbnail(photo_path)
            feat.setAttribute("Thumb", thumb_path if thumb_path else "")
            feat.setAttribute("Timestamp", info['timestamp'])

            # Relative path
            if self.rel_root:
                try:
                    rel = os.path.relpath(photo_path, self.rel_root)
                    if self.web_root:
                        rel = self.web_root.rstrip('/') + '/' + rel.replace('\\', '/')
                    feat.setAttribute("RelPath", rel)
                except ValueError:
                    feat.setAttribute("RelPath", photo_path)
            else:
                feat.setAttribute("RelPath", photo_path)

            writer.addFeature(feat)

        del writer

        if count == 0:
            self.error_msg = (
                f"No geotagged photos found. "
                f"Scanned {total} photos, {skipped} had no GPS data.")
            return False

        return True

    def _make_thumbnail(self, photo_path, size=128):
        """Create a small JPEG thumbnail for map display.

        Returns:
            str: Path to the thumbnail file, or None on failure.
        """
        try:
            from PIL import Image

            # Create .thumbs directory next to output file
            out_dir = os.path.dirname(self.output_path)
            thumb_dir = os.path.join(out_dir, ".thumbs")
            os.makedirs(thumb_dir, exist_ok=True)

            # Thumbnail filename
            base = os.path.splitext(os.path.basename(photo_path))[0]
            thumb_path = os.path.join(thumb_dir, f"{base}_thumb.jpg")

            # Skip if already exists
            if os.path.isfile(thumb_path):
                return thumb_path

            img = Image.open(photo_path)
            img.thumbnail((size, size), Image.LANCZOS)

            # Convert RGBA → RGB for JPEG
            if img.mode in ('RGBA', 'P'):
                img = img.convert('RGB')

            img.save(thumb_path, "JPEG", quality=75)
            return thumb_path
        except Exception:
            return None
