# Copyright (C) 2026 Wilhelmus Dingenus de Pooter
# SPDX-License-Identifier: GPL-3.0-or-later
"""
enc_explorer.py
Plugin entry point – registers the toolbar action and manages the dock widget.
"""

import os
try:                                    # Qt6 (QGIS 4): QAction lives in QtGui
    from qgis.PyQt.QtGui import QAction
except ImportError:                     # Qt5 (QGIS 3): QAction is in QtWidgets
    from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtGui import QIcon
from qgis.core import QgsApplication
from qgis.core import QgsSettings

from .enc_dock_widget import ENCDockWidget, open_user_manual


class ENCExplorer:
    """Top-level plugin object (one per QGIS session).

    Owns the toolbar action and the dock widget, resolves the bundled SVG and
    S-57 catalogue paths at runtime from the plugin's own location, and
    registers/unregisters everything through the QGIS initGui()/unload()
    lifecycle hooks."""

    def __init__(self, iface):
        self.iface       = iface
        self.dock        = None
        self.action      = None
        self.help_action = None
        # Absolute path to THIS plugin's bundled SVG folder, resolved at runtime
        # from the plugin file's own location — correct no matter where QGIS
        # installed the plugin, on any OS. QMLs reference SVGs by RELATIVE name
        # (e.g. "FLOATING/IALA-A/IALA-A-SB-CON.svg"); registering this folder in
        # QGIS's SVG search paths lets QGIS resolve those names here.
        self.svg_dir = os.path.join(
            os.path.dirname(__file__), "svg", "repository")
        # Bundled Inland-ENC S-57 catalogue (s57objectclasses.csv +
        # s57attributes.csv). Registered via the S57_CSV env var at initGui so
        # GDAL decodes Inland classes (e.g. boylat) during scan AND load.
        self.s57_catalogue_dir = os.path.join(
            os.path.dirname(__file__), "s57catalogue")
        self._s57_csv_set = False
        self._prev_s57_csv = None

    # ── SVG search-path management ──────────────────────────────────────────
    def _register_svg_path(self):
        """Add the plugin's bundled svg/ folder to QGIS's SVG search paths, so
        the AtoN QMLs' relative SVG references resolve offline, wherever the
        plugin is installed. Idempotent — won't add a duplicate entry."""
        if not os.path.isdir(self.svg_dir):
            return
        settings = QgsSettings()
        paths = settings.value("svg/searchPathsForSVG", [])
        # QgsSettings may return a single string, a list, or None.
        if paths is None:
            paths = []
        elif isinstance(paths, str):
            paths = [paths] if paths else []
        else:
            paths = list(paths)
        norm = os.path.normpath(self.svg_dir)
        if norm not in [os.path.normpath(p) for p in paths]:
            paths.append(self.svg_dir)
            settings.setValue("svg/searchPathsForSVG", paths)

    def _unregister_svg_path(self):
        """Remove the plugin's svg/ folder from QGIS's SVG search paths on
        unload, so we don't leave stale entries in the user's settings."""
        settings = QgsSettings()
        paths = settings.value("svg/searchPathsForSVG", [])
        if paths is None:
            return
        if isinstance(paths, str):
            paths = [paths] if paths else []
        else:
            paths = list(paths)
        norm = os.path.normpath(self.svg_dir)
        kept = [p for p in paths if os.path.normpath(p) != norm]
        if len(kept) != len(paths):
            settings.setValue("svg/searchPathsForSVG", kept)

    # ── Inland S-57 catalogue (S57_CSV) management ──────────────────────────
    def _register_s57_catalogue(self):
        """Point GDAL's S-57 reader at the bundled Inland catalogue via the
        S57_CSV environment variable, so Inland classes (e.g. boylat = OBJL
        17029) decode WITH their attributes instead of collapsing into the
        attribute-stripped Generic layer.

        Why the env var and not gdal.SetConfigOption: by the time the plugin
        loads, QGIS's GDAL is already initialised and does NOT pick up S57_CSV
        as a config option at dataset-open time — confirmed by test. The
        ENVIRONMENT VARIABLE *is* read at open time. The config option is set
        too, as a harmless belt-and-braces.

        Fail-safe: only acts when both REQUIRED catalogue files are present, so
        a missing/empty catalogue leaves GDAL's default behaviour untouched and
        never breaks standard S-57. The bundled catalogue is a SUPERSET of the
        standard one, so leaving it set for the session is benign.
        """
        required = ("s57objectclasses.csv", "s57attributes.csv")
        if not all(os.path.isfile(os.path.join(self.s57_catalogue_dir, f))
                   for f in required):
            return
        self._prev_s57_csv = os.environ.get("S57_CSV")
        self._s57_csv_set = True
        os.environ["S57_CSV"] = self.s57_catalogue_dir
        try:
            from osgeo import gdal
            gdal.SetConfigOption("S57_CSV", self.s57_catalogue_dir)
        except Exception:
            pass

    def _unregister_s57_catalogue(self):
        """Restore S57_CSV to its prior value on unload, only if we set it."""
        if not self._s57_csv_set:
            return
        if self._prev_s57_csv is None:
            os.environ.pop("S57_CSV", None)
        else:
            os.environ["S57_CSV"] = self._prev_s57_csv
        try:
            from osgeo import gdal
            gdal.SetConfigOption("S57_CSV", self._prev_s57_csv)
        except Exception:
            pass
        self._s57_csv_set = False

    def initGui(self):
        # Register bundled SVGs first, before any layer/style loads, so AtoN
        # portrayal resolves its relative SVG references immediately.
        self._register_svg_path()
        # Point GDAL at the bundled Inland catalogue BEFORE the first scan, so
        # the scanner enumerates real Inland class names (boylat, bcnlat, …)
        # instead of dumping them into Generic.
        self._register_s57_catalogue()

        icon_path = os.path.join(os.path.dirname(__file__), "icon.svg")
        if not os.path.exists(icon_path):
            icon_path = os.path.join(os.path.dirname(__file__), "icon.png")
        icon = QIcon(icon_path) if os.path.exists(icon_path) else QgsApplication.getThemeIcon("/mActionAddLayer.svg")

        self.action = QAction(icon, "ENCore GIS", self.iface.mainWindow())
        self.action.setCheckable(True)
        self.action.triggered.connect(self._toggle_dock)
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu("ENCore GIS", self.action)

        # User-manual menu item (also reachable from the dock's Help button).
        help_icon = QgsApplication.getThemeIcon("/mActionHelpContents.svg")
        self.help_action = QAction(help_icon, "User Manual",
                                   self.iface.mainWindow())
        self.help_action.triggered.connect(self._open_manual)
        self.iface.addPluginToMenu("ENCore GIS", self.help_action)

    def unload(self):
        # Clean up this session's scratch GeoPackages before tearing down, so
        # temp files don't pile up on the user's disk across QGIS sessions.
        if self.dock:
            # Stop any running background worker FIRST and wait for it, so a
            # mid-load LoadWorker isn't still building QGIS layers while QGIS
            # tears down (the close-mid-process crash).
            try:
                self.dock.stop_worker()
            except Exception:
                pass
            try:
                self.dock.cleanup_tile_cache()   # stop bake + delete ephemeral tiles
            except Exception:
                pass
            try:
                self.dock.cleanup_session_files()
            except Exception:
                pass
            self.iface.removeDockWidget(self.dock)
            self.dock.deleteLater()
            self.dock = None
        if self.help_action:
            self.iface.removePluginMenu("ENCore GIS", self.help_action)
            self.help_action = None
        if self.action:
            self.iface.removeToolBarIcon(self.action)
            self.iface.removePluginMenu("ENCore GIS", self.action)
        # Remove our SVG search path so we leave the user's settings clean.
        self._unregister_svg_path()
        # Restore S57_CSV to whatever it was before we set it.
        self._unregister_s57_catalogue()

    def _open_manual(self):
        """Open the bundled user manual (menu item; mirrors the dock Help button)."""
        open_user_manual(self.iface.mainWindow())

    def _toggle_dock(self, checked):
        if self.dock is None:
            self.dock = ENCDockWidget(self.iface)
            self.dock.visibilityChanged.connect(self.action.setChecked)
            self.iface.addDockWidget(0x2, self.dock)   # Qt.RightDockWidgetArea
        else:
            self.dock.setVisible(checked)
