import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from tkinter import ttk
import xml.etree.ElementTree as ET
import customtkinter as ctk
from pathlib import Path
import sys
import re
import csv
from datetime import datetime
try:
    from tksheet import Sheet
except ImportError:
    messagebox.showerror("Erreur", "La librairie 'tksheet' est requise.\n\nExécutez cette commande :\npython -m pip install tksheet")
    sys.exit(1)

class QETAnalyzer:
    def __init__(self, root):
        self.root = root
        self.root.title("QElectroTech Nomenclature - Analyseur")
        self.root.geometry("1200x700")
        
        self.tree_file = None
        self.file_path = None
        self.elements_data = []
        self.columns_hidden = False  # État d'affichage des colonnes
        
        # Définir l'ordre des colonnes une seule fois
        self.columns_order = ('UUID', 'LABEL', 'FABRICANT', 'NUM ARTICLE', 'DESCRIPTION', 'COMMENTAIRE', 
                              'LOCALISATION', 'INSTALLATION', 'BLOC AUX 1', 'BLOC AUX 2', 'FONCTION',
                              'NUMERO DE COMMANDE', 'FOURNISSEUR', 'NUMERO INTERNE', 'UNITE', 'QUANTITE', 'FOLIO')
        
        # Style
        ctk.set_appearance_mode("System")  # Modes: "System" (standard), "Dark", "Light"
        ctk.set_default_color_theme("blue")  # Themes: "blue" (standard), "green", "dark-blue"
        
        # Frame principal
        main_frame = ctk.CTkFrame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Frame du haut avec boutons
        top_frame = ctk.CTkFrame(main_frame)
        top_frame.pack(fill=tk.X, pady=5)
        
        ctk.CTkButton(top_frame, text="Ouvrir fichier .qet", command=self.open_file).pack(side=tk.LEFT, padx=5)
        ctk.CTkButton(top_frame, text="Sauvegarder", command=self.save_file).pack(side=tk.LEFT, padx=5)
        ctk.CTkButton(top_frame, text="Annuler (Undo)", command=lambda: self.sheet.undo()).pack(side=tk.LEFT, padx=5)
        ctk.CTkButton(top_frame, text="Rétablir (Redo)", command=lambda: self.sheet.redo()).pack(side=tk.LEFT, padx=5)
        ctk.CTkButton(top_frame, text="Exporter en CSV", command=self.export_csv).pack(side=tk.LEFT, padx=5)
        ctk.CTkButton(top_frame, text="Masquer/Afficher colonnes", command=self.toggle_columns).pack(side=tk.LEFT, padx=5)
        
        self.file_label = ctk.CTkLabel(top_frame, text="Aucun fichier ouvert", text_color="gray")
        self.file_label.pack(side=tk.LEFT, padx=20)
        
        # Frame pour le tableau
        tree_frame = ctk.CTkFrame(main_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # TKSHEET
        self.sheet = Sheet(tree_frame,
                           headers=list(self.columns_order),
                           empty_horizontal=0,
                           empty_vertical=0)
        self.sheet.enable_bindings("all") # Active copier/coller, édition, tri, etc.
        
        # Interdire l'ajout/suppression de lignes/colonnes via le clic droit pour éviter les erreurs
        self.sheet.disable_bindings("rc_insert_column", "rc_delete_column", "rc_insert_row", "rc_delete_row")
        # Rendre les colonnes LABEL (index 1) et FOLIO (index 16) en lecture seule
        self.sheet.readonly_columns(columns=[1, 16])
        self.sheet.pack(fill="both", expand=True)
        
        # Frame du bas avec info
        bottom_frame = ctk.CTkFrame(main_frame)
        bottom_frame.pack(fill=tk.X, pady=5)
        
        self.info_label = ctk.CTkLabel(bottom_frame, text="Éléments: 0")
        self.info_label.pack(side=tk.LEFT)
    
    def open_file(self):
        file_path = filedialog.askopenfilename(
            title="Sélectionner un fichier QElectroTech",
            filetypes=[("QET files", "*.qet"), ("All files", "*.*")]
        )
        
        if not file_path:
            return
        
        try:
            self.file_path = file_path
            self.tree_file = ET.parse(file_path)
            self.load_elements()
            self.file_label.configure(text=f"Fichier: {Path(file_path).name}", text_color="green")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de l'ouverture du fichier:\n{str(e)}")
    
    def load_elements(self):
        """Charger les éléments du fichier QET"""
        # Réinitialiser l'état d'affichage pour que la vue par défaut (masquée) s'applique
        self.columns_hidden = False
        
        self.elements_data = []
        
        if not self.tree_file:
            return
        
        root = self.tree_file.getroot()
        
        # Chercher tous les éléments dans le diagramme
        for diagram in root.findall('.//diagram'):
            # Extraire le numéro de folio depuis l'attribut folio et le titre du diagramme
            folio_title = diagram.get('title', '')
            folio_attr = diagram.get('folio', '')
            folio_number = self.extract_folio_number(folio_title, folio_attr)
            
            for element in diagram.findall('.//element'):
                # Ne garder que les éléments qui ont des elementInformations
                if element.find('.//elementInformations') is None:
                    continue
                
                # Filtrer les report de folio (coming_arrow et going_arrow)
                element_type = element.get('type', '')
                if '01coming_arrow.elmt' in element_type or '02going_arrow.elmt' in element_type:
                    continue
                
                # Filtrer les éléments avec une seule terminaison
                terminals = element.findall('.//terminal')
                if len(terminals) <= 1:
                    # Vérifier si le label commence par 'W' (câbles)
                    element_info = self.extract_element_info(element)
                    if not element_info['label'] or not element_info['label'].strip().upper().startswith('W'):
                        continue
                
                element_info = self.extract_element_info(element)
                
                # Ne garder que les éléments avec un label contenant au moins une lettre (A-Z)
                label = element_info['label'].strip()
                if not label or not any(c.isalpha() for c in label):
                    continue
                
                self.elements_data.append({
                    'element': element,
                    'uuid': element_info['uuid'],
                    'label': element_info['label'],
                    'quantity': element_info['quantity'],
                    'function': element_info['function'],
                    'designation': element_info['designation'],
                    'description': element_info['description'],
                    'comment': element_info['comment'],
                    'location': element_info['location'],
                    'manufacturer': element_info['manufacturer'],
                    'supplier': element_info['supplier'],
                    'manufacturer_reference': element_info['manufacturer_reference'],
                    'plant': element_info['plant'],
                    'machine_manufacturer_reference': element_info['machine_manufacturer_reference'],
                    'auxiliary1': element_info['auxiliary1'],
                    'auxiliary2': element_info['auxiliary2'],
                    'unity': element_info['unity'],
                    'folio': folio_number
                })
        
        # Tri par défaut sur le label
        self.elements_data.sort(key=lambda x: x['label'])
        
        self.populate_sheet()
        self.info_label.configure(text=f"Éléments affichés: {len(self.elements_data)}")
        
        # Appeler la fonction pour masquer les colonnes optionnelles par défaut
        self.toggle_columns()
        
    def populate_sheet(self):
        """Remplir le tableau avec les données actuelles"""
        sheet_data = []
        for item in self.elements_data:
            row = [
                item.get('uuid', ''),
                item.get('label', ''),
                item.get('manufacturer', ''),
                item.get('designation', ''),
                item.get('description', ''),
                item.get('comment', ''),
                item.get('location', ''),
                item.get('plant', ''),
                item.get('auxiliary1', ''),
                item.get('auxiliary2', ''),
                item.get('function', ''),
                item.get('manufacturer_reference', ''),
                item.get('supplier', ''),
                item.get('machine_manufacturer_reference', ''),
                item.get('unity', ''),
                item.get('quantity', ''),
                item.get('folio', '')
            ]
            sheet_data.append(row)
        
        self.sheet.set_sheet_data(sheet_data)
        
        # Masquer la colonne UUID (index 0)
        self.sheet.hide_columns([0])
        
        self.auto_resize_columns()

    def auto_resize_columns(self):
        """Redimensionner les colonnes en fonction du contenu"""
        sheet_data = self.sheet.get_sheet_data()
        
        # Si pas de données, on arrête là pour éviter les erreurs d'index
        if not sheet_data:
            return
            
        try:
            # Largeur en caractères pour chaque colonne (minimum: largeur de l'en-tête)
            col_char_widths = {i: len(str(h)) for i, h in enumerate(self.columns_order)}
            
            # Calculer la largeur maximale en caractères nécessaire pour chaque colonne
            for row in sheet_data:
                for i, cell in enumerate(row):
                    col_char_widths[i] = max(col_char_widths.get(i, 0), len(str(cell)))
            
            # Index des colonnes spéciales
            desc_idx = self.columns_order.index('DESCRIPTION')
            comment_idx = self.columns_order.index('COMMENTAIRE')
            
            # Appliquer les largeurs calculées en pixels
            for i, width_in_chars in col_char_widths.items():
                pixel_width = width_in_chars * 7 + 20  # Approximation: 7px par caractère + 20px de marge
                
                if i in [desc_idx, comment_idx]:
                    final_width = min(pixel_width, 500) # Cap à 500px pour les colonnes longues
                else:
                    final_width = min(pixel_width, 300) # Cap à 300px pour les autres
                
                self.sheet.column_width(column=i, width=max(final_width, 40)) # Largeur min de 40px
        except (ValueError, AttributeError, IndexError):
            # En cas d'erreur (colonne non trouvée ou méthode tksheet inexistante), on ne fait rien
            pass
    
    def extract_element_info(self, element):
        """Extraire les informations d'un élément"""
        uuid = element.get('uuid', 'N/A')
        element_type = element.get('type', 'N/A')
        prefix = element.get('prefix', '')
        
        # Initialiser tous les champs
        label = ''
        quantity = ''
        function = ''
        designation = ''
        description = ''
        comment = ''
        location = ''
        manufacturer = ''
        supplier = ''
        manufacturer_reference = ''
        plant = ''
        machine_manufacturer_reference = ''
        auxiliary1 = ''
        auxiliary2 = ''
        unity = ''
        
        elem_infos = element.find('.//elementInformations')
        if elem_infos is not None:
            for info in elem_infos.findall('elementInformation'):
                name = info.get('name', '')
                text = info.text or ''
                
                if name == 'label':
                    label = text
                elif name == 'quantity':
                    quantity = text
                elif name == 'function':
                    function = text
                elif name == 'designation':
                    designation = text
                elif name == 'description':
                    description = text
                elif name == 'comment':
                    comment = text
                elif name == 'location':
                    location = text
                elif name == 'manufacturer':
                    manufacturer = text
                elif name == 'supplier':
                    supplier = text
                elif name == 'manufacturer_reference':
                    manufacturer_reference = text
                elif name == 'plant':
                    plant = text
                elif name == 'machine_manufacturer_reference':
                    machine_manufacturer_reference = text
                elif name == 'auxiliary1':
                    auxiliary1 = text
                elif name == 'auxiliary2':
                    auxiliary2 = text
                elif name == 'unity':
                    unity = text
        
        # Sinon chercher dans dynamic_texts pour le label
        if not label:
            dynamic_texts = element.find('.//dynamic_texts')
            if dynamic_texts is not None:
                for text_elem in dynamic_texts.findall('.//dynamic_text'):
                    # Vérifier si c'est le label
                    info_name = text_elem.find('info_name')
                    if info_name is not None and info_name.text == 'label':
                        text_content = text_elem.find('text')
                        if text_content is not None and text_content.text:
                            label = text_content.text
                            break
        
        return {
            'uuid': uuid,
            'type': element_type.split('/')[-1] if '/' in element_type else element_type,
            'prefix': prefix,
            'label': label,
            'quantity': quantity,
            'function': function,
            'designation': designation,
            'description': description,
            'comment': comment,
            'location': location,
            'manufacturer': manufacturer,
            'supplier': supplier,
            'manufacturer_reference': manufacturer_reference,
            'plant': plant,
            'machine_manufacturer_reference': machine_manufacturer_reference,
            'auxiliary1': auxiliary1,
            'auxiliary2': auxiliary2,
            'unity': unity
        }
    
    def extract_folio_number(self, folio_title, folio_attr):
        """Extraire le numéro de folio depuis l'attribut folio ou le titre du diagramme"""
        # Priorité à l'attribut folio qui contient toujours le numéro
        if folio_attr and folio_attr.strip():
            return folio_attr.strip()
        
        # Sinon chercher un numéro dans le titre (fallback)
        if not folio_title:
            return ''
        
        # Chercher un numéro dans le titre (ex: "Folio 01", "Page 1", etc.)
        match = re.search(r'(\d+)', folio_title)
        if match:
            return match.group(1)
        
        # Si pas de numéro trouvé, retourner le titre tel quel
        return folio_title
    
    def update_xml_element(self, element, field_name, value):
        """Mettre à jour les informations de l'élément dans le XML"""
        field_key = field_name.lower()
        
        # Map inverse pour convertir les noms lisibles en noms XML
        field_xml_map = {
            'manufacturer ref': 'manufacturer_reference',
            'internal ref': 'machine_manufacturer_reference',
            'aux 1': 'auxiliary1',
            'aux 2': 'auxiliary2'
        }
        
        # Utiliser le nom XML correct
        xml_field_name = field_xml_map.get(field_key, field_key)
        
        if field_key == 'label':
            # Mettre à jour dans elementInformations
            elem_infos = element.find('.//elementInformations')
            if elem_infos is None:
                elem_infos = ET.SubElement(element, 'elementInformations')
            
            label_info = elem_infos.find("elementInformation[@name='label']")
            if label_info is None:
                label_info = ET.SubElement(elem_infos, 'elementInformation')
                label_info.set('name', 'label')
                label_info.set('show', '1')
            label_info.text = value
            
            # Aussi dans dynamic_texts
            dynamic_texts = element.find('.//dynamic_texts')
            if dynamic_texts is None:
                dynamic_texts = ET.SubElement(element, 'dynamic_texts')
            
            for text_elem in dynamic_texts.findall('.//dynamic_text'):
                info_name = text_elem.find('info_name')
                if info_name is not None and info_name.text == 'label':
                    text_content = text_elem.find('text')
                    if text_content is not None:
                        text_content.text = value
                    break
        
        else:
            # Mettre à jour dans elementInformations
            elem_infos = element.find('.//elementInformations')
            if elem_infos is None:
                elem_infos = ET.SubElement(element, 'elementInformations')
            
            info = elem_infos.find(f"elementInformation[@name='{xml_field_name}']")
            if info is None:
                info = ET.SubElement(elem_infos, 'elementInformation')
                info.set('name', xml_field_name)
                info.set('show', '1')
            info.text = value
    
    def export_csv(self):
        """Exporter les éléments en CSV"""
        sheet_data = self.sheet.get_sheet_data()
        if not sheet_data:
            messagebox.showwarning("Attention", "Aucun élément à exporter")
            return
        
        file_path = filedialog.asksaveasfilename(
            title="Exporter en CSV",
            defaultextension=".csv",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")]
        )
        
        if not file_path:
            return
        
        try:
            # Exclure la colonne UUID (index 0) pour l'export
            columns = self.columns_order[1:]
            
            with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile, delimiter=';')
                writer.writerow(columns)
                
                for row in sheet_data:
                    # Ignorer la première colonne (UUID)
                    writer.writerow(row[1:])
            
            messagebox.showinfo("Succès", f"Fichier CSV créé: {Path(file_path).name}")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de l'export:\n{str(e)}")
    
    def add_element(self):
        """Ajouter un nouvel élément"""
        if not self.tree_file:
            messagebox.showwarning("Attention", "Veuillez d'abord ouvrir un fichier")
            return
        
        messagebox.showinfo("Non implémenté", "Ajout d'éléments non implémenté dans cette version")
    
    def delete_element(self):
        """Supprimer l'élément sélectionné"""
        selected_rows = self.sheet.get_selected_rows(return_tuple=True)
        if not selected_rows:
            messagebox.showwarning("Attention", "Sélectionnez des lignes")
            return
        
        if messagebox.askyesno("Confirmation", "Êtes-vous sûr de vouloir supprimer les éléments sélectionnés ?"):
            # Récupérer les UUIDs avant suppression
            sheet_data = self.sheet.get_sheet_data()
            uuids_to_delete = [sheet_data[r][0] for r in selected_rows]
            
            # Trouver et supprimer l'élément du XML
            root = self.tree_file.getroot()
            for diagram in root.findall('.//diagram'):
                for element in diagram.findall('.//element'):
                    if element.get('uuid') in uuids_to_delete:
                        diagram.remove(element)
            
            # Retirer de la liste
            self.elements_data = [e for e in self.elements_data if e['uuid'] not in uuids_to_delete]
            
            # Supprimer du tableau
            self.sheet.delete_rows(list(selected_rows))
            
            self.info_label.configure(text=f"Éléments: {len(self.elements_data)}")
            messagebox.showinfo("Succès", "Élément supprimé")
    
    def save_file(self):
        """Sauvegarder les modifications"""
        if not self.file_path or not self.tree_file:
            messagebox.showwarning("Attention", "Aucun fichier ouvert")
            return
        
        try:
            # Récupérer toutes les données du tableau
            sheet_data = self.sheet.get_sheet_data()
            
            # Créer un mapping UUID -> Element XML pour accès rapide
            uuid_map = {}
            root = self.tree_file.getroot()
            for diagram in root.findall('.//diagram'):
                for element in diagram.findall('.//element'):
                    uuid = element.get('uuid')
                    if uuid:
                        uuid_map[uuid] = element
            
            # Mapping index colonne -> nom champ XML
            # 0: UUID, 1: LABEL, 2: FABRICANT, etc.
            col_map = {
                1: 'label', 2: 'manufacturer', 3: 'designation', 4: 'description',
                5: 'comment', 6: 'location', 7: 'plant', 8: 'auxiliary1',
                9: 'auxiliary2', 10: 'function', 11: 'manufacturer_reference',
                12: 'supplier', 13: 'machine_manufacturer_reference',
                14: 'unity', 15: 'quantity'
            }
            
            for row in sheet_data:
                uuid = row[0]
                if uuid in uuid_map:
                    element = uuid_map[uuid]
                    for col_idx, field_name in col_map.items():
                        if col_idx < len(row):
                            val = str(row[col_idx]) if row[col_idx] is not None else ""
                            self.update_xml_element(element, field_name, val)
            
            # Sauvegarder avec indentation
            self.tree_file.write(self.file_path, encoding='utf-8', xml_declaration=True)
            messagebox.showinfo("Succès", f"Fichier sauvegardé: {Path(self.file_path).name}")
        except Exception as e:
            messagebox.showerror("Erreur", f"Erreur lors de la sauvegarde:\n{str(e)}")
    
    def toggle_columns(self):
        """Masquer/Afficher les colonnes optionnelles."""
        if not self.elements_data:
            messagebox.showwarning("Attention", "Aucun fichier chargé")
            return
        
        # Colonnes à masquer/afficher
        columns_to_toggle = ['BLOC AUX 1', 'BLOC AUX 2', 'NUMERO DE COMMANDE', 'INSTALLATION', 
                           'NUMERO INTERNE', 'UNITE', 'QUANTITE']
        
        indices_to_toggle = {self.columns_order.index(c) for c in columns_to_toggle if c in self.columns_order}
        
        # Obtenir la liste de toutes les colonnes visibles possibles (tout sauf UUID à l'index 0)
        all_possible_indices = set(range(1, len(self.columns_order)))
        
        if self.columns_hidden:
            # L'état est "caché", donc on veut tout afficher.
            self.sheet.displayed_columns = sorted(list(all_possible_indices))
            self.columns_hidden = False
        else:
            # L'état est "affiché", donc on veut cacher les colonnes optionnelles.
            new_displayed = all_possible_indices - indices_to_toggle
            self.sheet.displayed_columns = sorted(list(new_displayed))
            self.columns_hidden = True
        
        self.sheet.redraw()
        self.auto_resize_columns()


if __name__ == "__main__":
    root = ctk.CTk()
    app = QETAnalyzer(root)
    root.mainloop()
