# -*- coding: utf-8 -*-
"""
LVT4U Plugin Suite — Menu Coordinator.

Creates the top-level "LVT4U" menu on the QGIS menu bar and registers
all sub-module actions. Each module is loaded on-demand when the user
clicks the corresponding menu item.

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

Scientific basis:
    - QGIS API Documentation (docs.qgis.org)
    - Cartographic standards for menu organization
"""

import os

from qgis.PyQt.QtCore import QSettings
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QMenu, QToolBar

from .shared.i18n import tr


class LvtPlugin:
    """Top-level orchestrator for the LVT Plugin Suite.

    Responsibilities:
        - Create and manage the "LVT" menu on the QGIS menu bar
        - Register menu items for each module
        - Lazy-load module dialogs on first use
    """

    PLUGIN_NAME = "LVT4U"

    def __init__(self, iface):
        """Initialize the plugin.

        Args:
            iface: QgisInterface instance for interacting with the QGIS environment.
        """
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        self.lvt_menu = None
        self.toolbar = None

        self.actions = []
        self.submenus = []

        # Module dialog instances (lazy-loaded)
        self._dialogs = {}
        self._basemap_submenu = None

    # ------------------------------------------------------------------
    # QGIS Plugin Lifecycle
    # ------------------------------------------------------------------

    def initGui(self):
        """Build the LVT menu and toolbar, insert into QGIS."""
        # First-run language selection
        self._first_run_language()

        self.lvt_menu = QMenu(self.PLUGIN_NAME)
        self.iface.mainWindow().menuBar().insertMenu(
            self.iface.firstRightStandardMenu().menuAction(),
            self.lvt_menu,
        )
        self._build_menu()
        self._build_toolbar()
        self._install_style_library()

    def unload(self):
        """Remove the LVT menu from the QGIS menu bar and clean up."""
        # Close any open sub-dialogs
        for dlg in self._dialogs.values():
            try:
                dlg.close()
            except Exception:
                pass

        if self.lvt_menu:
            self.iface.mainWindow().menuBar().removeAction(
                self.lvt_menu.menuAction()
            )
            self.lvt_menu.deleteLater()
            self.lvt_menu = None

        try:
            if self.toolbar:
                import sip
                if not sip.isdeleted(self.toolbar):
                    self.toolbar.clear()
                    self.iface.mainWindow().removeToolBar(self.toolbar)
                    self.toolbar.setParent(None)
                    self.toolbar.deleteLater()
        except (RuntimeError, TypeError, Exception):
            pass  # C++ object already deleted by Qt
        self.toolbar = None

        self.actions.clear()
        self.submenus.clear()
        self._dialogs.clear()

    # ------------------------------------------------------------------
    # Menu Construction
    # ------------------------------------------------------------------

    def _icon(self, name):
        """Resolve an icon path from the icons/ directory.

        Tries SVG first (sharp at any size), then falls back to PNG.

        Args:
            name: Icon filename (e.g. "layout.png").

        Returns:
            QIcon instance, or empty QIcon if file not found.
        """
        base, _ = os.path.splitext(name)
        icons_dir = os.path.join(self.plugin_dir, "icons")
        # Prefer SVG for crisp rendering at any toolbar size
        for ext in (".svg", ".png"):
            path = os.path.join(icons_dir, base + ext)
            if os.path.isfile(path):
                return QIcon(path)
        return QIcon()

    def _add_action(self, icon_name, text, callback, menu=None):
        """Create a QAction and add it to the specified menu.

        Args:
            icon_name: Filename of the icon in icons/.
            text: Display text (will be translated).
            callback: Callable invoked when the action is triggered.
            menu: Target QMenu. Defaults to the top-level LVT menu.

        Returns:
            QAction: The created action.
        """
        action = QAction(
            self._icon(icon_name),
            tr(text),
            self.iface.mainWindow())
        action.triggered.connect(callback)
        target = menu if menu is not None else self.lvt_menu
        target.addAction(action)
        self.actions.append(action)
        return action

    def _add_submenu(self, icon_name, text):
        """Create a submenu under the LVT menu.

        Args:
            icon_name: Filename of the icon in icons/.
            text: Display text (will be translated).

        Returns:
            QMenu: The created submenu.
        """
        submenu = QMenu(tr(text), self.iface.mainWindow())
        submenu.setIcon(self._icon(icon_name))
        self.lvt_menu.addMenu(submenu)
        self.submenus.append(submenu)
        return submenu

    def _build_menu(self):
        """Assemble the LVT menu — 5 workflow groups."""

        def _section(title):
            """Add a bold section header to the menu."""
            sep = QAction(title, self.iface.mainWindow())
            sep.setEnabled(False)
            font = sep.font()
            font.setBold(True)
            sep.setFont(font)
            self.lvt_menu.addAction(sep)

        # ═══════════════════════════════════════════════════════════
        # GROUP 1 — Setup / Nền tảng
        # ═══════════════════════════════════════════════════════════
        _section("── 📐 Setup ──")
        basemap_sub = self._add_submenu("basemap.png", "Basemap")
        self._basemap_submenu = basemap_sub
        self._build_basemap_menu(basemap_sub)
        self._add_action("crs.png", "Coordinate System", self._open_crs_tools)
        self._add_action("coord.png", "Coordinate Converter",
                         self._open_coord_convert)
        self._add_action("font.png", "Font Converter", self._open_font)

        self.lvt_menu.addSeparator()

        # ═══════════════════════════════════════════════════════════
        # GROUP 2 — Input / Nhập dữ liệu
        # ═══════════════════════════════════════════════════════════
        _section("── 📥 Input ──")
        self._add_action("excel.png", "Excel → GIS", self._open_excel_gis)
        self._add_action("flycam.png", "Flycam Photos", self._open_flycam)
        self._add_action("dwg.png", "DWG/DXF → SHP", self._open_dwg_converter)

        self.lvt_menu.addSeparator()

        # ═══════════════════════════════════════════════════════════
        # GROUP 3 — Processing / Xử lý
        # ═══════════════════════════════════════════════════════════
        _section("── ⚙️ Processing ──")
        self._add_action(
            "polyfix.png", "TopoFix",
            self._open_polyfix)
        self._add_action(
            "layer_editor.png", "Layer Editor",
            self._open_layer_editor)
        thematic_sub = self._add_submenu(
            "thematic.png", "Forest Status Thematic Map")
        self._add_action(
            "thematic.png",
            "Create Thematic Map",
            self._open_thematic_tt16,
            thematic_sub)
        thematic_sub.addSeparator()
        self._add_action(
            "thematic.png",
            "Create Plot Labels",
            self._open_thematic_labels,
            thematic_sub)
        try:
            self._add_action(
                "biodiversity.svg", "Biodiversity",
                self._open_biodiversity)
        except Exception:
            pass  # Biodiversity not available in this release
        self._add_action(
            "satellite.png", "Satellite Downloader",
            self._open_satellite_dl)

        self.lvt_menu.addSeparator()

        # ═══════════════════════════════════════════════════════════
        # GROUP 4 — Output / Xuất bản
        # ═══════════════════════════════════════════════════════════
        _section("── 📤 Output ──")
        self._add_action("layout.png", "Map Layout", self._open_layout)
        self._add_action("fsc_harvest.png", "FSC Plot Layout",
                         self._open_fsc_harvest)
        self._add_action("package.png", "Package Map", self._open_packager)
        kml_sub = self._add_submenu("kml.png", "KML")
        self._add_action(
            "kml.png",
            "SHP → KML/KMZ",
            self._open_kml_export,
            kml_sub)
        self._add_action(
            "kml.png",
            "KML/KMZ → SHP",
            self._open_kml_import,
            kml_sub)
        eudr_sub = self._add_submenu("eudr.png", "EUDR")
        self._add_action(
            "eudr.png", "Export GeoJSON", self._open_eudr, eudr_sub
        )
        self._add_action(
            "eudr.png", "Reference Documents",
            self._open_eudr_reference, eudr_sub
        )
        self._add_action("mbtiles.png", "Create MBTiles", self._open_mbtiles)
        self._add_action("sync.png", "LVT Sync", self._open_sync)

        self.lvt_menu.addSeparator()

        # ═══════════════════════════════════════════════════════════
        # GROUP 5 — Reference / Tham khảo
        # ═══════════════════════════════════════════════════════════
        _section("── 📚 Reference ──")
        self._add_action("legal.png", "Legal Documents", self._open_legal)
        self._add_action("about.png", "About", self._open_about)

    # ------------------------------------------------------------------
    # Basemap Registry
    # ------------------------------------------------------------------

    # Basemap catalog: (display_name, url, zmin, zmax)
    _BASEMAPS = {
        "🌍 Google": [
            ("Google Maps",
             "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}",
             0,
             22),
            ("Google Satellite",
             "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
             0,
             22),
            ("Google Hybrid",
             "https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}",
             0,
             22),
            ("Google Terrain Hybrid",
             "https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}",
             0,
             19),
        ],
        "🗺️ Esri": [
            ("Esri Satellite",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
             0,
             22),
            ("Esri Street",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
             0,
             22),
            ("Esri Topographic",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
             0,
             22),
            ("Esri National Geographic",
             "https://server.arcgisonline.com/arcgis/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}",
             0,
             22),
            ("Esri Gray (light)",
             "https://server.arcgisonline.com/arcgis/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}",
             0,
             16),
            ("Esri Gray (dark)",
             "https://server.arcgisonline.com/arcgis/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}",
             0,
             16),
            ("Esri Physical",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}",
             0,
             22),
            ("Esri Shaded Relief",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}",
             0,
             13),
            ("Esri Terrain",
             "https://server.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}",
             0,
             13),
        ],
        "🎨 CartoDB": [
            ("Carto Light",
             "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
             0,
             20),
            ("Carto Dark",
             "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
             0,
             20),
            ("Carto Antique",
             "https://cartocdn_a.global.ssl.fastly.net/base-antique/{z}/{x}/{y}.png",
             0,
             20),
            ("Carto Eco",
             "https://cartocdn_a.global.ssl.fastly.net/base-eco/{z}/{x}/{y}.png",
             0,
             20),
        ],
        "🗾 OpenStreetMap": [
            ("OpenStreetMap",
             "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
             0,
             19),
            ("OpenTopoMap",
             "https://tile.opentopomap.org/{z}/{x}/{y}.png",
             1,
             17),
        ],
        "🌐 Bing": [
            ("Bing VirtualEarth",
             "https://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1",
             0,
             22),
        ],
        "🔷 Other": [
            ("F4 Map 2D",
             "https://tile1.f4map.com/tiles/f4_2d/{z}/{x}/{y}.png",
             0,
             22),
        ],
    }

    def _build_basemap_menu(self, parent_menu):
        """Build flat basemap menu — one click to add any basemap."""
        for group_label, layers in self._BASEMAPS.items():
            # Add group label as disabled separator action
            sep_action = QAction(group_label, self.iface.mainWindow())
            sep_action.setEnabled(False)
            font = sep_action.font()
            font.setBold(True)
            sep_action.setFont(font)
            parent_menu.addAction(sep_action)
            for name, url, zmin, zmax in layers:
                action = QAction(
                    self._icon("basemap.png"), f"   {name}",
                    self.iface.mainWindow(),
                )
                action.triggered.connect(
                    lambda checked, n=name, u=url, lo=zmin, hi=zmax:
                        self._add_xyz_basemap(n, u, lo, hi)
                )
                parent_menu.addAction(action)
                self.actions.append(action)

    # ------------------------------------------------------------------
    # Module Launchers (Lazy Loading)
    # ------------------------------------------------------------------

    def _open_layout(self):
        """Open the Map Layout dialog."""
        from .layout.dialog import LvtDialog

        if "layout" not in self._dialogs:
            layout_dir = os.path.join(self.plugin_dir, "layout")
            self._dialogs["layout"] = LvtDialog(self.iface, layout_dir)

        dlg = self._dialogs["layout"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_fsc_harvest(self):
        """Open the FSC Plot Layout dialog."""
        from .fsc_harvest.dialog import FscHarvestDialog

        if "fsc_harvest" not in self._dialogs:
            self._dialogs["fsc_harvest"] = FscHarvestDialog(
                self.iface, self.plugin_dir)

        dlg = self._dialogs["fsc_harvest"]
        dlg._refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_kml_export(self):
        """Open the KML dialog (SHP to KML/KMZ tab)."""
        self._ensure_kml_dialog()
        dlg = self._dialogs["kml"]
        dlg.tabs.setCurrentIndex(0)
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_kml_import(self):
        """Open the KML dialog (KML/KMZ to SHP tab)."""
        self._ensure_kml_dialog()
        dlg = self._dialogs["kml"]
        dlg.tabs.setCurrentIndex(1)
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _ensure_kml_dialog(self):
        """Lazy-create the KML dialog instance."""
        if "kml" not in self._dialogs:
            from .kml.dialog import LvtKmlViewDialog
            self._dialogs["kml"] = LvtKmlViewDialog(
                self.iface, self.iface.mainWindow()
            )

    def _open_mbtiles(self):
        """Open the MBTiles creator dialog."""
        if "mbtiles" not in self._dialogs:
            from .mbtiles.dialog import MBTilesDialog
            self._dialogs["mbtiles"] = MBTilesDialog(self.iface)

        dlg = self._dialogs["mbtiles"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_sync(self):
        """Open the LVT Sync dialog."""
        if "sync" not in self._dialogs:
            from .sync.dialog import LVTSyncDialog
            self._dialogs["sync"] = LVTSyncDialog(self.iface)

        dlg = self._dialogs["sync"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_eudr(self):
        """Open the EUDR GeoJSON exporter dialog."""
        if "eudr" not in self._dialogs:
            from .eudr.dialog import EudrExportDialog
            self._dialogs["eudr"] = EudrExportDialog(self.iface)

        dlg = self._dialogs["eudr"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_eudr_reference(self):
        """Open the EUDR Reference Documents dialog."""
        if "eudr_ref" not in self._dialogs:
            from .eudr.reference_dialog import EudrReferenceDialog
            self._dialogs["eudr_ref"] = EudrReferenceDialog(self.iface)

        dlg = self._dialogs["eudr_ref"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_packager(self):
        """Open the Map Packager dialog."""
        if "packager" not in self._dialogs:
            from .map_packager.dialog import PackageMapDialog
            self._dialogs["packager"] = PackageMapDialog(self.iface)

        dlg = self._dialogs["packager"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_thematic_tt16(self):
        """Open the Thematic Map (TT16/2023) dialog."""
        if "tt16" not in self._dialogs:
            from .thematic_map.tt16_dialog import TT16Dialog
            self._dialogs["tt16"] = TT16Dialog(self.iface)

        dlg = self._dialogs["tt16"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_thematic_tt33(self):
        """Open the Thematic Map (TT33) dialog."""
        self._show_placeholder("Thematic Map — TT 33")

    def _open_thematic_labels(self):
        """Open the standalone Plot Labels dialog."""
        if "plot_labels" not in self._dialogs:
            from .thematic_map.plot_labels import PlotLabelsDialog
            self._dialogs["plot_labels"] = PlotLabelsDialog(self.iface)

        dlg = self._dialogs["plot_labels"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_layer_editor(self):
        """Open the Layer Editor dialog."""
        if "layer_editor" not in self._dialogs:
            from .layer_editor.dialog import LayerEditorDialog
            self._dialogs["layer_editor"] = LayerEditorDialog(self.iface)

        dlg = self._dialogs["layer_editor"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_polyfix(self):
        """Open the TopoFix topology check & fix dialog."""
        if "polyfix" not in self._dialogs:
            from .poly_fix.dialog import TopoFixDialog
            self._dialogs["polyfix"] = TopoFixDialog(self.iface)

        dlg = self._dialogs["polyfix"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_excel_gis(self):
        """Open the Excel → GIS importer dialog."""
        if "excel_gis" not in self._dialogs:
            from .excel_gis.dialog import ExcelGisDialog
            self._dialogs["excel_gis"] = ExcelGisDialog(self.iface)

        dlg = self._dialogs["excel_gis"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_dwg_converter(self):
        """Open the DWG/DXF to SHP converter dialog."""
        if "dwg_converter" not in self._dialogs:
            from .dwg_converter.dialog import DwgConverterDialog
            self._dialogs["dwg_converter"] = DwgConverterDialog(self.iface)

        dlg = self._dialogs["dwg_converter"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_legal(self):
        """Open the Legal Documents Library dialog."""
        if "legal" not in self._dialogs:
            from .legal_docs.dialog import LegalDocsDialog
            self._dialogs["legal"] = LegalDocsDialog(self.iface)

        dlg = self._dialogs["legal"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_font(self):
        """Open the Font Converter dialog."""
        if "font" not in self._dialogs:
            from .font_converter.dialog import FontConverterDialog
            self._dialogs["font"] = FontConverterDialog(self.iface)

        dlg = self._dialogs["font"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_crs_tools(self):
        """Open the CRS Tools dialog."""
        if "crs" not in self._dialogs:
            from .crs_tools.dialog import CrsToolsDialog
            self._dialogs["crs"] = CrsToolsDialog(self.iface)

        dlg = self._dialogs["crs"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_coord_convert(self):
        """Open the Coordinate Converter dialog."""
        if "coord" not in self._dialogs:
            from .coord_convert.dialog import CoordConverterDialog
            self._dialogs["coord"] = CoordConverterDialog(self.iface)

        dlg = self._dialogs["coord"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_biodiversity(self):
        """Open the Biodiversity (Đa dạng sinh học) launcher dialog."""
        if "biodiversity" not in self._dialogs:
            from .biodiversity.dialog import BiodiversityDialog
            self._dialogs["biodiversity"] = BiodiversityDialog(
                self.iface, self.iface.mainWindow())

        dlg = self._dialogs["biodiversity"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_flycam(self):
        """Open the Flycam Photos dialog."""
        if "flycam" not in self._dialogs:
            from .flycam.dialog import FlycamDialog
            self._dialogs["flycam"] = FlycamDialog(self.iface)

        dlg = self._dialogs["flycam"]
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_satellite_dl(self):
        """Open the Satellite Image Downloader dialog."""
        if "satellite_dl" not in self._dialogs:
            from .satellite_dl.dialog import SatelliteDlDialog
            self._dialogs["satellite_dl"] = SatelliteDlDialog(self.iface)

        dlg = self._dialogs["satellite_dl"]
        dlg.refresh_layers()
        dlg.show()
        dlg.raise_()
        dlg.activateWindow()

    def _open_about(self):
        """Open the About dialog."""
        from .about.dialog import AboutDialog

        if "about" not in self._dialogs:
            self._dialogs["about"] = AboutDialog(self.iface, self.plugin_dir)
        self._dialogs["about"].exec_()

    # ------------------------------------------------------------------
    # Toolbar
    # ------------------------------------------------------------------

    def _build_toolbar(self):
        """Create the LVT4U toolbar with icons for main modules."""
        self.toolbar = QToolBar("LVT4U", self.iface.mainWindow())
        self.toolbar.setObjectName("LVT4UToolbar")
        self.iface.addToolBar(self.toolbar)

        # Define toolbar actions — 5 workflow groups
        # Group 1: Setup
        toolbar_items = [
            ("basemap.png", "Basemap", self._show_basemap_popup),
            ("crs.png", "Coordinate System", self._open_crs_tools),
            ("coord.png", "Coordinate Converter", self._open_coord_convert),
            ("font.png", "Font Converter", self._open_font),
            (None, None, None),  # ── separator ──
            # Group 2: Input
            ("excel.png", "Excel → GIS", self._open_excel_gis),
            ("flycam.png", "Flycam Photos", self._open_flycam),
            ("dwg.png", "DWG/DXF → SHP", self._open_dwg_converter),
            (None, None, None),  # ── separator ──
            # Group 3: Processing
            ("polyfix.png", "TopoFix", self._open_polyfix),
            ("layer_editor.png", "Layer Editor", self._open_layer_editor),
            ("thematic.png", "Thematic Map", self._open_thematic_tt16),
            ("biodiversity.svg", "Biodiversity", self._open_biodiversity),
            ("satellite.png", "Satellite Downloader",
             self._open_satellite_dl),
            (None, None, None),  # ── separator ──
            # Group 4: Output
            ("layout.png", "Map Layout", self._open_layout),
            ("fsc_harvest.png", "FSC Plot Layout",
             self._open_fsc_harvest),
            ("package.png", "Package Map", self._open_packager),
            ("kml.png", "KML", self._open_kml_export),
            ("eudr.png", "EUDR", self._open_eudr),
            ("mbtiles.png", "Create MBTiles", self._open_mbtiles),
            ("sync.png", "LVT Sync", self._open_sync),
            (None, None, None),  # ── separator ──
            # Group 5: Reference
            ("legal.png", "Legal Documents", self._open_legal),
        ]

        for icon_name, text, callback in toolbar_items:
            if icon_name is None:
                self.toolbar.addSeparator()
            else:
                action = QAction(
                    self._icon(icon_name),
                    tr(text),
                    self.iface.mainWindow())
                action.setToolTip(tr(text))
                action.triggered.connect(callback)
                self.toolbar.addAction(action)
                self.actions.append(action)

    def _show_basemap_popup(self):
        """Show basemap submenu as popup at toolbar button position."""
        if self._basemap_submenu:
            from qgis.PyQt.QtGui import QCursor
            self._basemap_submenu.popup(QCursor.pos())

    # ------------------------------------------------------------------
    # First-Run Language Selection
    # ------------------------------------------------------------------

    def _first_run_language(self):
        """Show language picker on first install."""
        key = "LVT4U/first_run_done"
        if QSettings().value(key, False, type=bool):
            return

        from qgis.PyQt.QtWidgets import QMessageBox
        from .shared.i18n import set_language

        msg = QMessageBox(self.iface.mainWindow())
        msg.setWindowTitle("LVT4U — Language / Ngôn ngữ")
        msg.setText(
            "<h3>Welcome to LVT4U!</h3>"
            "<p>Please select your default language:<br>"
            "Vui lòng chọn ngôn ngữ mặc định:</p>"
        )
        msg.setIcon(QMessageBox.Question)
        msg.addButton("🇬🇧 English", QMessageBox.AcceptRole)
        btn_vn = msg.addButton("🇻🇳 Tiếng Việt", QMessageBox.AcceptRole)
        msg.exec_()

        clicked = msg.clickedButton()
        if clicked == btn_vn:
            set_language("vi")
        else:
            set_language("en")

        QSettings().setValue(key, True)
        if clicked == btn_vn:
            self.iface.messageBar().pushSuccess(
                "LVT4U", "✅ Ngôn ngữ: Tiếng Việt — Chào mừng bạn!"
            )
        else:
            self.iface.messageBar().pushSuccess(
                "LVT4U", "✅ Language: English — Welcome!"
            )

    # ------------------------------------------------------------------
    # Silent satellite engine install
    # ------------------------------------------------------------------

    def _check_satellite_engine(self):
        """Silently install earthengine-api if missing (background)."""
        try:
            import ee  # noqa: F401
            return  # Already installed — nothing to do
        except ImportError:
            pass

        # Deferred start: wait 5s so QGIS UI finishes loading first
        from qgis.PyQt.QtCore import QTimer
        QTimer.singleShot(5000, self._install_satellite_engine)

    def _install_satellite_engine(self):
        """Run pip install in a background thread."""
        from qgis.PyQt.QtCore import QThread, pyqtSignal

        # Find the correct python.exe (NOT sys.executable which is qgis-bin.exe)
        python_exe = self._find_python_exe()
        if not python_exe:
            self.iface.messageBar().pushWarning(
                "LVT4U", "Cannot find Python executable for package install.")
            return

        class _PipWorker(QThread):
            done = pyqtSignal(bool, str)

            def __init__(self, py_exe):
                super().__init__()
                self.py_exe = py_exe

            def run(self):
                import subprocess
                try:
                    result = subprocess.run(
                        [self.py_exe, "-m", "pip", "install",
                         "earthengine-api", "--quiet",
                         "--disable-pip-version-check",
                         "--user"],
                        capture_output=True, text=True, timeout=600,
                        creationflags=0x08000000)  # CREATE_NO_WINDOW
                    if result.returncode == 0:
                        self.done.emit(True, "")
                    else:
                        self.done.emit(False, result.stderr[:300])
                except Exception as e:
                    self.done.emit(False, str(e))

        self.iface.messageBar().pushInfo(
            "LVT4U",
            tr("Installing satellite image engine... (background)"))

        self._pip_worker = _PipWorker(python_exe)
        self._pip_worker.done.connect(self._on_engine_installed)
        self._pip_worker.start()

    @staticmethod
    def _find_python_exe():
        """Find the correct python.exe for QGIS's Python environment."""
        import sys

        # Method 1: Check if sys.executable is already python.exe
        exe = sys.executable
        if os.path.basename(exe).lower().startswith("python"):
            return exe

        # Method 2: Look in QGIS's apps/Python3XX/ directory
        # sys.executable = .../QGIS 3.xx/bin/qgis-bin.exe
        # python.exe    = .../QGIS 3.xx/apps/Python3XX/python.exe
        qgis_root = os.path.dirname(os.path.dirname(exe))
        apps_dir = os.path.join(qgis_root, "apps")
        if os.path.isdir(apps_dir):
            for d in os.listdir(apps_dir):
                if d.lower().startswith("python3"):
                    candidate = os.path.join(apps_dir, d, "python.exe")
                    if os.path.isfile(candidate):
                        return candidate

        # Method 3: Use sys.prefix (Python's install prefix)
        candidate = os.path.join(sys.prefix, "python.exe")
        if os.path.isfile(candidate):
            return candidate

        # Method 4: bin/python.exe next to qgis-bin.exe
        candidate = os.path.join(os.path.dirname(exe), "python.exe")
        if os.path.isfile(candidate):
            return candidate

        return None

    def _on_engine_installed(self, success, error_msg):
        """Handle pip install result."""
        if success:
            self.iface.messageBar().pushSuccess(
                "LVT4U",
                tr("Satellite image engine installed successfully."))
        else:
            self.iface.messageBar().pushWarning(
                "LVT4U",
                f"Satellite engine install failed: {error_msg[:200]}")
        # Clean up worker
        try:
            self._pip_worker.deleteLater()
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Utilities
    # ------------------------------------------------------------------

    def _install_style_library(self):
        """Import symbol libraries into QGIS Style Manager.

        Imports TCVN 11565-2016 library and any bundled XML files from
        layer_editor/styles/. Runs once per user profile.
        """
        from qgis.core import QgsStyle
        style = QgsStyle.defaultStyle()

        # --- 1. TCVN 11565-2016 (existing) ---
        tcvn_key = "LVT4U/style_imported_v1"
        if not QSettings().value(tcvn_key, False, type=bool):
            xml_path = os.path.join(
                self.plugin_dir, "thematic_map", "data",
                "tcvn11565_style.xml"
            )
            if os.path.isfile(xml_path):
                try:
                    count_before = style.symbolCount()
                    ok = style.importXml(xml_path)
                    if ok:
                        added = style.symbolCount() - count_before
                        QSettings().setValue(tcvn_key, True)
                        self.iface.messageBar().pushSuccess(
                            "LVT4U",
                            f"✅ TCVN 11565-2016: {added} symbols installed"
                        )
                except Exception as e:
                    self.iface.messageBar().pushWarning(
                        "LVT4U", f"⚠️ TCVN import error: {e}")

        # --- 2. LVT bundled XML libraries ---
        lvt_key = "LVT4U/style_imported_lvt_v1"
        if not QSettings().value(lvt_key, False, type=bool):
            styles_dir = os.path.join(
                self.plugin_dir, "layer_editor", "styles")
            if os.path.isdir(styles_dir):
                total_added = 0
                for fname in os.listdir(styles_dir):
                    if not fname.lower().endswith('.xml'):
                        continue
                    fpath = os.path.join(styles_dir, fname)
                    try:
                        before = style.symbolCount()
                        style.importXml(fpath)
                        total_added += style.symbolCount() - before
                    except Exception:
                        pass
                if total_added > 0:
                    QSettings().setValue(lvt_key, True)
                    self.iface.messageBar().pushSuccess(
                        "LVT4U",
                        f"✅ LVT Style Library: {total_added} symbols installed"
                    )

    def _show_placeholder(self, module_name):
        """Display a message bar notification for modules not yet implemented.

        Args:
            module_name: Human-readable name of the module.
        """
        self.iface.messageBar().pushInfo(
            "LVT4U",
            tr("{module} — Coming soon!").format(module=module_name),
        )

    def _add_xyz_basemap(self, name, url, zmin=0, zmax=19):
        """Add an XYZ tile layer to the current QGIS project.

        Uses the standard XYZ tile URI format supported by QGIS raster provider.

        Args:
            name: Display name for the layer.
            url: XYZ tile URL template with {x}, {y}, {z} placeholders.
            zmin: Minimum zoom level (default 0).
            zmax: Maximum zoom level (default 19).
        """
        from qgis.core import QgsRasterLayer, QgsProject
        from urllib.parse import quote as _url_quote

        # URL-encode the tile URL so that '&' inside it won't clash
        # with the '&' separating QGIS URI parameters.
        encoded_url = _url_quote(url, safe=':/{}.?%')

        uri = (
            f"type=xyz&url={encoded_url}"
            f"&zmin={zmin}&zmax={zmax}"
        )

        # Avoid duplicates — check if a layer with this name already exists
        existing = QgsProject.instance().mapLayersByName(name)
        if existing:
            self.iface.messageBar().pushInfo(
                "LVT4U",
                tr("{name} is already loaded.").format(name=name),
            )
            return

        layer = QgsRasterLayer(uri, name, "wms")
        if layer.isValid():
            QgsProject.instance().addMapLayer(layer)
            self.iface.messageBar().pushSuccess(
                "LVT4U",
                tr("{name} added successfully.").format(name=name),
            )
        else:
            self.iface.messageBar().pushWarning(
                "LVT4U",
                tr("Failed to load {name}.").format(name=name),
            )
