# -*- coding: utf-8 -*-

# qdrawEVT: plugin qui facilite le dessin
# Author: françois Thévand
#         francois.thevand@gmail.com
#
# Licence GNU GPL v3+
# Ce programme est distribué sans AUCUNE GARANTIE.
# Voir <http://www.gnu.org/licenses/>.

from __future__ import print_function, absolute_import
from builtins import str, object

# --- Initialisation du logger global QdrawEVT ---
from .logger import log, redirect_print_to_log
redirect_print_to_log()
log.info("=== QdrawEVT plugin initialization ===")

# --- Capture des exceptions globales ---

# def handle_uncaught_exception(exctype, value, tb):
#     """Capture toute erreur Python non interceptée et l’envoie au logger."""
#     import traceback
#     err = ''.join(traceback.format_exception(exctype, value, tb))
#     log.critical(f"Exception non interceptée : {err}")

# --- Imports QGIS / PyQt ---
from qgis.PyQt.QtCore import (
    Qt, QEvent, QUrl, QTranslator, QSettings, QCoreApplication,
    QLocale, qVersion, QLocale,
)
from qgis.PyQt.QtWidgets import (
    QAction, QApplication, QMessageBox, QMenu, QInputDialog, QLineEdit,
    QTextEdit, QPlainTextEdit, QDateEdit, QDateTimeEdit, QTimeEdit, QGroupBox,
    QPushButton, QComboBox, QLabel, QCheckBox, QProgressDialog, QStyledItemDelegate,
    QDialog, QVBoxLayout, QDialogButtonBox, QButtonGroup, QRadioButton,
    QTextBrowser, QHBoxLayout,
)
from qgis.PyQt.QtGui import QIcon, QColor, QFont

# --- Gestion des versions PyQt ---
from qgis.PyQt.QtCore import PYQT_VERSION_STR as pyqt_version
if pyqt_version.startswith("5"):
    pyqt_messagebox_question = QMessageBox.Question
    pyqt_messagebox_critical = QMessageBox.Critical
    pyqt_messagebox_warning = QMessageBox.Warning
    pyqt_messagebox_information = QMessageBox.Information
    yes_button = QMessageBox.Yes
    no_button = QMessageBox.No
    pyqt_messagebox_cancel = QMessageBox.Cancel
    qt_windows_modality = Qt.WindowModal
    qt_application_modal = Qt.ApplicationModal
    qt_window_stays_on_top_hint = Qt.WindowStaysOnTopHint
elif pyqt_version.startswith("6"):
    pyqt_messagebox_question = QMessageBox.Icon.Question
    pyqt_messagebox_critical = QMessageBox.Icon.Critical
    pyqt_messagebox_warning = QMessageBox.Icon.Warning
    pyqt_messagebox_information = QMessageBox.Icon.Information
    yes_button = QMessageBox.StandardButton.Yes
    no_button = QMessageBox.StandardButton.No
    pyqt_messagebox_cancel = QMessageBox.StandardButton.Cancel
    qt_windows_modality = Qt.WindowModality.WindowModal
    qt_application_modal = Qt.WindowModality.ApplicationModal
    qt_window_stays_on_top_hint = Qt.WindowType.WindowStaysOnTopHint

from qgis.core import *
from qgis.gui import QgsRubberBand, QgsAttributeEditorContext, QgsLayerTreeMapCanvasBridge

# --- Imports internes du plugin ---
from .AttributesTable import *
from .drawtools import DrawPoint, DrawRect, DrawLine, DrawCircle, DrawPolygon, SelectPoint, XYDialog, DMSDialog
from .qdrawsettings import QdrawSettings
from .qdrawlayerdialogEVT import QDrawLayerDialog, QDrawLayerDialogSelection
from datetime import datetime, date, time
import sys, os, unicodedata, random
from . import resources
from pathlib import Path

# Test existance du groupe Evènements avec suppression des espaces et des accents dans le nom du groupe testé
# evt = False
# groupevt = False
# for group in root.children():
#     test = ''.join(
#         x for x in unicodedata.normalize('NFKD', group.name()) if unicodedata.category(x)[0] == 'L').upper()
#     if test == 'EVENEMENTS':
#         evt = True
#         groupevt = group

# Inscrire ici le nom des couches evenement et les noms de colonnes attributaires utilisées dans ces couches
evtpnt = 'POINT_EVENEMENT'
evtlgn = 'LIGNE_EVENEMENT'
evtpol = 'POLYGONE_EVENEMENT'
lst_form_wgt = ['libelle','date','h_creation','source','h_constat','remarques','surface','longueur','x_gps','y_gps','utilisatr']
form_wgt_libelle = 'libelle'
form_wgt_date = 'date'
form_wgt_h_creation = 'h_creation'
form_wgt_source = 'source'
form_wgt_h_constat = 'h_constat'
form_wgt_remarques = 'remarques'
form_wgt_surface = 'surface'
form_wgt_longueur = 'longueur'
form_wgt_x_gps = 'x_gps'
form_wgt_y_gps = 'y_gps'
form_wgt_utilisatr = 'utilisatr'

# Prise en compte des variables dans la traduction

def normalize(s):
    return ''.join(x for x in unicodedata.normalize('NFKD', s)
                   if unicodedata.category(x)[0] == 'L').upper()

lst_group_no_select = [
    normalize('FONDSDEPLAN'),
    normalize('ADMINISTRATIF')
]
lst_group_evt = [
    normalize('EVENEMENTS')
]
lst_group_drawing = [
    normalize('DESSINS')
]

#Fonction de reconstruction du chemin absolu vers la ressource image ou autre fichier
def resolve(name, basepath=None):
  if not basepath:
    basepath = os.path.dirname(os.path.realpath(__file__))
  return os.path.join(basepath, name)


class UrlDelegate(QStyledItemDelegate):
    def editorEvent(self, event, model, option, index):
        """Ouvre un lien dans le navigateur si la cellule contient une URL."""
        if event.type() == QEvent.MouseButtonRelease:
            url = str(index.data() or "")
            if url.lower().startswith(("http://", "https://")):
                QDesktopServices.openUrl(QUrl(url))
        return super().editorEvent(event, model, option, index)


class QdrawEVT(object):
    def __init__(self, iface):
        self.action_aide = None
        self.groupe_selectionne = None
        self._groupe_msg_nul = None
        self._groupe_msg_affiche = None
        self.iface = iface
        log.info("QdrawEVT plugin: base initialization OK")

        self.table = None
        self.feature = None
        self.features = None
        self.layer = None
        self.index = None
        self.layers = None
        self.featureid = None
        self.groupes_coches = None
        self.request = None
        self.XYcrs = None
        self.layerevtname = None
        self.drawShape = None

        overrideLocale = QSettings().value("locale/overrideFlag", False, type=bool)
        if not overrideLocale: locale = QLocale.system().name()
        else:
            locale = QSettings().value("locale/userLocale", "")
            if locale.__class__.__name__=='QVariant': locale= 'en'
        locale = locale[0:2]
        locale_path = os.path.join(
            os.path.dirname(__file__),
            'i18n',
            'qdrawEVT_{}.qm'.format(locale))

        # *** ATTENTION *** : dans le fichier de traduction la ligne <name>QdrawEVT</name> porte sur la classe principale (ici QdrawEVT dans le fichier qdrawEVT.py)

        self.translator = None
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Obtenir l'instance du projet en cours
        self.project = QgsProject.instance()
        # Root du projet en cours
        self.root = self.project.layerTreeRoot()

        self.evt = None
        self.group_no_select = []
        self.group_evt = []
        self.group_drawing = []

        self.sb = self.iface.statusBarIface()
        self.tool = None
        self.toolname = None

        self.results = []

        self.bGeom = None

        self.actions = []
        self.menu = 'QdrawEVT'
        self.toolbar = self.iface.addToolBar('QdrawEVT')
        self.toolbar.setObjectName('QdrawEVT')

        # Emplacement réservé pour les icônes Gestion_crise (au début)
        self.gc_anchor = QAction("gc_anchor", self.toolbar)
        self.gc_anchor.setVisible(False)  # invisible mais agit comme un repère
        self.toolbar.addAction(self.gc_anchor)

        # Ajout de l’infobulle de la barre d’outils
        self.toolbar.setToolTip("QdrawEVT")

        self.settings = QdrawSettings(form_wgt_libelle, self.iface)
        # self.bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot(), self.iface.mapCanvas())

        # Ajout de la fenêtre de sélection
        self.loadingWindow = QProgressDialog(self.tr('Selecting...'),self.tr('Pass'),0,100)
        self.loadingWindow.setAutoClose(False)
        self.loadingWindow.close()

    def tr(self, message):
        return QCoreApplication.translate('QdrawEVT', message)

    def unload(self):
        log.info("QdrawEVT plugin unloaded cleanly.")
        for action in self.actions:
            self.iface.removePluginMenu("&QdrawEVT", self.action_update_gc)
            # self.iface.removePluginMenu("&QdrawEVT", self.action_install_gc)
            self.iface.removePluginMenu('&QdrawEVT', action)
            self.iface.removeToolBarIcon(action)
        del self.toolbar

    def read_plugin_version(self, plugin_dir: Path) -> str:
        """
        Renvoie la version du plugin à partir de metadata.txt
        Utilise configparser pour une lecture robuste (sections INI).
        """
        import configparser
        metadata_file = plugin_dir / "metadata.txt"
        if not metadata_file.is_file():
            return "0.0.0"
        config = configparser.ConfigParser()
        try:
            config.read(metadata_file, encoding="utf-8")
            # QGIS metadata.txt possède normalement une section [general]
            if "general" in config and "version" in config["general"]:
                return config["general"]["version"].strip()
            # fallback si fichier non standard
            for section in config.sections():
                if "version" in config[section]:
                    return config[section]["version"].strip()
        except Exception:
            pass
        return "0.0.0"

    def version_is_newer(self, v1: str, v2: str) -> bool:
        """Retourne True si v1 > v2."""
        def parse(v):
            return tuple(int(x) for x in v.split(".") if x.isdigit())
        return parse(v1) > parse(v2)

    def unload_plugin_clean(self, plugin_name: str):
        """Décharge proprement un plugin déjà actif, supprime icônes & actions."""
        try:
            pm = getattr(self.iface, "_QGISPluginManager", None)
        except Exception:
            pm = None
        # 1) Appel direct à unload pour le plugin courant
        try:
            if hasattr(self.iface, "removePluginMenu"):
                # Supprime les éléments de menu/menu contextuel potentiellement doublés
                # (QGIS ne nettoie pas toujours correctement)
                pass
        except Exception:
            pass
        # 2) Si instance chargée via notre loader, appelons unloadGui()
        if hasattr(self, "_loaded_plugins") and plugin_name in self._loaded_plugins:
            instance = self._loaded_plugins[plugin_name]
            if hasattr(instance, "unload"):
                try:
                    instance.unload()
                except Exception:
                    pass
            if hasattr(instance, "unloadGui"):
                try:
                    instance.unloadGui()
                except Exception:
                    pass
            # Enlever du registre interne
            del self._loaded_plugins[plugin_name]
        return True

    def update_gestion_crise(self):
        """
        Mise à jour / installation du plugin gestion_crise depuis le ZIP embarqué.
        - Compare versions (ZIP vs installé)
        - Supprime proprement l’ancienne version
        - Installe la nouvelle
        - Active immédiatement le plugin
        - Recharger sans redémarrer QGIS
        - Rafraîchir le gestionnaire d’extensions
        """
        import zipfile
        import tempfile
        import shutil
        from qgis.PyQt.QtCore import QFile, QIODevice
        from pathlib import Path
        import qgis.utils

        plugin_name = "gestion_crise"
        plugins_dir = Path(QgsApplication.qgisSettingsDirPath()) / "python" / "plugins"
        plugin_dir = plugins_dir / plugin_name

        # ------------------------------------------------------------
        # 1) Lire version locale du plugin (si déjà installé)
        # ------------------------------------------------------------
        installed_version = self.read_plugin_version(plugin_dir) if plugin_dir.is_dir() else "0.0.0"
        # ------------------------------------------------------------
        # 2) Extraire le ZIP embarqué dans les ressources
        # ------------------------------------------------------------
        resource_path = ":/plugins/qdrawEVT/resources/gestion_crise.zip"
        qfile = QFile(resource_path)

        if not qfile.exists():
            QMessageBox.critical(self.iface.mainWindow(), "QdrawEVT",
                                 "Ressource gestion_crise.zip introuvable.")
            return

        if not qfile.open(QIODevice.ReadOnly):
            QMessageBox.critical(self.iface.mainWindow(), "QdrawEVT",
                                 "Impossible de lire gestion_crise.zip.")
            return
        # Sauvegarde temporaire du ZIP
        data = bytes(qfile.readAll())
        qfile.close()

        temp_zip = Path(tempfile.gettempdir()) / "gestion_crise_update.zip"
        with open(temp_zip, "wb") as z:
            z.write(data)
        # Extraction temporaire
        tmp_extract = Path(tempfile.gettempdir()) / "gc_update_extract"
        if tmp_extract.exists():
            shutil.rmtree(tmp_extract)
        with zipfile.ZipFile(temp_zip, "r") as z:
            z.extractall(tmp_extract)

        zip_plugin_dir = tmp_extract / plugin_name
        zip_version = self.read_plugin_version(zip_plugin_dir)
        # ------------------------------------------------------------
        # 3) Comparer les versions
        # ------------------------------------------------------------
        if not self.version_is_newer(zip_version, installed_version):
            QMessageBox.information(
                self.iface.mainWindow(),
                "QdrawEVT",
                f"Aucune mise à jour nécessaire.\n"
                f"Version installée : {installed_version}\n"
                f"Version ZIP : {zip_version}"
            )
            return
        # ------------------------------------------------------------
        # 4) Décharger Gestion_crise proprement côté QGIS
        # ------------------------------------------------------------
        # Supprimer les icônes existantes (via notre loader interne)
        try:
            self.unload_plugin_clean(plugin_name)
        except Exception:
            pass
        # ------------------------------------------------------------
        # 5) Installer la nouvelle version du plugin
        # ------------------------------------------------------------
        try:
            if plugin_dir.exists():
                shutil.rmtree(plugin_dir)

            shutil.copytree(zip_plugin_dir, plugin_dir)

        except Exception as e:
            QMessageBox.critical(
                self.iface.mainWindow(),
                "QdrawEVT",
                f"Erreur lors de l’installation de Gestion_crise :\n{e}"
            )
            return
        # ------------------------------------------------------------
        # 6) Activer le plugin dans qgis.ini
        # ------------------------------------------------------------
        settings = QgsSettings()
        settings.beginGroup("PythonPlugins")
        settings.setValue(plugin_name, True)
        settings.endGroup()
        # ------------------------------------------------------------
        # 7) Recharger le plugin immédiatement
        # ------------------------------------------------------------
        ok = False
        try:
            ok = qgis.utils.reloadPlugin(plugin_name)
        except Exception:
            ok = False

        if not ok:
            QMessageBox.warning(
                self.iface.mainWindow(),
                "QdrawEVT",
                "Plugin mis à jour mais n’a pas pu être rechargé automatiquement."
            )
        else:
            QMessageBox.information(
                self.iface.mainWindow(),
                "QdrawEVT",
                f"Installation réussie de Gestion_crise (v{zip_version})."
            )
        # ------------------------------------------------------------
        # 8) Rafraîchir la fenêtre Extensions (QGIS 3.x)
        # ------------------------------------------------------------
        try:
            pm = self.iface.pluginManagerInterface()
            pm.reloadModel()  # Re-scan complet des plugins installés
        except Exception:
            pass
        # ------------------------------------------------------------
        # 9) Ouvrir la fenêtre Extensions (optionnel)
        # ------------------------------------------------------------
        # self.iface.actionManagePlugins().trigger()

    def add_action(
            self,
            icon_path,
            text,
            callback,
            enabled_flag=True,
            checkable=False,
            add_to_menu=True,
            add_to_toolbar=True,
            status_tip=None,
            whats_this=None,
            menu=None,
            parent=None):
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        action.setCheckable(checkable)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if menu is not None:
            action.setMenu(menu)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        # Chargement de l’interface et actions
        log.info("Initialisation de l’interface graphique QdrawEVT")
        redirect_print_to_log()  # ✅ redirection sûre ici
        log.info("Redirection print() → logger QdrawEVT activée")

        pointMenu = QMenu()
        pointMenu.addAction(
            QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY_EVT.png'),
            self.tr('XY Point drawing tool'), self.drawXYPoint)
        pointMenu.addAction(
            QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS_EVT.png'),
            self.tr('DMS Point drawing tool'), self.drawDMSPoint)
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawText_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Text drawing tool'),
            checkable=True,
            callback=self.drawText,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawPt_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Point drawing tool'),
            checkable=True,
            menu=pointMenu,
            callback=self.drawPoint,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawL_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Line drawing tool'),
            checkable=True,
            callback=self.drawLine,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawR_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Rectangle drawing tool'),
            checkable=True,
            callback=self.drawRect,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawC_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Circle drawing tool'),
            checkable=True,
            callback=self.drawCircle,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawP_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Polygon drawing tool'),
            checkable=True,
            callback=self.drawPolygon,
            parent=self.iface.mainWindow()
        )
        bufferMenu = QMenu()
        polygonBufferAction = QAction(
            QIcon(':/plugins/qdrawEVT/resources/icon_DrawTP_EVT.png'),
            self.tr('Buffer drawing with polygonal selection of objects in the selected layer'),
            bufferMenu)
        polygonBufferAction.triggered.connect(self.drawPolygonBuffer)
        bufferMenu.addAction(polygonBufferAction)
        icon_path = ':/plugins/qdrawEVT/resources/icon_DrawT_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Buffer drawing on objects in selected layer'),
            checkable=True,
            menu=bufferMenu,
            callback=self.drawBuffer,
            parent=self.iface.mainWindow()
        )
        icon_path = ':/plugins/qdrawEVT/resources/icon_Settings_EVT.png'
        self.add_action(
            icon_path,
            text=self.tr('Settings'),
            callback=self.showSettingsWindow,
            parent=self.iface.mainWindow()
        )

        # --- Mettre à jour plugin Gestion_Crise ---
        self.action_update_gc = QAction(
            QIcon(":/plugins/qdrawEVT/resources/icon_GC.png"),
            self.tr("Install the Gestion_crise plugin"), self.iface.mainWindow()
        )
        self.action_update_gc.triggered.connect(self.update_gestion_crise)
        self.iface.addPluginToMenu("&QdrawEVT", self.action_update_gc)

        # Détermination de l'icône native QGIS pour l'aide
        icon_help = QgsApplication.getThemeIcon("mActionHelpContents.svg")
        self.action_aide = QAction(
            icon_help,
            self.tr("Help / User manual"),
            self.iface.mainWindow()
        )
        self.action_aide.setObjectName("gc_action_aide")
        self.action_aide.triggered.connect(self.afficher_aide)
        # On ajoute l’action EN BAS du menu
        self.iface.addPluginToMenu(self.menu, self.action_aide)
        # On garde une trace pour unload()
        self.actions.append(self.action_aide)

    # --- Fonction d'installation du plugin gestion_crise ---
    def install_gestion_crise(self):
        """
        Installe le plugin gestion_crise à partir du ZIP embarqué
        dans les ressources de qdrawEVT, l’active et le charge sans
        redémarrer QGIS.
        """
        try:
            from qgis.PyQt.QtCore import QFile, QIODevice
            import zipfile
            import tempfile

            # 1) ZIP embarqué dans les ressources de qdrawEVT
            resource_path = ":/plugins/qdrawEVT/resources/gestion_crise.zip"
            qfile = QFile(resource_path)

            if not qfile.exists():
                QMessageBox.critical(
                    self.iface.mainWindow(),
                    "QdrawEVT",
                    "Le fichier gestion_crise.zip est introuvable dans les ressources "
                    "(ajoute-le dans resources.qrc et recompile resources.py).",
                )
                return

            if not qfile.open(QIODevice.ReadOnly):
                QMessageBox.critical(
                    self.iface.mainWindow(),
                    "QdrawEVT",
                    "Impossible d’ouvrir gestion_crise.zip dans les ressources.",
                )
                return

            data = qfile.readAll()
            qfile.close()

            # 2) Écriture dans un ZIP temporaire
            temp_zip = Path(tempfile.gettempdir()) / "gestion_crise_install.zip"
            with open(temp_zip, "wb") as zf:
                zf.write(bytes(data))

            # 3) Extraction dans le dossier plugins utilisateur
            plugins_dir = Path(QgsApplication.qgisSettingsDirPath()) / "python" / "plugins"
            plugins_dir.mkdir(parents=True, exist_ok=True)

            with zipfile.ZipFile(temp_zip, "r") as z:
                z.extractall(plugins_dir)

            # 4) Nom du dossier du plugin (dans le ZIP, c’est "gestion_crise/")
            plugin_folder = "gestion_crise"
            plugin_path = plugins_dir / plugin_folder
            if not plugin_path.is_dir():
                QMessageBox.critical(
                    self.iface.mainWindow(),
                    "QdrawEVT",
                    f"Le dossier du plugin n’a pas été trouvé : {plugin_path}",
                )
                return

            # 5) Activer dans [PythonPlugins] de qgis.ini
            settings = QgsSettings()
            settings.beginGroup("PythonPlugins")
            settings.setValue(plugin_folder, True)
            settings.endGroup()

            # 6) Chargement immédiat sans redémarrage
            ok = self.force_load_plugin_standard(plugin_folder)

            if ok:
                QMessageBox.information(
                    self.iface.mainWindow(),
                    "QdrawEVT",
                    "Le plugin Gestion_crise a été installé et chargé immédiatement.",
                )
            else:
                QMessageBox.warning(
                    self.iface.mainWindow(),
                    "QdrawEVT",
                    "Le plugin a été installé mais n’a pas pu être chargé automatiquement.\n"
                    "Il restera néanmoins disponible dans le Gestionnaire d’extensions.",
                )

        except Exception as e:
            QMessageBox.critical(
                self.iface.mainWindow(),
                "QdrawEVT",
                f"Erreur lors de l’installation : {e}",
            )

    def force_load_plugin_standard(self, plugin_name: str) -> bool:
        """Charge un plugin standard (avec classFactory) sans redémarrage."""
        try:
            import importlib, sys
            plugins_dir = Path(QgsApplication.qgisSettingsDirPath()) / "python" / "plugins"
            plugin_dir = plugins_dir / plugin_name

            if not plugin_dir.is_dir():
                return False

            if str(plugins_dir) not in sys.path:
                sys.path.insert(0, str(plugins_dir))

            # Import / reload package
            try:
                if plugin_name in sys.modules:
                    pkg = importlib.reload(sys.modules[plugin_name])
                else:
                    pkg = importlib.import_module(plugin_name)
            except Exception as e:
                QMessageBox.critical(self.iface.mainWindow(), "QdrawEVT",
                                     f"Erreur import plugin {plugin_name} : {e}")
                return False

            if not hasattr(pkg, "classFactory"):
                QMessageBox.warning(self.iface.mainWindow(), "QdrawEVT",
                                    "classFactory non trouvé dans le plugin.")
                return False

            plugin_instance = pkg.classFactory(self.iface)

            if hasattr(plugin_instance, "initGui"):
                plugin_instance.initGui()

            # Empêche destruction par Python
            if not hasattr(self, "_loaded_plugins"):
                self._loaded_plugins = {}
            self._loaded_plugins[plugin_name] = plugin_instance

            return True

        except Exception as e:
            QMessageBox.critical(self.iface.mainWindow(), "QdrawEVT",
                                 f"Erreur chargement automatique plugin : {e}")
            return False

    # --- Fonction principale d'affichage de la table d’attributs ---
    def showAttributesTable(self, name):
        """Affiche la table d’attributs pour toutes les couches visibles contenant une sélection."""
        # Création de la fenêtre d’attributs
        tab = AttributesTable(name, self.iface)
        tab.setWindowFlags(qt_window_stays_on_top_hint)

        layers = self.project.mapLayers().values()
        selected_found = False  # Pour vérifier s'il y a au moins une sélection

        for layer in layers:
            try:
                # Ne traiter que les couches vectorielles visibles
                if (
                        layer.type() == QgsMapLayer.VectorLayer
                        and QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible()
                ):
                    cells = layer.selectedFeatures()
                    if not cells:
                        continue

                    selected_found = True

                    # Récupération des colonnes non masquées et de leurs alias
                    columns = layer.attributeTableConfig().columns()
                    fields_name = []
                    fields_type = []
                    idx_visible = []

                    for col in columns:
                        if col.hidden:
                            continue
                        idx = layer.fields().indexOf(col.name)
                        alias = layer.attributeAlias(idx)
                        fields_name.append(alias if alias else col.name)
                        fields_type.append(col.type)
                        idx_visible.append(idx)

                    # Ajout de la couche dans la table
                    tab.addLayer(layer, fields_name, fields_type, cells, idx_visible)

            except Exception as e:
                QgsMessageLog.logMessage(f"Erreur dans showAttributesTable : {e}", "QdrawEVT", Qgis.Warning)

        # Si aucune sélection trouvée
        if not selected_found:
            QMessageBox.information(
                self.iface.mainWindow(),
                self.tr("No selection"),
                self.tr("No selected items found in visible vector layers."),
            )
            return

        # Fonction utilitaire : détection des colonnes d’URL
        def is_url_item(item):
            if not item:
                return False
            txt = item.text().strip().lower()
            return txt.startswith("http://") or txt.startswith("https://")

        # Application du UrlDelegate sur les colonnes d’URL
        for idx in range(tab.tabWidget.count()):
            widget = tab.tabWidget.widget(idx)
            table_widget = next(
                (child for child in widget.children() if isinstance(child, QTableWidget)), None
            )
            if not table_widget:
                continue

            for col in range(table_widget.columnCount()):
                if all(
                        is_url_item(table_widget.item(row, col))
                        for row in range(table_widget.rowCount())
                        if table_widget.item(row, col)
                ):
                    table_widget.setItemDelegateForColumn(col, UrlDelegate(table_widget))

        # Affichage de la fenêtre d’attributs
        tab.loadingWindow.close()
        tab.show()
        tab.activateWindow()
        tab.showNormal()

        # Connexion du signal destroyed pour nettoyage
        tab.destroyed.connect(lambda: self.closeAttributesTable(tab))

        # Sauvegarde dans la liste des résultats et nettoyage de la sélection
        self.results.append(tab)
        self.unselectAll()

    def unselectAll(self):
        # Récupérer toutes les couches du projet
        layers = QgsProject.instance().mapLayers().values()
        # Parcourir chaque couche
        for layer in layers:
            if layer.type() == QgsMapLayer.VectorLayer:  # Vérifier si c'est une couche vectorielle
                layer.removeSelection()  # Désélectionner tous les éléments
        # Mettre à jour l'affichage de la carte
        self.iface.mapCanvas().refresh()

    def closeAttributesTable(self, tab):
        """
        Supprime la fenêtre d’attributs fermée de la liste des résultats actifs.
        Évite les références fantômes et nettoie proprement la mémoire.
        """
        try:
            if tab in self.results:
                self.results.remove(tab)
                QgsMessageLog.logMessage(
                    f"AttributesTable fermée et retirée de self.results ({len(self.results)} restantes).",
                    "QdrawEVT",
                    Qgis.Info,
                )
        except Exception as e:
            QgsMessageLog.logMessage(
                f"Erreur dans closeAttributesTable : {e}", "QdrawEVT", Qgis.Warning
            )

    def testGroups(self):
        # mise en variable des groupes a gérer pour la sélection
        self.group_no_select = []
        for group in self.root.children():
            test = ''.join(x for x in unicodedata.normalize('NFKD', group.name()) if unicodedata.category(x)[0] == 'L').upper()
            if test in lst_group_no_select:
                self.group_no_select.append(group)
        self.group_evt = []
        for group in self.root.children():
            test = ''.join(x for x in unicodedata.normalize('NFKD', group.name()) if unicodedata.category(x)[0] == 'L').upper()
            if test in lst_group_evt:
                self.group_evt.append(group)
                self.group_no_select.append(group)
        self.group_drawing = []
        for group in self.root.children():
            test = ''.join(x for x in unicodedata.normalize('NFKD', group.name()) if unicodedata.category(x)[0] == 'L').upper()
            if test in lst_group_drawing:
                self.group_drawing.append(group)
                self.group_no_select.append(group)
        if self.group_evt:
            self.evt = True
        else:
            self.evt = False

    def isEVT(self):
        layers_evt = []
        layers_group_drawing = []
        sortieEVT = False
        for group in self.root.children():
            test = ''.join(x for x in unicodedata.normalize('NFKD', group.name()) if unicodedata.category(x)[0] == 'L').upper()
            if test in lst_group_evt:
                # Réinitialisation de l'analyse thématique des couches évènement
                if group:
                    for lyr in group.children():
                        layers_evt.append(self.project.mapLayersByName(lyr.name())[0])
                        # self.QgisV3LayerRandomColor(layer, form_wgt_libelle)
                        # self.iface.layerTreeView().refreshLayerSymbology(layer.name())
                # return True, layers_evt
                sortieEVT = True
                break

            # Code en prévision utilisation couche dessin
            # elif test in lst_group_drawing:
            #     if group:
            #         for lyr in group.children():
            #             layers_group_drawing.append(self.project.mapLayersByName(lyr.name())[0])
            #             # self.QgisV3LayerRandomColor(layer, form_wgt_libelle)
            #             # self.iface.layerTreeView().refreshLayerSymbology(layer.name())
            #     sortie_group_drawing = True

        if sortieEVT:
            return True, layers_evt
        else:
            return False, layers_evt

    def drawPoint(self):
        # Modification qdrawEVT
        # print('self.isEVT() : ' + str(self.isEVT()))
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[1].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt_EVT'))
            self.actions[1].menu().actions()[0].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY_EVT'))
            self.actions[1].menu().actions()[1].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS_EVT'))
        else:
            self.actions[1].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt'))
            self.actions[1].menu().actions()[0].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY'))
            self.actions[1].menu().actions()[1].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS'))
        self.layerevtname = evtpnt
        self.actions[1].triggered.disconnect()
        self.actions[1].triggered.connect(self.drawPoint)
        self.actions[1].menu().actions()[0].triggered.disconnect()
        self.actions[1].menu().actions()[0].triggered.connect(self.drawXYPoint)
        self.actions[1].menu().actions()[1].triggered.disconnect()
        self.actions[1].menu().actions()[1].triggered.connect(self.drawDMSPoint)
        if self.tool:
            self.tool.reset()
        self.tool = DrawPoint(self.iface, self.settings.getColor())
        self.tool.setAction(self.actions[1])
        self.tool.selectionDone.connect(self.draw)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'point'
        self.toolname = 'drawPoint'
        self.resetSB()

    def drawText(self):
        if self.tool:
            self.tool.reset()
        self.tool = DrawPoint(self.iface, self.settings.getColor())
        self.tool.setAction(self.actions[0])
        self.tool.selectionDone.connect(self.draw)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'text'
        self.toolname = 'drawText'
        # Modification qdrawEVT
        self.layerevtname = None
        self.resetSB()

    def drawXYPoint(self):
        # Modification qdrawEVT
        self.layerevtname = evtpnt
        mc = self.iface.mapCanvas()
        ms = mc.mapSettings()
        # default for drawXYPoint data : WGS84 EPSG:4326
        crs = QgsCoordinateReferenceSystem(4326)
        ms.setDestinationCrs(crs)
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt_EVT.png'))
            self.actions[1].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY_EVT.png'))
            self.actions[1].menu().actions()[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS_EVT.png'))
        elif not self.isEVT()[0]:
            self.actions[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt.png'))
            self.actions[1].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY.png'))
            self.actions[1].menu().actions()[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS.png'))
        self.actions[1].menu().actions()[0].setText(
            self.tr('XY point drawing tool'))
        self.actions[1].menu().actions()[1].setText(
            self.tr('DMS point drawing tool'))
        self.actions[1].menu().actions()[0].triggered.disconnect()
        self.actions[1].menu().actions()[0].triggered.connect(self.drawXYPoint)
        self.actions[1].menu().actions()[1].triggered.disconnect()
        self.actions[1].menu().actions()[1].triggered.connect(self.drawDMSPoint)
        try:
            xytuple, ok = XYDialog().getPoint(ms.destinationCrs())
        except:
            pyqt_messagebox_critical(
                self.iface.mainWindow(),
                self.tr('Error'), self.tr('Saisie incorrecte ou clic sur Annuler'))
            return
        # print('xytuple : ' + str(xytuple))
        # print('crs : ' + str(crs))
        point = xytuple[0]
        ptOrigin = point
        if xytuple[1] != crs:
            crsSrc = xytuple[1] # projection choisie par l'utilisateur
        else:
            crsSrc = crs # projection définie par défaut dans drawXYPoint() qdrawEVT.py (4326)
        crsDest = QgsProject.instance().crs()
        crsTransform = QgsCoordinateTransform(crsSrc,crsDest, QgsProject.instance())
        # Transformation crsSrc -> crsDest
        point = crsTransform.transform(ptOrigin)
        # Remplacé pour utilisation de la projection du projet en cours
        # crs = QgsCoordinateReferenceSystem(2154)
        # Ligne de remplacement
        crs = QgsProject.instance().crs()
        ms.setDestinationCrs(crs)
        self.XYcrs = crs
        if ok:
            if point.x() == 0 and point.y() == 0:
                pyqt_messagebox_critical(
                    self.iface.mainWindow(),
                    self.tr('Error'), self.tr('Invalid input !'))
                if self.tool:
                    self.tool.reset()
                self.tool = DrawPoint(self.iface, self.settings.getColor())
                self.tool.setAction(self.actions[1])
                self.tool.selectionDone.connect(self.drawPoint)
                self.iface.mapCanvas().setMapTool(self.tool)
                self.drawShape = 'point'
                self.toolname = 'drawPoint'
                self.resetSB()
                return
            else:
                self.drawPoint()
                self.tool.rb = QgsRubberBand(
                    self.iface.mapCanvas(), QgsWkbTypes.PointGeometry)
                self.tool.rb.addPoint(point)
                self.tool.rb.setColor(self.settings.getColor())
                self.tool.rb.setWidth(4)
                self.drawShape = 'XYpoint'
                # print('self.tool.rb : ' + str(self.tool.rb))
                self.draw()
                mc.setCenter(point)
                mc.refresh()
#                # wait .5 seconds to simulate a flashing effect
#                QTimer.singleShot(500,self.resetSB)

    def drawDMSPoint(self):
        # Modification qdrawEVT
        mc = self.iface.mapCanvas()
        ms = mc.mapSettings()
        # Gestion du changement d'aspact de l'icone d'outil
        self.layerevtname = evtpnt
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt_EVT.png'))
            self.actions[1].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY_EVT.png'))
            self.actions[1].menu().actions()[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS_EVT.png'))
        elif not self.isEVT()[0]:
            self.actions[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPt.png'))
            self.actions[1].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtXY.png'))
            self.actions[1].menu().actions()[1].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawPtDMS.png'))
        self.actions[1].menu().actions()[0].setText(
            self.tr('XY point drawing tool'))
        self.actions[1].menu().actions()[1].setText(
            self.tr('DMS point drawing tool'))
        self.actions[1].menu().actions()[0].triggered.disconnect()
        self.actions[1].menu().actions()[0].triggered.connect(self.drawXYPoint)
        self.actions[1].menu().actions()[1].triggered.disconnect()
        self.actions[1].menu().actions()[1].triggered.connect(self.drawDMSPoint)

        point, ok = DMSDialog().getPoint()
        # default for drawDMSPoint data : WGS84 EPSG:4326
        crsSrc = QgsCoordinateReferenceSystem(4326)
        crsDest = QgsProject.instance().crs()
        ptOrigin = point
        crsTransform = QgsCoordinateTransform(crsSrc,crsDest, QgsProject.instance())
        # Transformation crsSrc -> crsDest
        point = crsTransform.transform(ptOrigin)
        crs = QgsProject.instance().crs()
        ms.setDestinationCrs(crs)
        self.XYcrs = crs

        if ok:
            if point.x() == 0 and point.y() == 0:
                pyqt_messagebox_critical(
                    self.iface.mainWindow(),
                    self.tr('Error'), self.tr('Invalid input !'))
            else:
                self.drawPoint()
                self.tool.rb = QgsRubberBand(
                    self.iface.mapCanvas(), QgsWkbTypes.PointGeometry)
                self.tool.rb.setColor(self.settings.getColor())
                self.tool.rb.setWidth(3)
                self.tool.rb.addPoint(point)
                self.drawShape = 'XYpoint'
                self.draw()
                # centrer la carte sur le point
                mc.setCenter(point)
                mc.refresh()

    def drawLine(self):
        # Modification qdrawEVT
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[2].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawL_EVT'))
        else:
            self.actions[2].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawL'))
        self.layerevtname = evtlgn
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = DrawLine(self.iface, self.settings.getColor())
        self.tool.setAction(self.actions[2])
        self.tool.selectionDone.connect(self.draw)
        self.tool.move.connect(self.updateSB)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'line'
        self.toolname = 'drawLine'
        self.resetSB()

    def drawRect(self):
        # Modification qdrawEVT
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[3].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawR_EVT'))
        else:
            self.actions[3].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawR'))
        self.layerevtname = evtpol
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = DrawRect(self.iface, self.settings.getColor())
        self.tool.setAction(self.actions[3])
        self.tool.selectionDone.connect(self.draw)
        self.tool.move.connect(self.updateSB)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'polygon'
        self.toolname = 'drawRect'
        self.resetSB()

    def drawCircle(self):
        # Modification qdrawEVT
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[4].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawC_EVT'))
        else:
            self.actions[4].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawC'))
        self.layerevtname = evtpol

        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = DrawCircle(self.iface, self.settings.getColor(), 40)
        self.tool.setAction(self.actions[4])
        self.tool.selectionDone.connect(self.draw)
        self.tool.move.connect(self.updateSB)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'polygon'
        self.toolname = 'drawCircle'
        self.resetSB()

    def drawPolygon(self):
        # Modification qdrawEVT
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[5].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawP_EVT'))
        else:
            self.actions[5].setIcon(QIcon(':/plugins/qdrawEVT/resources/icon_DrawP'))
        self.layerevtname = evtpol
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = DrawPolygon(self.iface, self.settings.getColor())
        self.tool.setAction(self.actions[5])
        self.tool.selectionDone.connect(self.draw)
        self.tool.move.connect(self.updateSB)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'polygon'
        self.toolname = 'drawPolygon'
        self.resetSB()

    def drawBuffer(self):
        self.layerevtname = evtpol
        self.bGeom = None
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = SelectPoint(self.iface, self.settings.getColor())
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[6].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawT_EVT.png'))
            self.actions[6].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawTP_EVT.png'))
            self.actions[6].setText(
                self.tr('Buffer drawing tool on the selected layer'))
            self.actions[6].menu().actions()[0].setText(
                self.tr('Polygon buffer drawing tool on the selected layer'))
        elif not self.isEVT()[0]:
            self.actions[6].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawT.png'))
            self.actions[6].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawTP.png'))
            self.actions[6].setText(
                self.tr('Buffer drawing tool on the selected layer'))
            self.actions[6].menu().actions()[0].setText(
                self.tr('Polygon buffer drawing tool on the selected layer'))
        self.actions[6].triggered.disconnect()
        self.actions[6].triggered.connect(self.drawBuffer)
        self.actions[6].menu().actions()[0].triggered.disconnect()
        self.actions[6].menu().actions()[0].triggered.connect(self.drawPolygonBuffer)
        self.tool.setAction(self.actions[6])
        self.tool.select.connect(self.selectBuffer)
        self.tool.selectionDone.connect(self.draw)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'polygon'
        self.toolname = 'drawBuffer'
        self.resetSB()

    def drawPolygonBuffer(self):
        self.layerevtname = evtpol
        self.bGeom = None
        if self.tool:
            self.tool.reset()
        self.request = 'intersects'
        self.tool = DrawPolygon(self.iface, self.settings.getColor())
        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0]:
            self.actions[6].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawTP_EVT.png'))
            self.actions[6].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawT_EVT.png'))
            self.actions[6].setText(
                self.tr('Polygon buffer drawing tool on the selected layer'))
            self.actions[6].menu().actions()[0].setText(
                self.tr('Buffer drawing tool on the selected layer'))
        elif not self.isEVT()[0]:
            self.actions[6].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawTP.png'))
            self.actions[6].menu().actions()[0].setIcon(
                QIcon(':/plugins/qdrawEVT/resources/icon_DrawT.png'))
            self.actions[6].setText(
                self.tr('Polygon buffer drawing tool on the selected layer'))
            self.actions[6].menu().actions()[0].setText(
                self.tr('Buffer drawing tool on the selected layer'))
        self.actions[6].triggered.disconnect()
        self.actions[6].triggered.connect(self.drawPolygonBuffer)
        self.actions[6].menu().actions()[0].triggered.disconnect()
        self.actions[6].menu().actions()[0].triggered.connect(self.drawBuffer)
        self.tool.setAction(self.actions[6])
        self.tool.selectionDone.connect(self.selectBuffer)
        self.iface.mapCanvas().setMapTool(self.tool)
        self.drawShape = 'polygon'
        self.toolname = 'drawBuffer'
        self.resetSB()

    def showSettingsWindow(self):
        self.settings.settingsChanged.connect(self.settingsChangedSlot)
        self.settings.show()

    # triggered when a setting is changed
    def settingsChangedSlot(self):
        if self.tool:
            self.tool.rb.setColor(self.settings.getColor())

    def tr_messages(self):
        return {
            'drawText': self.tr('Left click to place a text.'),
            'drawPoint': self.tr('Left click to place a point.'),
            'drawLine': self.tr('Left click to place points. Right click to confirm.'),
            'drawRect': self.tr('Maintain the left click to draw a rectangle.'),
            'drawCircle': self.tr(
                'Maintain the left click to draw a circle. '
                'Simple left click to give a perimeter.'
            ),
            'drawPolygon': self.tr(
                'Left click to place points. Right click to confirm.'
            ),
            'drawBuffer': self.tr(
                'Select a vector layer in the Layer Tree, then select an entity on the map.'
            ),
        }

    def resetSB(self):
        self.sb.showMessage(self.tr_messages().get(self.toolname, ''))

    def updateSB(self):
        g = self.geomTransform(
            self.tool.rb.asGeometry(),
            self.iface.mapCanvas().mapSettings().destinationCrs(),
            QgsProject.instance().crs()
        )

        if self.toolname == "drawLine":
            length = max(0.0, float(g.length()))
            self.sb.showMessage(
                self.tr("Length: {value} m").format(value=f"{length:.2f}")
            )
        else:
            area = max(0.0, float(g.area()))
            self.sb.showMessage(
                self.tr("Area: {value} m²").format(value=f"{area:.2f}")
            )

        # (cette ligne ne fait rien, tu peux la supprimer)
        # self.iface.mapCanvas().mapSettings().destinationCrs().authid()

    def geomTransform(self, geom, crs_orig, crs_dest):
        g = QgsGeometry(geom)
        crsTransform = QgsCoordinateTransform(
            crs_orig, crs_dest, QgsCoordinateTransformContext())  # which context ?
        g.transform(crsTransform)
        return g

    def selectBuffer(self):
        rb = self.tool.rb
        if isinstance(self.tool, DrawPolygon):
            rbSelect = self.tool.rb
        else:
            rbSelect = self.tool.rbSelect
        layer = self.iface.layerTreeView().currentLayer()
        if layer is not None and layer.type() == QgsMapLayer.VectorLayer \
                and self.iface.layerTreeView().currentNode().isVisible():
            # rubberband reprojection
            g = self.geomTransform(
                rbSelect.asGeometry(),
                self.iface.mapCanvas().mapSettings().destinationCrs(),
                layer.crs())
            features = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
            rbGeom = []
            for feature in features:
                geom = feature.geometry()
                try:
                    if g.intersects(geom):
                        rbGeom.append(feature.geometry())
                except:
                    # there's an error but it intersects
                    # fix_print_with_import
                    # print('error with '+layer.name()+' on '+str(feature.id()))
                    rbGeom.append(feature.geometry())
            if len(rbGeom) > 0:
                for geometry in rbGeom:
                    if rbGeom[0].combine(geometry) is not None:
                        if self.bGeom is None:
                            self.bGeom = geometry
                        else:
                            self.bGeom = self.bGeom.combine(geometry)
                rb.setToGeometry(self.bGeom, layer)
        if isinstance(self.tool, DrawPolygon):
            self.draw()

    from qgis.core import (
        Qgis,
        QgsPalLayerSettings,
        QgsVectorLayerSimpleLabeling,
        QgsTextFormat,
    )

    def _apply_text_labeling(self, layer, field_name: str):
        """
        Labeling texte robuste multi-QGIS (3.22 → 3.42)
        Spécialement corrigé pour QGIS 3.40.x (Bratislava).
        """

        layer_settings = QgsPalLayerSettings()

        # --------------------------------------------------
        # Format texte
        # --------------------------------------------------
        text_format = QgsTextFormat()
        text_format.setFont(self.settings.getFont())
        text_format.setSize(self.settings.getFont().pointSize())
        text_format.setColor(self.settings.getColorFont())

        layer_settings.setFormat(text_format)
        layer_settings.fieldName = field_name

        v = Qgis.QGIS_VERSION_INT

        # --------------------------------------------------
        # Placement (ENUM CORRECT SELON VERSION)
        # --------------------------------------------------
        if v >= 34200:
            # QGIS 3.42+
            layer_settings.placement = QgsPalLayerSettings.PointPlacement

        elif v >= 34000:
            # QGIS 3.40.x (Bratislava) → ENUM DANS Qgis
            layer_settings.placement = Qgis.LabelPlacement.OverPoint

        elif v >= 33000:
            # QGIS 3.30 → 3.38
            layer_settings.placement = QgsPalLayerSettings.LabelPlacement.OverPoint

        else:
            # QGIS ≤ 3.28
            layer_settings.placement = QgsPalLayerSettings.OverPoint

        # --------------------------------------------------
        # NE PAS toucher predefinedPosition en 3.40.x
        # --------------------------------------------------

        labeling = QgsVectorLayerSimpleLabeling(layer_settings)
        layer.setLabeling(labeling)
        layer.setLabelsEnabled(True)

    def draw(self):
        test_draw = None
        rb = self.tool.rb
        g = rb.asGeometry()
        # Modification qdrawEVT
        mc = self.iface.mapCanvas()
        mc.setCenter(g.centroid().asPoint())
        if self.drawShape in ['point', 'XYpoint', 'text']:
            mc.zoomScale(5000)
        else:
            mc.zoomToFeatureExtent(g.boundingBox())

        ok = True
        warning = False
        errBuffer_noAtt = False
        errBuffer_Vertices = False

        self.layer = self.iface.layerTreeView().currentLayer()
        if self.toolname == 'drawBuffer':
            if self.bGeom is None:
                warning = True
                errBuffer_noAtt = True
            else:
                perim, ok = QInputDialog.getDouble(
                    self.iface.mainWindow(), self.tr('Perimeter'),
                    self.tr('Enter the buffer size in meters:\n(works only with a metric coordinate reference system)'),
                    min=0)
                g = self.bGeom.buffer(perim, 40)
                rb.setToGeometry(g, QgsVectorLayer(
                    "Polygon?crs=" + self.layer.crs().authid(), "", "memory"))
                if g.length() == 0 and ok:
                    warning = True
                    errBuffer_Vertices = True

        if self.toolname == 'drawCopies':
            errBuffer_noAtt = True

        name_draw = ''
        add = False
        self.testGroups()
        if ok and not warning:
            ok = True
            self.index = 0
            self.layers = []
            # print('self.toolname : ' + str(self.toolname))
            while not name_draw.strip() and not add and ok:
                # Lancement de la boite de dialogue avec passage de la définition de self.drawShape (gtype en sortie)
                dlg = QDrawLayerDialog(self.iface, self.drawShape, self.layerevtname, self.evt)
                name_draw, add, self.index, self.layers, ok = dlg.getName(self.iface, self.drawShape, self.layerevtname, self.evt)

        if ok and not warning and self.evt:
            if self.drawShape == 'text':

                GROUP_KEY = "DESSINS"

                group = self.root.findGroup(GROUP_KEY)
                if group is None:
                    group = QgsLayerTreeGroup(GROUP_KEY)
                    self.root.insertChildNode(0, group)

                group.setName(self.tr("Dessins"))

                test_draw = 1
                self.layer = QgsVectorLayer(
                    "Point?crs=" + self.iface.mapCanvas().mapSettings().destinationCrs().authid() + "&field=" + self.tr(
                        'Drawings') + ":string(255)", name_draw, "memory")
                g = g.centroid()  # force geometry as point
                # self.layer.beginEditCommand("Memory layer")
                self.layer.startEditing()
                symbols = self.layer.renderer().symbols(QgsRenderContext())  # todo which context ?
                symbols[0].setColor(self.settings.getColor())
                self.feature = QgsFeature(self.layer.fields())
                self.feature.setGeometry(g)
                self.feature.setAttributes([name_draw])
                self.layer.dataProvider().addFeatures([self.feature])
                self.featureid = self.feature.id()

                # ajout dessin de texte
                symbols[0].setSize(0)

                self._apply_text_labeling(
                    layer=self.layer,
                    field_name="Drawings"
                )

                if not add:
                    self.project.addMapLayer(self.layer, False)
                    group.insertLayer(0, self.layer)
                self.iface.layerTreeView().refreshLayerSymbology(self.layer.id())
                self.iface.mapCanvas().refresh()
            else:
                # ajout point, ligne ou polygone
                self.layer = self.project.mapLayersByName(self.layerevtname)[0]
                self.iface.setActiveLayer(self.layer)
                self.layer.setReadOnly(False)
                self.layer.startEditing()
                self.features = QgsFeature()
                # Ajout de la géométrie dans la couche
                self.features.setGeometry(g)
                # Récupération de la structure de la couche
                self.features.setFields(self.layer.fields())  # retrieve the fields from layer and set them to the feature
                # Récupération du nom de l'utilisateur
                self.features.setAttribute(form_wgt_utilisatr, os.environ.get("USERNAME"))
                # Test du type pour inscription des données dimentionnelles adaptées
                if self.drawShape in ['point', 'XYpoint']:
                    # Transformation de la coordonnée en EPSG 4326
                    crsSrc = QgsProject.instance().crs()  # L93
                    crsDest = QgsCoordinateReferenceSystem(4326)  # WGS 84
                    xform1 = QgsCoordinateTransform(crsSrc, crsDest, self.project)
                    geom = g.centroid().asPoint()
                    # Transformation crsSrc -> crsDest
                    pt1 = xform1.transform(geom)
                    x1, y1 = pt1.x(), pt1.y()
                    # Inscription en donnée attributaire
                    self.features.setAttribute(form_wgt_x_gps, round(x1, 6))
                    self.features.setAttribute(form_wgt_y_gps, round(y1, 6))
                elif self.drawShape == 'line':
                    self.features.setAttribute(form_wgt_longueur, int(round(g.length(), 0)))
                elif self.drawShape == 'polygon':
                    self.features.setAttribute(form_wgt_surface, int(round(g.area(), -2)))
                # Ouverture du formulaire (enregistrement de l'objet si click sur ok)
                form = self.iface.getFeatureForm(self.layer, self.features)
                # Mise du formulaire en mode création
                form.setMode(QgsAttributeEditorContext.AddFeatureMode)
                # Inscription du nom de l'évènement dans le formulaire
                if form_wgt_libelle in lst_form_wgt:
                    libelle = form.findChild(QLineEdit, form_wgt_libelle)
                    libelle.setText(name_draw)
                # Attribution du focus à l'item "Source"
                if form_wgt_source in lst_form_wgt:
                    source = form.findChild(QLineEdit, form_wgt_source)
                    source.setFocus()
                test_draw = form.exec()
                if test_draw == 1:
                    self.QgisV3LayerRandomColor(self.layer, form_wgt_libelle)
                    self.layer.commitChanges()
                    self.layer.setReadOnly(True)
                else:
                    self.layer.rollBack()
                    self.layer.setReadOnly(True)

        if ok and not warning and not self.evt:
            # Pas de groupe évènement
            # --------------------------------------------------
            # Groupe Drawings (clé interne fixe + nom traduit)
            # --------------------------------------------------
            GROUP_KEY = "DRAWINGS"

            group = self.root.findGroup(GROUP_KEY)
            if group is None:
                group = QgsLayerTreeGroup(GROUP_KEY)
                self.root.insertChildNode(0, group)
            # Nom visible (UI) traduit
            group.setName(self.tr("Drawings"))

            test_draw = 1
            if add:
                self.layer = self.layers[self.index]
                if self.drawShape in ['point', 'XYpoint', 'text']:
                    g = g.centroid()
            else:
                if self.drawShape in ['point', 'text']:
                    self.layer = QgsVectorLayer(
                        "Point?crs=" + self.iface.mapCanvas().mapSettings().destinationCrs().authid() +
                        "&field=" + 'Drawings' + ":string(255)", name_draw, "memory")
                    g = g.centroid()  # force geometry as point
                elif self.drawShape == 'XYpoint':
                    self.layer = QgsVectorLayer(
                        "Point?crs=" + self.XYcrs.authid() +
                        "&field=" + 'Drawings' + ":string(255)", name_draw, "memory")
                    g = g.centroid()
                elif self.drawShape == 'line':
                    self.layer = QgsVectorLayer(
                        "LineString?crs=" + self.iface.mapCanvas().mapSettings().destinationCrs().authid() +
                        "&field=" + 'Drawings' + ":string(255)", name_draw, "memory")
                else:
                    self.layer = QgsVectorLayer(
                        "Polygon?crs=" + self.iface.mapCanvas().mapSettings().destinationCrs().authid() +
                        "&field=" + 'Drawings' + ":string(255)", name_draw, "memory")
            self.layer.startEditing()
            symbols = self.layer.renderer().symbols(QgsRenderContext())  # todo which context ?
            symbols[0].setColor(self.settings.getColor())
            self.feature = QgsFeature(self.layer.fields())
            self.feature.setGeometry(g)
            self.feature.setAttributes([name_draw])
            self.layer.dataProvider().addFeatures([self.feature])
            self.featureid = self.feature.id()

            # ajout dessin de texte
            if self.drawShape == 'text':
                symbols[0].setSize(0)

                self._apply_text_labeling(
                    layer=self.layer,
                    field_name="Drawings"
                )

                self.layer.renderer().symbol().symbolLayer(0).setShape(QgsSimpleMarkerSymbolLayerBase.Star)
                self.layer.triggerRepaint()
                self.iface.layerTreeView().refreshLayerSymbology(self.layer.id())
                self.iface.mapCanvas().refresh()

            if not add:
                self.project.addMapLayer(self.layer, False)
                group.insertLayer(0, self.layer)
            self.iface.layerTreeView().refreshLayerSymbology(self.layer.id())
            self.iface.mapCanvas().refresh()

        # Modification pour ajout de la sélection des couches intersectant
        # une ligne ou un polygone, avec cochage automatique puis décochage du groupe sélectionné
        if self.drawShape not in ['point', 'XYpoint', 'text'] and test_draw == 1:
            # Définition du choix d'une sélection en intersection
            box = QMessageBox()
            box.setWindowTitle(self.tr('Choosing a selection'))
            box.setIcon(pyqt_messagebox_information)
            box.setText(self.tr("{name} drawn. Make a selection within its extent?").format(name=name_draw))
            box.setStandardButtons(yes_button | no_button)
            box.setDefaultButton(yes_button)
            buttonYes = box.button(yes_button)
            buttonNo = box.button(no_button)
            box.setWindowModality(qt_windows_modality)
            box.setWindowFlags(qt_window_stays_on_top_hint)
            box.exec()

            self.groupes_coches = []
            test_coche = self.verifCocheGroupes()

            if box.clickedButton() == buttonYes:

                # ------------------------------------------------------------------
                # PATCH : Aucun groupe existant → création d'un groupe + choix des couches
                # ------------------------------------------------------------------

                root = QgsProject.instance().layerTreeRoot()

                # Construire la liste des groupes exclus
                excluded_groups = self.group_no_select + self.group_evt + self.group_drawing

                # Construire la liste des couches exclues (pour ne pas les proposer dans la boîte)
                excluded_layer_ids = set()
                for grp in excluded_groups:
                    for child in grp.children():
                        if isinstance(child, QgsLayerTreeLayer):
                            excluded_layer_ids.add(child.layerId())

                # Groupes existants (hors groupes exclus)
                groupes_existants = [
                    g for g in root.children()
                    if isinstance(g, QgsLayerTreeGroup) and g not in excluded_groups
                ]

                if len(groupes_existants) == 0:
                    # Aucun groupe exploitable → proposer création d’un nouveau
                    dlg = QDialog()
                    dlg.setWindowTitle(self.tr("No group found"))
                    layout = QVBoxLayout(dlg)

                    label = QLabel(self.tr(
                        "Aucun groupe n’existe dans le projet.\n"
                        "Créez un groupe et choisissez les couches à y mettre :"
                    ))
                    layout.addWidget(label)

                    # Nom du groupe
                    name_edit = QLineEdit()
                    name_edit.setPlaceholderText(self.tr("New group name"))
                    layout.addWidget(name_edit)

                    # Couches disponibles (hors groupes exclus)
                    layers_box = QGroupBox(self.tr("Available layers"))
                    vbox_layers = QVBoxLayout(layers_box)

                    checkboxes = {}

                    for layer in QgsProject.instance().mapLayers().values():
                        # On exclut :
                        # 1) les couches non vectorielles
                        # 2) les couches contenues dans les groupes exclus
                        if (
                            layer.type() == QgsMapLayer.VectorLayer
                            and layer.id() not in excluded_layer_ids
                        ):
                            cb = QCheckBox(layer.name())
                            vbox_layers.addWidget(cb)
                            checkboxes[cb] = layer

                    layout.addWidget(layers_box)

                    # Boutons OK / Annuler
                    buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
                    layout.addWidget(buttons)

                    def accept():
                        if not name_edit.text().strip():
                            QMessageBox.warning(
                                dlg, self.tr("Error"),
                                self.tr("You must name the group.")
                            )
                            return
                        dlg.accept()

                    buttons.accepted.connect(accept)
                    buttons.rejected.connect(dlg.reject)

                    res = dlg.exec()

                    # L’utilisateur annule
                    if res != QDialog.Accepted:
                        if self.isEVT() == None:
                            self.msgisEVTNone()
                            return
                        elif self.isEVT()[0]:
                            self.layer.rollBack()
                        else:
                            self.project.removeMapLayer(self.layer.id())
                        self.msgForgottenTick()
                        return

                    # Création du groupe
                    new_group = QgsLayerTreeGroup(name_edit.text().strip())
                    root.insertChildNode(0, new_group)

                    # ----------------------------------------------------------
                    # Nouveau déplacement des couches cochées
                    # ----------------------------------------------------------
                    layers_to_move = [
                        layer
                        for cb, layer in checkboxes.items()
                        if cb.isChecked()
                    ]

                    # Appel de l’algo robuste
                    self.moveLayersIntoGroup(layers_to_move, new_group)
                    # ----------------------------------------------------------

                    new_group.setItemVisibilityChecked(True)

                    self.iface.messageBar().pushMessage(
                        self.tr("Group created"),
                        self.tr("The {new_group_name} group has been created and the selected layers have been moved.").format(new_group_name=new_group.name())
                        ,
                        Qgis.Success,
                        3
                    )

                    self.groupe_selectionne = new_group
                    self.groupes_coches = [new_group]
                    test_coche = True

                else:
                    # Il existe au moins un groupe exploitable
                    # 1) Si aucun groupe n’est coché, on propose le choix des groupes non cochés
                    if not test_coche:
                        groupe_selectionne = self.askUncheckedGroup()
                        if groupe_selectionne:
                            # L’utilisateur a choisi un groupe : on le mémorise
                            self.groupe_selectionne = groupe_selectionne
                            test_coche = True
                            self.groupes_coches = [groupe_selectionne]
                        else:
                            # L’utilisateur n’a rien choisi → annulation comme avant
                            if self.isEVT() == None:
                                self.msgisEVTNone()
                                return
                            elif self.isEVT()[0]:
                                self.layer.rollBack()
                            else:
                                self.project.removeMapLayer(self.layer.id())
                            self.msgForgottenTick()
                            return
                    # 2) À partir d’ici, on est sûr d’avoir un groupe exploitable coché
                    warning = False
                    ok = True
                    active = False
                    errBuffer_noAtt = False
                    errBuffer_Vertices = False
                    buffer_geom = None
                    buffer_geom_crs = None

                    # Masquer les groupes non sélectionnables
                    for group in self.root.children():
                        if group in self.group_no_select:
                            group.setItemVisibilityChecked(False)
                        if group in self.group_drawing:
                            group.setItemVisibilityCheckedRecursive(False)

                    # Vérifie qu’au moins une couche visible existe
                    for layer in QgsProject().instance().mapLayers().values():
                        try:
                            if self.root.findLayer(layer.id()).isVisible():
                                warning = False
                                active = True
                                break
                        except:
                            pass

                    # Création d’un buffer sur la couche courante (si demandé)
                    if self.request == 'buffer':
                        layer = self.iface.layerTreeView().currentLayer()
                        if layer is not None and layer.type() == QgsMapLayer.VectorLayer and QgsProject.instance().layerTreeRoot().findLayer(
                                layer.id()).isVisible():
                            g = self.geomTransform(rb.asGeometry(),
                                                   self.iface.mapCanvas().mapSettings().destinationCrs(), layer.crs())
                            features = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
                            style = layer.renderer()
                            rbGeom = []
                            for feature in features:
                                geom = feature.geometry()
                                if g.intersects(geom):
                                    rbGeom.append(QgsGeometry(feature.geometry()))
                            if len(rbGeom) > 0:
                                union_geoms = rbGeom[0]
                                for geometry in rbGeom:
                                    if union_geoms.combine(geometry) is not None:
                                        union_geoms = union_geoms.combine(geometry)

                                rb.setToGeometry(union_geoms, layer)
                                perim, ok = QInputDialog.getDouble(
                                    self.iface.mainWindow(),
                                    self.tr('Perimeter'),
                                    self.tr('Give a perimeter in m:\n(works only with metric crs)'),
                                    min=0)
                                buffer_geom_crs = layer.crs()
                                buffer_geom = union_geoms.buffer(perim, 40)
                                rb.setToGeometry(buffer_geom,
                                                 QgsVectorLayer("Polygon?crs=" + layer.crs().authid(), "", "memory"))
                                rb.setRenderer(style)
                                if buffer_geom.length == 0:
                                    warning = True
                                    errBuffer_Vertices = True
                            else:
                                warning = True
                                errBuffer_noAtt = True
                        else:
                            warning = True

                    # --- Sélection des entités intersectantes ---
                    if len(QgsProject().instance().mapLayers().values()) > 0 and not warning and ok:
                        self.loadingWindow.show()
                        self.loadingWindow.activateWindow()
                        self.loadingWindow.showNormal()

                        for layer in QgsProject().instance().mapLayers().values():
                            try:
                                if layer.type() == QgsMapLayer.VectorLayer and QgsProject.instance().layerTreeRoot().findLayer(
                                        layer.id()).isVisible():
                                    if self.request == 'buffer' and self.iface.layerTreeView().currentLayer() == layer:
                                        layer.selectByIds([])
                                        continue
                                    self.loadingWindow.reset()
                                    self.loadingWindow.setWindowTitle(self.tr('Selecting...'))
                                    self.loadingWindow.setLabelText(layer.name())

                                    # rubberband reprojection
                                    if self.request == 'buffer':
                                        if buffer_geom_crs.authid() != layer.crs().authid():
                                            g = self.geomTransform(buffer_geom, buffer_geom_crs, layer.crs())
                                        else:
                                            g = self.geomTransform(buffer_geom, buffer_geom_crs, layer.crs())
                                    else:
                                        g = self.geomTransform(
                                            rb.asGeometry(),
                                            self.iface.mapCanvas().mapSettings().destinationCrs(),
                                            layer.crs()
                                        )

                                    feat_id = []
                                    features = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))
                                    count = layer.getFeatures(QgsFeatureRequest(g.boundingBox()))

                                    nbfeatures = 0
                                    for feature in count:
                                        nbfeatures += 1

                                    index = 0
                                    for feature in features:
                                        geom = feature.geometry()
                                        try:
                                            if g.intersects(geom):
                                                feat_id.append(feature.id())
                                        except:
                                            feat_id.append(feature.id())
                                        index += 1
                                        self.loadingWindow.setValue(int((float(index) / nbfeatures) * 100))
                                        if self.loadingWindow.wasCanceled():
                                            self.loadingWindow.reset()
                                            break
                                        QApplication.processEvents()
                                    layer.selectByIds(feat_id)
                            except:
                                pass

                        self.loadingWindow.close()
                        self.showAttributesTable(name_draw)

                        # Une fois la sélection terminée → décochage du groupe
                        if self.groupe_selectionne:
                            if self.groupExists(self.groupe_selectionne):
                                self.groupe_selectionne.setItemVisibilityChecked(False)
                            else:
                                self.groupe_selectionne = None
                            
                            QApplication.processEvents()
                            self.iface.messageBar().pushMessage(
                                self.tr("End of selection"),
                                self.tr("The {sel_group} group has been unchecked.").format(sel_group=self.groupe_selectionne.name()),
                                Qgis.Info,
                                2
                            )
                            self._groupe_msg_affiche = False
                            self._groupe_msg_nul = False
                    else:
                        # Avertissements en cas d’erreur
                        if errBuffer_noAtt:
                            self.iface.messageBar().pushWarning(
                                self.tr('Warning'),
                                self.tr("You didn't click on a layer's attribute !"))
                        elif errBuffer_Vertices:
                            self.iface.messageBar().pushWarning(
                                self.tr('Warning'),
                                self.tr("You must give a non-null value for a point's or line's perimeter!"))
                        else:
                            self.iface.messageBar().pushWarning(
                                self.tr('Warning'),
                                self.tr('There is no selected layer, or it is not vector nor visible!'))
                        for layer in self.isEVT()[1]:
                            layer.rollback()

            elif box.clickedButton() == buttonNo:
                pass

        else:
            pass

        if self.isEVT() == None:
            self.msgisEVTNone()
            return
        elif self.isEVT()[0] and ok:
            if self.layer is None:
                self.msgNoSelect()
                return
            self.QgisV3LayerRandomColor(self.layer, form_wgt_libelle)
            self.layer.setReadOnly(True)
            try:
                for couche in self.groupes_coches:
                    couche.setItemVisibilityChecked(False)
                for group in self.group_no_select:
                    group.setItemVisibilityChecked(True)
                for group in self.group_drawing:
                    group.setItemVisibilityCheckedRecursive(True)
            except:
                pass
        elif not self.isEVT()[0]:
            for layer in self.isEVT()[1]:
                self.QgisV3LayerRandomColor(layer, 'Drawings')
            for group in self.group_drawing:
                group.setItemVisibilityCheckedRecursive(True)
        if not self.groupes_coches:
            return
        try:
            self.layer.commitChanges()
        except:
            pass
        self.tool.reset()
        self.resetSB()
        self.bGeom = None

    # --------------------------------------------------------------
    # Fonction pour vérifier si un groupe existe
    # --------------------------------------------------------------
    def groupExists(self, group):
        """Vérifie si un QgsLayerTreeGroup existe encore dans l'arbre."""
        if group is None:
            return False
        try:
            # Accès à une propriété simple : si l'objet est deleted → RuntimeError
            _ = group.name()
            return True
        except RuntimeError:
            return False

    # --------------------------------------------------------------
    # Fonction pour afficher les groupes non cochés (sélection unique)
    # --------------------------------------------------------------
    def askUncheckedGroup(self):
        # Mise à jour des groupes interdits / EVT / Drawings
        self.testGroups()
        unchecked_groups = []
        # --- Récupération des groupes non cochés dans l’arbre ---
        root = QgsProject.instance().layerTreeRoot()
        for child in root.children():
            if isinstance(child, QgsLayerTreeGroup):
                if (
                        not child.isVisible()
                        and normalize(child.name()) not in lst_group_no_select
                        and normalize(child.name()) not in lst_group_evt
                        and normalize(child.name()) not in lst_group_drawing
                ):
                    unchecked_groups.append(child)
        if not unchecked_groups:
            QMessageBox.information(
                self.iface.mainWindow(),
                self.tr("No groups"),
                self.tr("All groups are already checked.")
            )
            return None

        # --- Création de la boîte de dialogue ---
        self.dlg = QDialog(self.iface.mainWindow())
        dlg = self.dlg
        title = self.tr("Choose a group for selection")
        dlg.setWindowTitle(title)

        # Supprime les boutons système (fermer, réduire, agrandir)
        dlg.setWindowFlags(
            Qt.Window |
            Qt.CustomizeWindowHint |
            Qt.WindowTitleHint |
            Qt.WindowStaysOnTopHint
        )
        dlg.setWindowModality(Qt.ApplicationModal)

        dlg_layout = QVBoxLayout(dlg)
        label = QLabel(self.tr("Choose a group from the list:"))
        dlg_layout.addWidget(label)

        # Groupe exclusif de boutons radio
        button_group = QButtonGroup(dlg)
        button_group.setExclusive(True)

        # Fonction appelée dès qu'un groupe est choisi
        def on_selection(group):
            self.groupe_selectionne = group
            group.setItemVisibilityChecked(True)
            dlg.done(QDialog.Accepted)

        # --- Calcul de la largeur maximale parmi les noms de groupes ---
        font_metrics = dlg.fontMetrics()
        max_group_width = 0
        for group in unchecked_groups:
            text_width = font_metrics.boundingRect(group.name()).width()
            if text_width > max_group_width:
                max_group_width = text_width

        # Ajout des boutons radio
        for group in unchecked_groups:
            rb = QRadioButton(group.name())
            dlg_layout.addWidget(rb)
            button_group.addButton(rb)
            rb.toggled.connect(lambda checked, g=group: on_selection(g) if checked else None)

        # --- Ajustement automatique de la largeur ---
        title_width = font_metrics.boundingRect(title).width()
        dlg.adjustSize()
        dlg_width = max(title_width, max_group_width) + 120  # 120 px marge visuelle
        dlg.setFixedWidth(dlg_width)
        dlg.setSizeGripEnabled(False)

        result = dlg.exec_()
        if result == QDialog.Accepted:
            return self.groupe_selectionne
        return None

    def QgisV3LayerRandomColor(self, layer, fieldName):
        """
        QGIS3 : Classification automatique du moteur de rendu pyqgis catégorisé sur le champ 'fieldname' de la table attributaire 'layer'
        copyright           : (C) 2019 by Sylvain T., CNRS FR 3020
        Licence             : Creative Commons CC-BY-SA V4+
        Sources             : https://gis.stackexchange.com/questions/226642/automatic-pyqgis-categorized-renderer-classification
        :param layer: QgsVectorLayer
        :param fieldName: str
        :return:
        """
        # provide file name index and field's unique values
        fni = layer.dataProvider().fields().indexFromName(fieldName)
        unique_values = layer.uniqueValues(fni)

        # fill categories
        categories = []
        # print('layer.geometryType() : ' + str(layer.geometryType()))
        # layer.geometryType() : point = 0, ligne = 1, polygone = 2
        geom_type = layer.geometryType()
        for unique_value in unique_values:
            # initialize the default symbol for this geometry type
            symbol = QgsSymbol.defaultSymbol(layer.geometryType())
            if geom_type == 0:
                # Réglage de la taille des points
                symbol.setSize(4)
            elif geom_type == 1:
                # Réglage de l'épaisseur des lignes
                symbol.setWidth(2)
            symbol.setOpacity(0.4)

            # Couleurs aléatoires, le dernier paramètre règle le niveau de transparence (de 0 à 100%) Impose
            # l'importation de random layer_style = %1 layer_style['color'] = '%d, %d, %d' % (random.randrange(0,
            # 256), random.randrange(0, 256), random.randrange(0, 256)) layer_style['outline'] = '#000000'
            # symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style) # replace default symbol layer with the
            # configured one if symbol_layer is not None: symbol.changeSymbolLayer(0, symbol_layer)

            # Couleur rouge sur tous les objets
            symbol.setColor(QColor(255, 0, 0))

            # create renderer object
            category = QgsRendererCategory(unique_value, symbol, str(unique_value))
            # entry for the list of category items
            categories.append(category)
        # Création du rendu de l'objet (on ne fais rien si c'est un objet texte)
        if self.drawShape != 'text':
            renderer = QgsCategorizedSymbolRenderer(fieldName, categories)
            # assign the created renderer to the layer
            if renderer is not None:
                layer.setRenderer(renderer)
        layer.triggerRepaint()

    def verifCocheGroupes(self):
        # Toujours réinitialiser
        self.groupes_coches = []
        for group in self.root.children():
            # Exclure tous les groupes non sélectionnables
            if (
                    group not in self.group_no_select
                    and group not in self.group_evt
                    and group not in self.group_drawing
            ):
                if isinstance(group, QgsLayerTreeGroup):
                    node = self.root.findGroup(group.name())
                else:
                    lyr = self.project.mapLayersByName(group.name())[0]
                    node = self.root.findLayer(lyr.id())
                if node and node.isVisible():
                    self.groupes_coches.append(group)
        return bool(self.groupes_coches)

    def moveLayersIntoGroup(self, layers_to_move, target_group):
        """
        Déplace proprement une liste de QgsVectorLayer dans un groupe donné.
        On utilise l'API layer tree standard : root.findLayer(), parent.removeChildNode().
        """
        root = self.root

        for lyr in layers_to_move:
            if lyr is None:
                continue

            # Node correspondant à la couche dans l'arbre
            node = root.findLayer(lyr.id())
            if node is None:
                continue

            parent = node.parent()
            if parent is None:
                # cas très rare, mais on évite de crasher
                continue

            # On clone le node, on insère le clone dans le groupe,
            # puis on supprime l'ancien du parent.
            cloned = node.clone()
            target_group.insertChildNode(0, cloned)
            parent.removeChildNode(node)

    def findLayerNodeAnywhere(self, root, layer_id):
        """Recherche un node de couche dans tout l'arbre."""
        stack = [root]
        while stack:
            node = stack.pop()
            if isinstance(node, QgsLayerTreeLayer) and node.layerId() == layer_id:
                return node
            for child in node.children():
                stack.append(child)
        return None

    def msgisEVTNone(self):
        msgBox = QMessageBox()
        msgBox.setIcon(pyqt_messagebox_critical)
        msgBox.setWindowTitle(self.tr("WARNING!"))
        msgBox.setText(self.tr("Command not available: empty project."))
        msgBox.setStandardButtons(yes_button)
        msgBox.setDefaultButton(yes_button)
        buttonYes = msgBox.button(yes_button)
        buttonYes.setText(self.tr('Start again'))
        # Pour que la fenêtre reste au premier plan de toutes les applications en cours
        msgBox.setWindowModality(qt_windows_modality)
        msgBox.setWindowFlags(qt_window_stays_on_top_hint)
        returnValue = msgBox.exec()
        if returnValue == buttonYes:
            # print('OK clicked')
            pass

    def msgForgottenTick(self):
        msgBox = QMessageBox()
        msgBox.setIcon(pyqt_messagebox_critical)
        msgBox.setWindowTitle(self.tr("WARNING!"))
        msgBox.setText(self.tr("No layer or group is selected. Start again."))
        msgBox.setStandardButtons(yes_button)
        msgBox.setDefaultButton(yes_button)
        buttonYes = msgBox.button(yes_button)
        buttonYes.setText(self.tr('Start again'))
        # Pour que la fenêtre reste au premier plan de toutes les applications en cours
        msgBox.setWindowModality(qt_windows_modality)
        msgBox.setWindowFlags(qt_window_stays_on_top_hint)
        returnValue = msgBox.exec()
        if returnValue == buttonYes:
            # print('OK clicked')
            pass

    def msgForgottenPolygon(self):
        msgBox = QMessageBox()
        msgBox.setIcon(pyqt_messagebox_warning)
        msgBox.setWindowTitle(self.tr("WARNING!"))
        # msgBox.setText(self.tr("Selection not possible. the checked layer is not a polygon."))
        msgBox.setText(self.tr("Selection not possible. The checked layer is not a polygon."))
        msgBox.setStandardButtons(yes_button)
        msgBox.setDefaultButton(yes_button)
        buttonYes = msgBox.button(yes_button)
        buttonYes.setText(self.tr('Start again'))
        # Pour que la fenêtre reste au premier plan de toutes les applications en cours
        msgBox.setWindowModality(qt_windows_modality)
        msgBox.setWindowFlags(qt_window_stays_on_top_hint)
        returnValue = msgBox.exec()
        if returnValue == buttonYes:
            # print('OK clicked')
            pass

    def msgNoSelect(self):
        msgBox = QMessageBox()
        msgBox.setIcon(pyqt_messagebox_warning)
        msgBox.setWindowTitle(self.tr("WARNING!"))
        # msgBox.setText(self.tr("Selection not possible. No layers are selected in the Layer Manager."))
        msgBox.setText(self.tr("Selection not possible. No layer is selected in the Layer Manager."))
        msgBox.setStandardButtons(yes_button)
        msgBox.setDefaultButton(yes_button)
        buttonYes = msgBox.button(yes_button)
        buttonYes.setText(self.tr('Start again'))
        # Pour que la fenêtre reste au premier plan de toutes les applications en cours
        msgBox.setWindowModality(qt_windows_modality)
        msgBox.setWindowFlags(qt_window_stays_on_top_hint)
        returnValue = msgBox.exec()
        if returnValue == buttonYes:
            # print('OK clicked')
            pass

    def afficher_aide(self):
        """Affiche le fichier d'aide (HTML / Markdown / TXT) dans une boîte de dialogue."""

        # --- 1) Détermination de la langue ---
        lang_full = QLocale.system().name().lower()  # ex: 'fr_fr', 'en_gb'
        lang = lang_full.split("_")[0]  # 'fr', 'en', 'de', ...

        plugin_dir = Path(__file__).resolve().parent
        help_dir = plugin_dir / "help"

        # --- 2) Ordre de recherche des fichiers d'aide ---
        candidats = [
            help_dir / f"readme_{lang}.html",
            help_dir / f"readme_{lang}.htm",
            help_dir / f"readme_{lang}.md",
            help_dir / f"readme_{lang}.txt",
            help_dir / "readme_fr.html",
            help_dir / "readme_fr.htm",
            help_dir / "readme_fr.md",
            help_dir / "readme_fr.txt",
        ]
        fichier = None
        for cand in candidats:
            if cand.is_file():
                fichier = cand
                break
        # Si aucun fichier trouvé : message simple
        if fichier is None:
            QMessageBox.warning(
                self.iface.mainWindow(),
                self.tr("QdrawEVT Help - Gestion_crise"),
                self.tr("No help file was found in the 'help' folder."),
            )
            return
        # --- 3) Lecture du contenu ---
        try:
            contenu = fichier.read_text(encoding="utf-8")
        except Exception as e:
            QMessageBox.critical(
                self.iface.mainWindow(),
                self.tr("QdrawEVT Help - Gestion_crise"),
                self.tr("Unable to read the help file: {error}").format(error=str(e)),
            )
            return
        suffix = fichier.suffix.lower()
        # --- 4) Préparation HTML / Markdown / Texte brut ---
        html = ""
        if suffix in (".html", ".htm"):
            # On suppose que le contenu est déjà du HTML
            html = contenu
        elif suffix == ".md":
            # Conversion très simple Markdown -> HTML (sans dépendance externe)
            # - # Titre -> <h1>, ## -> <h2>, etc.
            # - lignes normales -> <p>
            lines = contenu.splitlines()
            html_lines = []
            for line in lines:
                stripped = line.strip()
                if stripped.startswith("###### "):
                    html_lines.append("<h6>{}</h6>".format(stripped[7:]))
                elif stripped.startswith("##### "):
                    html_lines.append("<h5>{}</h5>".format(stripped[6:]))
                elif stripped.startswith("#### "):
                    html_lines.append("<h4>{}</h4>".format(stripped[5:]))
                elif stripped.startswith("### "):
                    html_lines.append("<h3>{}</h3>".format(stripped[4:]))
                elif stripped.startswith("## "):
                    html_lines.append("<h2>{}</h2>".format(stripped[3:]))
                elif stripped.startswith("# "):
                    html_lines.append("<h1>{}</h1>".format(stripped[2:]))
                elif stripped == "":
                    html_lines.append("<br>")
                else:
                    # on échappe juste les < et > pour éviter de casser le HTML
                    safe = stripped.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
                    html_lines.append("<p>{}</p>".format(safe))
            html = "<html><body>{}</body></html>".format("\n".join(html_lines))
        else:
            # TXT : conversion propre en HTML avec wrapping automatique
            safe = contenu.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
            safe = safe.replace("\n", "<br>")
            html = f"""
                <html>
                <head>
                    <style>
                        body {{
                            font-family: 'Segoe UI', Arial, sans-serif;
                            font-size: 11pt;
                            line-height: 1.4;
                            white-space: normal;
                            word-wrap: break-word;
                            margin: 8px;
                        }}
                    </style>
                </head>
                <body>{safe}</body>
                </html>
                """
        # --- 5) Création de la boîte de dialogue ---
        dlg = QDialog(self.iface.mainWindow())
        dlg.setWindowTitle(self.tr("QdrawEVT Help - Gestion_crise"))
        dlg.resize(800, 600)

        layout = QVBoxLayout(dlg)

        browser = QTextBrowser()
        browser.setOpenExternalLinks(True)  # liens cliquables vers le web, si présents
        browser.setHtml(html)
        layout.addWidget(browser)
        # --- 6) Boutons en bas (Fermer) ---
        btn_layout = QHBoxLayout()
        btn_layout.addStretch(1)

        btn_close = QPushButton(self.tr("Close"))
        btn_close.clicked.connect(dlg.accept)
        btn_layout.addWidget(btn_close)

        layout.addLayout(btn_layout)

        dlg.exec()

    def get_gc_anchor(self):
        return self.gc_anchor

    def get_toolbar(self):
        return self.toolbar

