#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Programme pour autonuméroter les bornes et éléments dans les fichiers QElectroTech (.qet)
Version avec onglets
"""

import customtkinter as ctk
import tkinter as tk
from tkinter import filedialog, messagebox
import xml.etree.ElementTree as ET
import os
from collections import OrderedDict

# ============================================================
#  Préfixes disponibles pour la numérotation des éléments
# ============================================================
PREFIXES_ELEMENTS = ["KM", "KA", "KO", "B", "WB", "Q", "VAR", "M", "S", "H", "HV", "W"]


class QETBorneNumbering:
    def __init__(self, root):
        self.root = root
        self.root.title("Autonumérotation QElectroTech")
        # Démarrer en plein écran (maximisé)
        screen_w = self.root.winfo_screenwidth()
        screen_h = self.root.winfo_screenheight()
        self.root.geometry(f"{screen_w}x{screen_h}+0+0")

        # ── Variables onglet Bornes ──────────────────────────
        self.file_path = None
        self.xml_root = None
        self.bornes_trouvees = []
        self.selected_borne = None
        self.use_wire_num = tk.BooleanVar(value=False)

        # ── Variables onglet Éléments ───────────────────────
        self.elem_file_path = None
        self.elem_xml_root = None
        self.elem_selected_prefix = None

        # ── Variables onglet XIO ────────────────────────────
        self.xio_file_path = None
        self.xio_xml_root = None
        self.xio_folios = []  # Liste des (nom_folio, diagram_elem)
        self.xio_selected_folios = []

        # ── Variables onglet Renommage ──────────────────────
        self.ren_file_path = None
        self.ren_xml_root = None
        self.ren_bornes_a_traiter = []

        self.create_widgets()
        self.root.after(100, lambda: self.root.state('zoomed'))
    # ════════════════════════════════════════════════════════
    #  CONSTRUCTION DE L'INTERFACE
    # ════════════════════════════════════════════════════════

    def create_widgets(self):
        """Crée la fenêtre principale avec onglets"""
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)

        # ─── TabView ────────────────────────────────────────
        self.tabview = ctk.CTkTabview(self.root)
        self.tabview.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)

        self.tab_bornes   = self.tabview.add("🔌  Num borne simple")
        self.tab_xio      = self.tabview.add("📥  NUM XIO à étage")
        self.tab_elements = self.tabview.add("⚙️  Numérotation Éléments")
        self.tab_renomme  = self.tabview.add("🔄  Renommage Multi-niveaux")

        # ─── Contenu des onglets ────────────────────────────
        self._create_tab_bornes()
        self._create_tab_xio()
        self._create_tab_elements()
        self._create_tab_renomme()

    # ────────────────────────────────────────────────────────
    #  ONGLET 1 — BORNES  (code original intact)
    # ────────────────────────────────────────────────────────

    def _create_tab_bornes(self):
        """Crée l'interface graphique de l'onglet Bornes (original)"""
        main_frame = ctk.CTkFrame(self.tab_bornes)
        main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
        self.tab_bornes.columnconfigure(0, weight=1)
        self.tab_bornes.rowconfigure(0, weight=1)

        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(3, weight=1)

        # Bouton pour ouvrir le fichier
        btn_open = ctk.CTkButton(main_frame, text="Ouvrir un fichier .qet", command=self.open_file)
        btn_open.grid(row=0, column=0, columnspan=2, pady=10, sticky="we")

        # Label pour afficher le fichier ouvert
        self.label_file = ctk.CTkLabel(main_frame, text="Aucun fichier sélectionné", text_color="gray")
        self.label_file.grid(row=1, column=0, columnspan=2, sticky="we")

        # Frame pour les bornes trouvées
        frame_bornes = ctk.CTkFrame(main_frame)
        frame_bornes.grid(row=2, column=0, columnspan=2, sticky="we", pady=10)
        frame_bornes.columnconfigure(0, weight=1)

        label_bornes = ctk.CTkLabel(frame_bornes, text="Bornes trouvées")
        label_bornes.grid(row=0, column=0, columnspan=2, pady=(5, 0), padx=5)

        self.scrollable_frame = ctk.CTkScrollableFrame(frame_bornes, height=150)
        self.scrollable_frame.grid(row=1, column=0, columnspan=2, sticky="we", padx=5, pady=5)
        self.scrollable_frame.columnconfigure(0, weight=1)

        self.borne_buttons = []

        # Frame pour les boutons d'action
        frame_actions = ctk.CTkFrame(main_frame)
        frame_actions.grid(row=3, column=0, columnspan=2, sticky="nsew", pady=10)
        frame_actions.columnconfigure(0, weight=1)

        label_actions = ctk.CTkLabel(frame_actions, text="Actions")
        label_actions.grid(row=0, column=0, columnspan=2, pady=(5, 0), padx=5)

        # Bouton d'autonumérotation
        self.btn_numeroter = ctk.CTkButton(
            frame_actions, text="Numéroter les bornes sélectionnées",
            command=self.numeroter_bornes, state="disabled")
        self.btn_numeroter.grid(row=1, column=0, pady=5, sticky="we")

        # Checkbox pour le mode de numérotation (Numéro de fil)
        self.chk_wire = ctk.CTkCheckBox(
            frame_actions, text="Utiliser n° fil (Label_Fil)",
            variable=self.use_wire_num)
        self.chk_wire.grid(row=2, column=0, pady=5, sticky="we")

        # Bouton pour sauvegarder
        self.btn_save = ctk.CTkButton(
            frame_actions, text="Sauvegarder le fichier",
            command=self.save_file, state="disabled")
        self.btn_save.grid(row=3, column=0, pady=5, sticky="we")

        # ── Boutons suppression formula ──────────────────────
        lbl_formula = ctk.CTkLabel(frame_actions, text="─── Nettoyage Formula ───")
        lbl_formula.grid(row=4, column=0, pady=(10, 0))

        self.btn_remove_formula_sel = ctk.CTkButton(
            frame_actions,
            text="🧹 Supprimer formula (borne sélectionnée)",
            command=self.remove_formula_selected,
            state="disabled", fg_color="#b34700")
        self.btn_remove_formula_sel.grid(row=5, column=0, pady=5, sticky="we")

        self.btn_remove_formula_all = ctk.CTkButton(
            frame_actions,
            text="🧹 Supprimer TOUTES les formula (tout le schéma)",
            command=self.remove_formula_all,
            state="disabled", fg_color="#8b0000")
        self.btn_remove_formula_all.grid(row=6, column=0, pady=5, sticky="we")

        # ── Bouton Réinitialiser ────────────────────────────
        self.btn_reset_labels = ctk.CTkButton(
            frame_actions,
            text="🔄 Réinitialiser les labels (Base uniquement)",
            command=self.reset_terminal_labels,
            state="disabled", fg_color="#1f538d")
        self.btn_reset_labels.grid(row=7, column=0, pady=(15, 5), sticky="we")

        # Zone de statut
        self.status_var = tk.StringVar(value="Prêt")
        status_bar = ctk.CTkLabel(main_frame, textvariable=self.status_var)
        status_bar.grid(row=4, column=0, columnspan=2, sticky="we", pady=(10, 0))

    # ────────────────────────────────────────────────────────
    #  ONGLET 2 — ÉLÉMENTS
    # ────────────────────────────────────────────────────────

    def _create_tab_elements(self):
        """Crée l'interface graphique de l'onglet Éléments"""
        main_frame = ctk.CTkFrame(self.tab_elements)
        main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
        self.tab_elements.columnconfigure(0, weight=1)
        self.tab_elements.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(3, weight=1)

        # ── Ouvrir fichier ───────────────────────────────────
        btn_open_elem = ctk.CTkButton(
            main_frame, text="Ouvrir un fichier .qet",
            command=self.open_file_elements)
        btn_open_elem.grid(row=0, column=0, pady=10, sticky="we")

        self.elem_label_file = ctk.CTkLabel(
            main_frame, text="Aucun fichier sélectionné", text_color="gray")
        self.elem_label_file.grid(row=1, column=0, sticky="we")

        # ── Sélection du préfixe ─────────────────────────────
        frame_prefixes = ctk.CTkFrame(main_frame)
        frame_prefixes.grid(row=2, column=0, sticky="we", pady=10)
        frame_prefixes.columnconfigure(tuple(range(len(PREFIXES_ELEMENTS))), weight=1)

        lbl_prefix = ctk.CTkLabel(frame_prefixes, text="Choisir le type d'élément à numéroter :")
        lbl_prefix.grid(row=0, column=0, columnspan=len(PREFIXES_ELEMENTS), pady=(5, 2), padx=5)

        self.elem_prefix_buttons = {}
        for col, prefix in enumerate(PREFIXES_ELEMENTS):
            btn = ctk.CTkButton(
                frame_prefixes, text=prefix, width=70,
                command=lambda p=prefix: self.select_prefix(p),
                fg_color="transparent", border_width=1)
            btn.grid(row=1, column=col, padx=4, pady=6, sticky="we")
            self.elem_prefix_buttons[prefix] = btn

        # ── Résultat de l'analyse ────────────────────────────
        frame_result = ctk.CTkFrame(main_frame)
        frame_result.grid(row=3, column=0, sticky="nsew", pady=5)
        frame_result.columnconfigure(0, weight=1)
        frame_result.rowconfigure(1, weight=1)

        lbl_result = ctk.CTkLabel(frame_result, text="Éléments trouvés")
        lbl_result.grid(row=0, column=0, pady=(5, 0), padx=5)

        self.elem_textbox = ctk.CTkTextbox(frame_result, state="disabled", height=160)
        self.elem_textbox.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)

        # ── Actions ──────────────────────────────────────────
        frame_actions = ctk.CTkFrame(main_frame)
        frame_actions.grid(row=4, column=0, sticky="we", pady=10)
        frame_actions.columnconfigure(0, weight=1)

        lbl_act = ctk.CTkLabel(frame_actions, text="Actions")
        lbl_act.grid(row=0, column=0, pady=(5, 0))

        self.elem_btn_numero = ctk.CTkButton(
            frame_actions, text="Numéroter les éléments sélectionnés",
            command=self.numeroter_elements, state="disabled")
        self.elem_btn_numero.grid(row=1, column=0, pady=5, sticky="we")

        self.elem_btn_save = ctk.CTkButton(
            frame_actions, text="Sauvegarder le fichier",
            command=self.save_file_elements, state="disabled")
        self.elem_btn_save.grid(row=2, column=0, pady=5, sticky="we")

        # ── Boutons suppression formula (Éléments) ──────────
        lbl_formula_elem = ctk.CTkLabel(frame_actions, text="─── Nettoyage Formula ───")
        lbl_formula_elem.grid(row=3, column=0, pady=(10, 0))

        self.elem_btn_remove_formula_sel = ctk.CTkButton(
            frame_actions,
            text="🧹 Supprimer formula (type sélectionné)",
            command=self.remove_elem_formula_selected,
            state="disabled", fg_color="#b34700")
        self.elem_btn_remove_formula_sel.grid(row=4, column=0, pady=5, sticky="we")

        self.elem_btn_remove_formula_all = ctk.CTkButton(
            frame_actions,
            text="🧹 Supprimer TOUTES les formula (non-slaves)",
            command=self.remove_elem_formula_all,
            state="disabled", fg_color="#8b0000")
        self.elem_btn_remove_formula_all.grid(row=5, column=0, pady=5, sticky="we")

        # ── Barre de statut ──────────────────────────────────
        self.elem_status_var = tk.StringVar(value="Prêt")
        ctk.CTkLabel(main_frame, textvariable=self.elem_status_var).grid(
            row=5, column=0, sticky="we", pady=(5, 0))

    # ────────────────────────────────────────────────────────
    #  ONGLET 3 — RENOMMAGE MULTI-NIVEAUX
    # ────────────────────────────────────────────────────────

    def _create_tab_renomme(self):
        """Crée l'interface graphique de l'onglet Renommage"""
        main_frame = ctk.CTkFrame(self.tab_renomme)
        main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
        self.tab_renomme.columnconfigure(0, weight=1)
        self.tab_renomme.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(2, weight=1)

        # ── Ouvrir fichier ───────────────────────────────────
        btn_open_ren = ctk.CTkButton(
            main_frame, text="Ouvrir un fichier .qet",
            command=self.open_file_rename)
        btn_open_ren.grid(row=0, column=0, pady=10, sticky="we")

        self.ren_label_file = ctk.CTkLabel(
            main_frame, text="Aucun fichier sélectionné", text_color="gray")
        self.ren_label_file.grid(row=1, column=0, sticky="we")

        # ── Résultat de l'analyse ────────────────────────────
        frame_result = ctk.CTkFrame(main_frame)
        frame_result.grid(row=2, column=0, sticky="nsew", pady=5)
        frame_result.columnconfigure(0, weight=1)
        frame_result.rowconfigure(1, weight=1)

        lbl_result = ctk.CTkLabel(frame_result, text="Bornes multi-niveaux détectées")
        lbl_result.grid(row=0, column=0, pady=(5, 0), padx=5)

        self.ren_textbox = ctk.CTkTextbox(frame_result, state="disabled", height=250)
        self.ren_textbox.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)

        # ── Actions ──────────────────────────────────────────
        frame_actions = ctk.CTkFrame(main_frame)
        frame_actions.grid(row=3, column=0, sticky="we", pady=10)
        frame_actions.columnconfigure(0, weight=1)

        self.ren_btn_go = ctk.CTkButton(
            frame_actions, text="Renommer les bornes (A,B,C,D → .1,.2,.3,.4)",
            command=self.process_rename, state="disabled", fg_color="darkgreen")
        self.ren_btn_go.grid(row=0, column=0, pady=5, sticky="we")

        self.ren_btn_save = ctk.CTkButton(
            frame_actions, text="Sauvegarder le fichier modifié",
            command=self.save_file_rename, state="disabled")
        self.ren_btn_save.grid(row=1, column=0, pady=5, sticky="we")

        # ── Barre de statut ──────────────────────────────────
        self.ren_status_var = tk.StringVar(value="Prêt")
        ctk.CTkLabel(main_frame, textvariable=self.ren_status_var).grid(
            row=4, column=0, sticky="we", pady=(5, 0))

    def _create_tab_xio(self):
        """Interface pour la numérotation automatique des bornes E/S (XIO)"""
        main_frame = ctk.CTkFrame(self.tab_xio)
        main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
        self.tab_xio.columnconfigure(0, weight=1)
        self.tab_xio.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(2, weight=1)

        # ── Bouton Ouvrir ───────────────────────────────────
        btn_open = ctk.CTkButton(main_frame, text="1. Ouvrir le fichier .qet", command=self.open_file_xio)
        btn_open.grid(row=0, column=0, pady=10, sticky="we")

        self.xio_label_file = ctk.CTkLabel(main_frame, text="Aucun fichier ouvert", text_color="gray")
        self.xio_label_file.grid(row=1, column=0, sticky="we")

        # ── Liste des Folios ────────────────────────────────
        frame_list = ctk.CTkFrame(main_frame)
        frame_list.grid(row=2, column=0, sticky="nsew", pady=5)
        frame_list.columnconfigure(0, weight=1)
        frame_list.rowconfigure(1, weight=1)

        ctk.CTkLabel(frame_list, text="2. Sélectionner les folios à traiter :").grid(row=0, column=0, pady=5)

        self.xio_scroll_folios = ctk.CTkScrollableFrame(frame_list)
        self.xio_scroll_folios.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
        self.xio_folio_checks = []

        # ── Actions ──────────────────────────────────────────
        frame_actions = ctk.CTkFrame(main_frame)
        frame_actions.grid(row=3, column=0, sticky="we", pady=10)
        frame_actions.columnconfigure(0, weight=1)

        self.xio_btn_process = ctk.CTkButton(
            frame_actions, text="3. Lancer la numérotation XIO",
            command=self.process_xio, state="disabled", fg_color="darkgreen")
        self.xio_btn_process.grid(row=0, column=0, pady=5, sticky="we")

        self.xio_btn_save = ctk.CTkButton(
            frame_actions, text="4. Sauvegarder le fichier",
            command=self.save_file_xio, state="disabled")
        self.xio_btn_save.grid(row=1, column=0, pady=5, sticky="we")

        self.xio_status_var = tk.StringVar(value="Prêt")
        ctk.CTkLabel(main_frame, textvariable=self.xio_status_var).grid(row=4, column=0, pady=5)

    # ════════════════════════════════════════════════════════
    #  LOGIQUE ONGLET 1 — BORNES  (original, non modifié)
    # ════════════════════════════════════════════════════════

    def open_file(self):
        """Ouvre un fichier .qet et analyse les bornes"""
        file_path = filedialog.askopenfilename(
            title="Sélectionner un fichier QElectroTech",
            filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")]
        )

        if file_path:
            try:
                self.file_path = file_path
                self.label_file.configure(text=f"Fichier: {os.path.basename(file_path)}")

                self.analyze_file()

                if hasattr(self, '_already_modified'):
                    del self._already_modified

                self.status_var.set(f"Fichier chargé: {len(self.bornes_trouvees)} bornes trouvées")

            except Exception as e:
                messagebox.showerror("Erreur", f"Impossible d'ouvrir le fichier:\n{str(e)}")
                self.status_var.set("Erreur lors de l'ouverture du fichier")

    def analyze_file(self):
        """Analyse le fichier XML pour trouver les bornes"""
        try:
            tree = ET.parse(self.file_path)
            self.xml_root = tree.getroot()

            self.bornes_trouvees = self.extract_bornes()

            self.update_listbox()

        except ET.ParseError as e:
            raise Exception(f"Erreur de parsing XML: {str(e)}")
        except Exception as e:
            raise Exception(f"Erreur lors de l'analyse: {str(e)}")

    def extract_bornes(self):
        """Extrait toutes les bornes du fichier XML"""
        bornes = []

        for diagram in self.xml_root.iter('diagram'):
            folio = diagram.get('folio', '1')

            for element in diagram.iter('element'):
                x = float(element.get('x', '0'))
                y = float(element.get('y', '0'))

                for child in element:
                    if child.tag == 'elementInformations':
                        for info in child:
                            if info.tag == 'elementInformation':
                                name = info.get('name', '')
                                if name == 'label':
                                    text = info.text
                                    if text and text.upper().startswith('X'):
                                        bornes.append({
                                            'element': element,
                                            'label': text,
                                            'position': (x, y),
                                            'folio': folio
                                        })
                                        break

        return bornes

    def get_borne_base(self, label):
        """Extrait la base du label (tronque après ':')"""
        if ':' in label:
            return label.split(':')[0]
        return label

    def update_listbox(self):
        """Met à jour la listbox avec les bornes uniques"""
        for button in self.borne_buttons:
            button.destroy()
        self.borne_buttons = []

        bases_uniques = list(OrderedDict.fromkeys(
            [self.get_borne_base(b['label']) for b in self.bornes_trouvees]))

        for label in sorted(bases_uniques):
            button = ctk.CTkButton(
                self.scrollable_frame, text=label,
                command=lambda l=label: self.select_borne(l))
            button.grid(row=len(self.borne_buttons), column=0, sticky="we", pady=2)
            self.borne_buttons.append(button)

        if bases_uniques:
            self.btn_numeroter.configure(state="normal")
            self.btn_remove_formula_sel.configure(state="normal")
            self.btn_remove_formula_all.configure(state="normal")
            self.btn_reset_labels.configure(state="normal")
        else:
            self.btn_numeroter.configure(state="disabled")
            self.btn_remove_formula_sel.configure(state="disabled")
            self.btn_remove_formula_all.configure(state="disabled")
            self.btn_reset_labels.configure(state="disabled")

    def select_borne(self, label):
        """Gère la sélection d'une borne via les boutons"""
        self.selected_borne = label
        self.status_var.set(f"Borne sélectionnée: {label}")

    def get_connected_wire_text(self, diagram, element_id):
        """Trouve le texte du conducteur connecté à un élément donné"""
        if not element_id:
            return ""
        for conductor in diagram.iter('conductor'):
            if conductor.get('element1') == element_id or conductor.get('element2') == element_id:
                text = conductor.get('num', '')
                if text and text.strip() and text.strip() != "_":
                    return text.strip()

            for point in conductor.iter('point'):
                if point.get('element') == element_id:
                    text = conductor.get('text', '')
                    if not text:
                        text = conductor.get('num', '')
                    if text and text.strip() and text.strip() != "_":
                        return text.strip()
        return ""

    def numeroter_bornes(self):
        """Numérote les bornes sélectionnées dans leur ordre d'apparition"""
        if not self.selected_borne:
            messagebox.showwarning("Avertissement", "Veuillez sélectionner une borne à numéroter")
            return

        try:
            if not hasattr(self, '_already_modified'):
                tree = ET.parse(self.file_path)
                self.xml_root = tree.getroot()
                self._already_modified = True

            bornes_a_numeroter = []
            for diagram in self.xml_root.iter('diagram'):
                folio = diagram.get('folio', '1')
                for element in diagram.iter('element'):
                    x = float(element.get('x', '0'))
                    y = float(element.get('y', '0'))

                    for child in element:
                        if child.tag == 'elementInformations':
                            for info in child:
                                if info.tag == 'elementInformation':
                                    name = info.get('name', '')
                                    if name == 'label':
                                        text = info.text
                                        if text and self.get_borne_base(text) == self.selected_borne:
                                            bornes_a_numeroter.append({
                                                'element': element,
                                                'label': text,
                                                'position': (x, y),
                                                'folio': folio,
                                                'diagram': diagram
                                            })
                                            break

            if not bornes_a_numeroter:
                messagebox.showwarning("Avertissement", "Aucune borne trouvée pour ce label")
                return

            def natural_sort_key(s):
                """Fonction utilitaire pour trier les folios numériquement si possible"""
                import re
                return [int(text) if text.isdigit() else text.lower()
                        for text in re.split('([0-9]+)', s)]

            bornes_triees = sorted(bornes_a_numeroter,
                                   key=lambda x: (natural_sort_key(x['folio']),
                                                   x['position'][0], x['position'][1]))

            for i, borne in enumerate(bornes_triees):
                num_seq = i + 1
                if self.use_wire_num.get():
                    element_id = borne['element'].get('uuid')
                    if not element_id:
                        element_id = borne['element'].get('id')
                    wire_text = self.get_connected_wire_text(borne['diagram'], element_id)
                    suffixe = wire_text if wire_text else "?"
                    nouveau_label = f"{self.selected_borne}:{num_seq}_{suffixe}"
                else:
                    nouveau_label = f"{self.selected_borne}:{num_seq}"

                for child in borne['element']:
                    if child.tag == 'elementInformations':
                        for info in child:
                            if info.tag == 'elementInformation':
                                name = info.get('name', '')
                                if name == 'label':
                                    info.text = nouveau_label

                    elif child.tag == 'dynamic_texts':
                        for dynamic_text in child:
                            if dynamic_text.tag == 'dynamic_elmt_text':
                                for sub_child in dynamic_text:
                                    if sub_child.tag == 'info_name' and sub_child.text == 'label':
                                        for text_elem in dynamic_text:
                                            if text_elem.tag == 'text':
                                                text_elem.text = nouveau_label

            self.status_var.set(
                f"{len(bornes_triees)} bornes {self.selected_borne} numérotées avec succès")
            self.btn_save.configure(state="normal")

            messagebox.showinfo(
                "Succès", f"{len(bornes_triees)} bornes {self.selected_borne} ont été numérotées")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la numérotation:\n{str(e)}")

    def reset_terminal_labels(self):
        """Réinitialise les labels des bornes sélectionnées à leur base (ex: X2:1 -> X2)"""
        if not self.selected_borne:
            messagebox.showwarning("Avertissement", "Veuillez sélectionner une borne")
            return
        if self.xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        confirm = messagebox.askyesno(
            "Confirmation",
            f"Voulez-vous réinitialiser tous les labels des bornes '{self.selected_borne}' ?\n\n"
            f"Exemple: {self.selected_borne}:1 ➔ {self.selected_borne}")
        if not confirm:
            return

        try:
            if not hasattr(self, '_already_modified'):
                tree = ET.parse(self.file_path)
                self.xml_root = tree.getroot()
                self._already_modified = True

            count = 0
            new_label = self.selected_borne

            for diagram in self.xml_root.iter('diagram'):
                for element in diagram.iter('element'):
                    is_match = False
                    # Vérifier le label actuel dans elementInformations
                    for info in element.findall(".//elementInformation[@name='label']"):
                        if info.text and self.get_borne_base(info.text) == self.selected_borne:
                            is_match = True
                            info.text = new_label
                            break
                    
                    if is_match:
                        count += 1
                        # Mettre à jour les textes dynamiques
                        for dynamic in element.findall(".//dynamic_elmt_text"):
                            # On cherche soit un texte dont la source est "label", soit un info_name "label"
                            is_label_text = False
                            for child in dynamic:
                                if child.tag == 'info_name' and child.text == 'label':
                                    is_label_text = True
                                    break
                            
                            if is_label_text:
                                for child in dynamic:
                                    if child.tag == 'text':
                                        child.text = new_label

            if count > 0:
                self.status_var.set(f"{count} bornes réinitialisées. Sauvegarde requise.")
                messagebox.showinfo("Succès", f"{count} bornes ont été réinitialisées en '{new_label}'.\n\nPensez à sauvegarder pour appliquer les changements.")
                self.btn_save.configure(state="normal")
            else:
                messagebox.showinfo("Information", "Aucune borne à réinitialiser trouvée.")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la réinitialisation:\n{str(e)}")

    # ── Suppression des champs formula ────────────────────

    def remove_formula_selected(self):
        """Supprime les champs 'formula' des bornes du bornier sélectionné"""
        if not self.selected_borne:
            messagebox.showwarning("Avertissement", "Veuillez sélectionner une borne")
            return
        if self.xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        try:
            if not hasattr(self, '_already_modified'):
                tree = ET.parse(self.file_path)
                self.xml_root = tree.getroot()
                self._already_modified = True

            count = 0
            for diagram in self.xml_root.iter('diagram'):
                for element in diagram.iter('element'):
                    # Vérifier si cet élément fait partie du bornier sélectionné
                    is_selected = False
                    for child in element:
                        if child.tag == 'elementInformations':
                            for info in child:
                                if (info.tag == 'elementInformation'
                                        and info.get('name', '') == 'label'):
                                    text = info.text
                                    if text and self.get_borne_base(text) == self.selected_borne:
                                        is_selected = True
                                    break

                    if is_selected:
                        for child in element:
                            if child.tag == 'elementInformations':
                                formulas = [info for info in child
                                            if info.tag == 'elementInformation'
                                            and info.get('name', '') == 'formula']
                                for f in formulas:
                                    child.remove(f)
                                    count += 1

            if count > 0:
                self.btn_save.configure(state="normal")
                self.status_var.set(
                    f"{count} champ(s) formula supprimé(s) sur les bornes {self.selected_borne}")
                messagebox.showinfo(
                    "Succès",
                    f"{count} champ(s) 'formula' supprimé(s)\n"
                    f"sur les bornes du bornier {self.selected_borne}")
            else:
                messagebox.showinfo(
                    "Information",
                    f"Aucun champ 'formula' trouvé sur les bornes {self.selected_borne}")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la suppression:\n{str(e)}")

    def remove_formula_all(self):
        """Supprime les champs 'formula' de TOUTES les bornes du schéma"""
        if self.xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        confirm = messagebox.askyesno(
            "Confirmation",
            "Supprimer les champs 'formula' de TOUTES les bornes du schéma ?\n\n"
            "Cette action concerne toutes les bornes (X...) du fichier.")
        if not confirm:
            return

        try:
            if not hasattr(self, '_already_modified'):
                tree = ET.parse(self.file_path)
                self.xml_root = tree.getroot()
                self._already_modified = True

            count = 0
            for diagram in self.xml_root.iter('diagram'):
                for element in diagram.iter('element'):
                    # Vérifier si c'est une borne (label commence par X)
                    is_borne = False
                    for child in element:
                        if child.tag == 'elementInformations':
                            for info in child:
                                if (info.tag == 'elementInformation'
                                        and info.get('name', '') == 'label'):
                                    text = info.text
                                    if text and text.upper().startswith('X'):
                                        is_borne = True
                                    break

                    if is_borne:
                        for child in element:
                            if child.tag == 'elementInformations':
                                formulas = [info for info in child
                                            if info.tag == 'elementInformation'
                                            and info.get('name', '') == 'formula']
                                for f in formulas:
                                    child.remove(f)
                                    count += 1

            if count > 0:
                self.btn_save.configure(state="normal")
                self.status_var.set(
                    f"{count} champ(s) formula supprimé(s) sur toutes les bornes")
                messagebox.showinfo(
                    "Succès",
                    f"{count} champ(s) 'formula' supprimé(s)\n"
                    f"sur l'ensemble des bornes du schéma")
            else:
                messagebox.showinfo(
                    "Information",
                    "Aucun champ 'formula' trouvé sur les bornes du schéma")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la suppression:\n{str(e)}")

    def save_file(self):
        """Sauvegarde le fichier modifié (onglet Bornes)"""
        if self.xml_root is None:
            messagebox.showwarning("Avertissement", "Aucun fichier à sauvegarder")
            return

        try:
            save_path = filedialog.asksaveasfilename(
                title="Sauvegarder le fichier",
                defaultextension=".qet",
                filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")],
                initialfile=os.path.basename(self.file_path)
            )

            if save_path:
                tree = ET.ElementTree(self.xml_root)
                ET.indent(tree, space="  ", level=0)
                tree.write(save_path, encoding='utf-8', xml_declaration=True)
                self.status_var.set(f"Fichier sauvegardé: {os.path.basename(save_path)}")
                messagebox.showinfo("Succès", "Fichier sauvegardé avec succès")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")

    # ════════════════════════════════════════════════════════
    #  LOGIQUE ONGLET 2 — ÉLÉMENTS
    # ════════════════════════════════════════════════════════

    def open_file_elements(self):
        """Ouvre un fichier .qet pour la numérotation des éléments"""
        file_path = filedialog.askopenfilename(
            title="Sélectionner un fichier QElectroTech",
            filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")]
        )

        if file_path:
            try:
                self.elem_file_path = file_path
                self.elem_label_file.configure(text=f"Fichier: {os.path.basename(file_path)}")

                tree = ET.parse(self.elem_file_path)
                self.elem_xml_root = tree.getroot()

                # Réinitialiser la sélection
                self.elem_selected_prefix = None
                self._reset_prefix_buttons()
                self._update_elem_textbox("Fichier chargé. Sélectionnez un type d'élément ci-dessus.")
                self.elem_btn_numero.configure(state="disabled")
                self.elem_btn_save.configure(state="disabled")
                self.elem_status_var.set(f"Fichier chargé : {os.path.basename(file_path)}")

                # Réinitialiser le flag de modification
                if hasattr(self, '_elem_already_modified'):
                    del self._elem_already_modified

            except Exception as e:
                messagebox.showerror("Erreur", f"Impossible d'ouvrir le fichier:\n{str(e)}")
                self.elem_status_var.set("Erreur lors de l'ouverture du fichier")

    def select_prefix(self, prefix):
        """Gère la sélection d'un préfixe et affiche les éléments trouvés"""
        if self.elem_xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        self.elem_selected_prefix = prefix

        # Mettre en valeur le bouton sélectionné
        self._reset_prefix_buttons()
        self.elem_prefix_buttons[prefix].configure(fg_color=("#1a73e8", "#1a55a8"), border_width=0)

        # Extraire et afficher les éléments (les slaves sont exclus automatiquement)
        elements = self.extract_elements_by_prefix(self.elem_xml_root, prefix)
        count = len(elements)
        slaves_exclus = getattr(self, '_last_slaves_exclus', 0)

        if count == 0:
            msg = f"Aucun élément commençant par '{prefix}' trouvé dans ce fichier."
            if slaves_exclus > 0:
                msg += f"\n\n⚠ {slaves_exclus} élément(s) 'slave' exclus (contacts auxiliaires)."
            self._update_elem_textbox(msg)
            self.elem_btn_numero.configure(state="disabled")
            self.elem_btn_remove_formula_sel.configure(state="disabled")
            self.elem_btn_remove_formula_all.configure(state="normal")  # Toujours possible si fichier ouvert
        else:
            lines = [f"→ {count} élément(s) '{prefix}' trouvé(s) :"]
            if slaves_exclus > 0:
                lines.append(f"  ℹ {slaves_exclus} élément(s) 'slave' exclus (contacts auxiliaires)\n")
            else:
                lines.append("")
            for el in elements:
                lines.append(
                    f"  • Folio {el['folio']}  |  Label : {el['label']}  |  "
                    f"Position : ({el['position'][0]:.0f}, {el['position'][1]:.0f})"
                )
            self._update_elem_textbox("\n".join(lines))
            self.elem_btn_numero.configure(state="normal")
            self.elem_btn_remove_formula_sel.configure(state="normal")
            self.elem_btn_remove_formula_all.configure(state="normal")

        status = f"{count} élément(s) '{prefix}' trouvé(s)"
        if slaves_exclus > 0:
            status += f" — {slaves_exclus} slave(s) exclus"
        self.elem_status_var.set(status)

    def _build_link_type_map(self, xml_root):
        """Construit un dictionnaire {nom_fichier_elmt: link_type} depuis la collection du projet."""
        link_type_map = {}
        collection = xml_root.find('collection')
        if collection is not None:
            for element in collection.iter('element'):
                elem_name = element.get('name', '')
                definition = element.find('definition')
                if definition is not None and elem_name:
                    link_type_map[elem_name] = definition.get('link_type', '')
        return link_type_map

    def extract_elements_by_prefix(self, xml_root, prefix):
        """Extrait les éléments dont le label commence par le préfixe donné.
        Les éléments de type 'slave' sont automatiquement exclus."""
        elements = []
        slaves_exclus = 0
        prefix_upper = prefix.upper()

        # Construire la carte link_type depuis la collection du projet
        link_type_map = self._build_link_type_map(xml_root)

        for diagram in xml_root.iter('diagram'):
            folio = diagram.get('folio', '1')
            for element in diagram.iter('element'):
                # Extraire le nom du fichier .elmt depuis l'attribut 'type'
                # Ex: 'embed://import/com_puiss4.elmt' -> 'com_puiss4.elmt'
                elem_type = element.get('type', '')
                elem_filename = elem_type.split('/')[-1] if '/' in elem_type else elem_type

                # Exclure les éléments de type 'slave'
                if link_type_map.get(elem_filename, '') == 'slave':
                    # Vérifier si le label commence par le préfixe pour le comptage
                    for child in element:
                        if child.tag == 'elementInformations':
                            for info in child:
                                if (info.tag == 'elementInformation'
                                        and info.get('name', '') == 'label'):
                                    text = info.text
                                    if text and text.upper().startswith(prefix_upper):
                                        slaves_exclus += 1
                    continue

                x = float(element.get('x', '0'))
                y = float(element.get('y', '0'))

                for child in element:
                    if child.tag == 'elementInformations':
                        for info in child:
                            if info.tag == 'elementInformation':
                                if info.get('name', '') == 'label':
                                    text = info.text
                                    if text and text.upper().startswith(prefix_upper):
                                        elements.append({
                                            'element': element,
                                            'label': text,
                                            'position': (x, y),
                                            'folio': folio,
                                            'diagram': diagram,
                                            'slaves_exclus': 0  # réservé
                                        })
                                        break

        # Stocker le nombre de slaves exclus pour l'affichage
        self._last_slaves_exclus = slaves_exclus
        return elements

    def numeroter_elements(self):
        """Numérote séquentiellement les éléments du préfixe sélectionné"""
        if not self.elem_selected_prefix:
            messagebox.showwarning("Avertissement", "Veuillez sélectionner un type d'élément")
            return
        if not self.elem_xml_root:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        try:
            # Recharger le fichier XML si c'est la première numérotation
            if not hasattr(self, '_elem_already_modified'):
                tree = ET.parse(self.elem_file_path)
                self.elem_xml_root = tree.getroot()
                self._elem_already_modified = True

            prefix = self.elem_selected_prefix
            elements = self.extract_elements_by_prefix(self.elem_xml_root, prefix)

            if not elements:
                messagebox.showwarning("Avertissement",
                                       f"Aucun élément '{prefix}' trouvé dans ce fichier")
                return

            def natural_sort_key(s):
                import re
                return [int(t) if t.isdigit() else t.lower()
                        for t in re.split('([0-9]+)', s)]

            # Trier par folio puis par position (gauche→droite, haut→bas)
            elements_tries = sorted(
                elements,
                key=lambda e: (natural_sort_key(e['folio']),
                               e['position'][0], e['position'][1])
            )

            # Numéroter : KM → KM1, KM2, KM3 ...
            for i, elem in enumerate(elements_tries):
                nouveau_label = f"{prefix}{i + 1}"

                for child in elem['element']:
                    if child.tag == 'elementInformations':
                        for info in child:
                            if (info.tag == 'elementInformation'
                                    and info.get('name', '') == 'label'):
                                info.text = nouveau_label

                    elif child.tag == 'dynamic_texts':
                        for dynamic_text in child:
                            if dynamic_text.tag == 'dynamic_elmt_text':
                                for sub_child in dynamic_text:
                                    if (sub_child.tag == 'info_name'
                                            and sub_child.text == 'label'):
                                        for text_elem in dynamic_text:
                                            if text_elem.tag == 'text':
                                                text_elem.text = nouveau_label

            self.elem_status_var.set(
                f"{len(elements_tries)} élément(s) '{prefix}' numérotés avec succès")
            self.elem_btn_save.configure(state="normal")

            messagebox.showinfo(
                "Succès",
                f"{len(elements_tries)} élément(s) '{prefix}' numérotés :\n"
                f"  {prefix}1, {prefix}2, ... {prefix}{len(elements_tries)}"
            )

            # Rafraîchir l'affichage
            self.select_prefix(prefix)

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la numérotation:\n{str(e)}")

    def remove_elem_formula_selected(self):
        """Supprime les champs 'formula' des éléments du type (préfixe) sélectionné"""
        if not self.elem_selected_prefix:
            messagebox.showwarning("Avertissement", "Veuillez sélectionner un type d'élément")
            return
        if self.elem_xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        try:
            if not hasattr(self, '_elem_already_modified'):
                tree = ET.parse(self.elem_file_path)
                self.elem_xml_root = tree.getroot()
                self._elem_already_modified = True

            prefix = self.elem_selected_prefix
            elements = self.extract_elements_by_prefix(self.elem_xml_root, prefix)

            if not elements:
                messagebox.showwarning("Avertissement", f"Aucun élément '{prefix}' trouvé")
                return

            count = 0
            for item in elements:
                element = item['element']
                for child in element:
                    if child.tag == 'elementInformations':
                        formulas = [info for info in child
                                    if info.tag == 'elementInformation'
                                    and info.get('name', '') == 'formula']
                        for f in formulas:
                            child.remove(f)
                            count += 1

            if count > 0:
                self.elem_btn_save.configure(state="normal")
                self.elem_status_var.set(
                    f"{count} champ(s) formula supprimé(s) sur les éléments {prefix}")
                messagebox.showinfo(
                    "Succès",
                    f"{count} champ(s) 'formula' supprimé(s)\n"
                    f"sur les éléments de type {prefix}")
            else:
                messagebox.showinfo(
                    "Information",
                    f"Aucun champ 'formula' trouvé sur les éléments {prefix}")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la suppression:\n{str(e)}")

    def remove_elem_formula_all(self):
        """Supprime les champs 'formula' de TOUS les éléments du schéma (hors slaves)"""
        if self.elem_xml_root is None:
            messagebox.showwarning("Avertissement", "Veuillez d'abord ouvrir un fichier .qet")
            return

        confirm = messagebox.askyesno(
            "Confirmation",
            "Supprimer les champs 'formula' de TOUT les éléments (hors slaves) ?\n\n"
            "Cette action nettoiera tous les composants principaux du schéma.")
        if not confirm:
            return

        try:
            if not hasattr(self, '_elem_already_modified'):
                tree = ET.parse(self.elem_file_path)
                self.elem_xml_root = tree.getroot()
                self._elem_already_modified = True

            link_type_map = self._build_link_type_map(self.elem_xml_root)
            count = 0

            for diagram in self.elem_xml_root.iter('diagram'):
                for element in diagram.iter('element'):
                    # Exclure les slaves
                    elem_type = element.get('type', '')
                    elem_filename = elem_type.split('/')[-1] if '/' in elem_type else elem_type
                    if link_type_map.get(elem_filename, '') == 'slave':
                        continue

                    # Supprimer les formulas
                    for child in element:
                        if child.tag == 'elementInformations':
                            formulas = [info for info in child
                                        if info.tag == 'elementInformation'
                                        and info.get('name', '') == 'formula']
                            for f in formulas:
                                child.remove(f)
                                count += 1

            if count > 0:
                self.elem_btn_save.configure(state="normal")
                self.elem_status_var.set(
                    f"{count} champ(s) formula supprimé(s) sur tous les éléments")
                messagebox.showinfo(
                    "Succès",
                    f"{count} champ(s) 'formula' supprimé(s)\n"
                    f"sur l'ensemble des éléments (hors slaves)")
            else:
                messagebox.showinfo(
                    "Information",
                    "Aucun champ 'formula' trouvé sur les éléments")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la suppression:\n{str(e)}")

    def save_file_elements(self):
        """Sauvegarde le fichier modifié (onglet Éléments)"""
        if self.elem_xml_root is None:
            messagebox.showwarning("Avertissement", "Aucun fichier à sauvegarder")
            return

        try:
            save_path = filedialog.asksaveasfilename(
                title="Sauvegarder le fichier",
                defaultextension=".qet",
                filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")],
                initialfile=os.path.basename(self.elem_file_path)
            )

            if save_path:
                tree = ET.ElementTree(self.elem_xml_root)
                ET.indent(tree, space="  ", level=0)
                tree.write(save_path, encoding='utf-8', xml_declaration=True)
                self.elem_status_var.set(f"Fichier sauvegardé : {os.path.basename(save_path)}")
                messagebox.showinfo("Succès", "Fichier sauvegardé avec succès")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")

    # ════════════════════════════════════════════════════════
    #  LOGIQUE ONGLET XIO
    # ════════════════════════════════════════════════════════

    def open_file_xio(self):
        """Ouvre un fichier pour le traitement XIO"""
        file_path = filedialog.askopenfilename(
            title="Ouvrir un fichier QElectroTech",
            filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")]
        )
        if not file_path: return

        try:
            self.xio_file_path = file_path
            self.xio_label_file.configure(text=f"Fichier: {os.path.basename(file_path)}")
            tree = ET.parse(file_path)
            self.xio_xml_root = tree.getroot()

            # Extraire les folios
            for widget in self.xio_folio_checks: widget.destroy()
            self.xio_folio_checks = []
            self.xio_folios = []

            for diagram in self.xio_xml_root.iter('diagram'):
                folio_name = diagram.get('folio', '?')
                title = diagram.get('title', '')
                display_text = f"Folio {folio_name} - {title}"
                
                var = tk.BooleanVar(value=False)
                check = ctk.CTkCheckBox(self.xio_scroll_folios, text=display_text, variable=var)
                check.grid(row=len(self.xio_folio_checks), column=0, sticky="w", pady=2, padx=5)
                
                self.xio_folio_checks.append(check)
                self.xio_folios.append({'name': folio_name, 'diagram': diagram, 'var': var})

            self.xio_btn_process.configure(state="normal")
            self.xio_status_var.set(f"{len(self.xio_folios)} folios chargés")

        except Exception as e:
            messagebox.showerror("Erreur", str(e))

    def process_xio(self):
        """Algorithme de numérotation XIO"""
        selected_data = [f for f in self.xio_folios if f['var'].get()]
        if not selected_data:
            messagebox.showwarning("Attention", "Sélectionnez au moins un folio")
            return

        INDx, INDy, INDz = 0, 0, 0
        total_modifs = 0

        try:
            for folio in selected_data:
                diagram = folio['diagram']
                start_INDx = INDx
                INDy, INDz = start_INDx, start_INDx

                # 1. Signaux (.3) : I, O, E, Q, A
                signaux = self._find_xio_bornes(diagram, ["I", "O", "E", "Q", "A"])
                for b in signaux:
                    INDx += 1
                    self._rename_borne(b['element'], b['base'], f"{INDx}.3")
                    total_modifs += 1

                # 2. Masse (.1) : 0V
                masses = self._find_xio_bornes(diagram, ["0V", "0v"])
                for b in masses:
                    INDy += 1
                    self._rename_borne(b['element'], b['base'], f"{INDy}.1")
                    total_modifs += 1

                # 3. Alim (.2) : 24V
                alims = self._find_xio_bornes(diagram, ["24V", "24v"])
                for b in alims:
                    INDz += 1
                    self._rename_borne(b['element'], b['base'], f"{INDz}.2")
                    total_modifs += 1

            self.xio_btn_save.configure(state="normal")
            self.xio_status_var.set(f"Traitement fini : {total_modifs} bornes renommées")
            messagebox.showinfo("Succès", f"Numérotation terminée.\n{total_modifs} bornes traitées sur {len(selected_data)} folios.")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur pendant le traitement : {str(e)}")

    def _find_xio_bornes(self, diagram, patterns):
        """Trouve les bornes X... raccordées à un fil correspondant aux patterns"""
        found = []
        for element in diagram.iter('element'):
            # Vérifier si c'est une borne
            base_label = ""
            for info in element.findall(".//elementInformation[@name='label']"):
                if info.text and info.text.upper().startswith('X'):
                    base_label = self.get_borne_base(info.text)
                    break
            
            if not base_label: continue

            # Vérifier le fil connecté
            element_id = element.get('uuid') or element.get('id')
            wire_text = self.get_connected_wire_text(diagram, element_id)
            
            if any(p in wire_text for p in patterns):
                x = float(element.get('x', '0'))
                y = float(element.get('y', '0'))
                found.append({'element': element, 'base': base_label, 'x': x, 'y': y})

        # Trier par position (Gauche -> Droite, puis Haut -> Bas)
        return sorted(found, key=lambda b: (b['y'], b['x']))

    def _rename_borne(self, element, base, suffix):
        """Applique le nouveau label X...:IND.S"""
        new_label = f"{base}:{suffix}"
        
        # 1. elementInformations
        for info in element.findall(".//elementInformation[@name='label']"):
            info.text = new_label
        
        # 2. dynamic_texts
        for dynamic in element.findall(".//dynamic_elmt_text"):
            info_name = dynamic.find("info_name")
            if info_name is not None and info_name.text == "label":
                txt_item = dynamic.find("text")
                if txt_item is not None:
                    txt_item.text = new_label

    def save_file_xio(self):
        """Sauvegarde après traitement XIO"""
        if self.xio_xml_root is None: return
        path = filedialog.asksaveasfilename(defaultextension=".qet", initialfile=os.path.basename(self.xio_file_path))
        if path:
            tree = ET.ElementTree(self.xio_xml_root)
            ET.indent(tree, space="  ", level=0)
            tree.write(path, encoding='utf-8', xml_declaration=True)
            messagebox.showinfo("Sauvegarde", "Fichier enregistré avec succès")

    # ════════════════════════════════════════════════════════
    #  LOGIQUE ONGLET 3 — RENOMMAGE
    # ════════════════════════════════════════════════════════

    def open_file_rename(self):
        """Ouvre un fichier .qet pour le renommage des bornes"""
        file_path = filedialog.askopenfilename(
            title="Sélectionner un fichier QElectroTech",
            filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")]
        )

        if file_path:
            try:
                self.ren_file_path = file_path
                self.ren_label_file.configure(text=f"Fichier: {os.path.basename(file_path)}")

                tree = ET.parse(self.ren_file_path)
                self.ren_xml_root = tree.getroot()

                self.analyze_rename()

            except Exception as e:
                messagebox.showerror("Erreur", f"Impossible d'ouvrir le fichier:\n{str(e)}")
                self.ren_status_var.set("Erreur lors de l'ouverture du fichier")

    def analyze_rename(self):
        """Recherche les bornes avec des lettres A, B, C, D"""
        import re
        self.ren_bornes_a_traiter = []
        # Regex pour capturer X + nombre + lettre A/B/C/D + optionnel (: + sequence)
        pattern = re.compile(r'^X(\d+)([ABCD])(?::(\d+))?$', re.IGNORECASE)
        
        preview_lines = []

        for diagram in self.ren_xml_root.iter('diagram'):
            folio = diagram.get('folio', '1')
            for element in diagram.iter('element'):
                for child in element:
                    if child.tag == 'elementInformations':
                        for info in child:
                            if info.tag == 'elementInformation' and info.get('name', '') == 'label':
                                label = info.text
                                if label:
                                    match = pattern.match(label.strip())
                                    if match:
                                        num_borne = match.group(1)
                                        lettre = match.group(2).upper()
                                        seq = match.group(3)
                                        
                                        # Déterminer le suffixe .1, .2, .3, .4
                                        suffix = {"A": ".1", "B": ".2", "C": ".3", "D": ".4"}.get(lettre, "")
                                        
                                        if seq:
                                            nouveau_label = f"X{num_borne}:{seq}{suffix}"
                                        else:
                                            nouveau_label = f"X{num_borne}{suffix}"
                                            
                                        self.ren_bornes_a_traiter.append({
                                            'info_elem': info,
                                            'old_label': label,
                                            'new_label': nouveau_label,
                                            'element': element,
                                            'folio': folio
                                        })
                                        preview_lines.append(f"Folio {folio:3} | {label:8} ➔ {nouveau_label}")
                                        break

        if not self.ren_bornes_a_traiter:
            self._update_rename_textbox("Aucune borne de type X...A, X...B, etc. trouvée.")
            self.ren_btn_go.configure(state="disabled")
            self.ren_status_var.set("Aucune borne à traiter.")
        else:
            header = f"{len(self.ren_bornes_a_traiter)} bornes détectées à transformer :\n"
            header += "-" * 40 + "\n"
            self._update_rename_textbox(header + "\n".join(preview_lines))
            self.ren_btn_go.configure(state="normal")
            self.ren_status_var.set(f"{len(self.ren_bornes_a_traiter)} bornes prêtes.")
        
        self.ren_btn_save.configure(state="disabled")

    def process_rename(self):
        """Applique les changements de noms dans le XML en mémoire"""
        if not self.ren_bornes_a_traiter:
            return
            
        count = 0
        for item in self.ren_bornes_a_traiter:
            new_label = item['new_label']
            # Mettre à jour elementInformation 'label'
            item['info_elem'].text = new_label
            
            # Mettre à jour les textes dynamiques si présents
            for child in item['element']:
                if child.tag == 'dynamic_texts':
                    for dynamic_text in child:
                        if dynamic_text.tag == 'dynamic_elmt_text':
                            is_label = False
                            for sub_child in dynamic_text:
                                if sub_child.tag == 'info_name' and sub_child.text == 'label':
                                    is_label = True
                                    break
                            if is_label:
                                for text_elem in dynamic_text:
                                    if text_elem.tag == 'text':
                                        text_elem.text = new_label
            count += 1
            
        self.ren_status_var.set(f"{count} bornes renommées.")
        self.ren_btn_save.configure(state="normal")
        self.ren_btn_go.configure(state="disabled")
        messagebox.showinfo("Succès", f"{count} bornes ont été transformées avec succès.")

    def save_file_rename(self):
        """Sauvegarde le fichier après renommage"""
        if not self.ren_xml_root:
            return

        try:
            save_path = filedialog.asksaveasfilename(
                title="Sauvegarder le fichier",
                defaultextension=".qet",
                filetypes=[("Fichiers QElectroTech", "*.qet"), ("Tous les fichiers", "*.*")],
                initialfile=os.path.basename(self.ren_file_path)
            )

            if save_path:
                tree = ET.ElementTree(self.ren_xml_root)
                ET.indent(tree, space="  ", level=0)
                tree.write(save_path, encoding='utf-8', xml_declaration=True)
                self.ren_status_var.set(f"Sauvegardé : {os.path.basename(save_path)}")
                messagebox.showinfo("Succès", "Fichier sauvegardé avec succès")

        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")

    def _update_rename_textbox(self, text):
        """Met à jour le textbox de l'onglet Renommage"""
        self.ren_textbox.configure(state="normal")
        self.ren_textbox.delete("1.0", "end")
        self.ren_textbox.insert("1.0", text)
        self.ren_textbox.configure(state="disabled")

    # ════════════════════════════════════════════════════════
    #  UTILITAIRES
    # ════════════════════════════════════════════════════════

    def _reset_prefix_buttons(self):
        """Remet tous les boutons de préfixe à leur état neutre"""
        for btn in self.elem_prefix_buttons.values():
            btn.configure(fg_color="transparent", border_width=1)

    def _update_elem_textbox(self, text):
        """Met à jour le textbox de résultat de l'onglet Éléments"""
        self.elem_textbox.configure(state="normal")
        self.elem_textbox.delete("1.0", "end")
        self.elem_textbox.insert("1.0", text)
        self.elem_textbox.configure(state="disabled")


# ════════════════════════════════════════════════════════════
#  POINT D'ENTRÉE
# ════════════════════════════════════════════════════════════

def main():
    """Fonction principale"""
    ctk.set_appearance_mode("System")
    ctk.set_default_color_theme("blue")
    root = ctk.CTk()
    app = QETBorneNumbering(root)
    root.mainloop()


if __name__ == "__main__":
    main()
