"""
Gestionnaire de raccourcis pour intercepter et remplacer les mots-clés
"""
import keyboard
import time
import re
import os
import sys
import base64
import requests
from pathlib import Path
from typing import List, Dict, Optional, Callable, Tuple
from datetime import datetime
from urllib.parse import urlparse
import hashlib

class ShortcutHandler:
    """Gère l'interception et le remplacement des raccourcis"""
    
    def __init__(self, api_client, config, on_shortcut_found: Optional[Callable] = None):
        self.api_client = api_client
        self.config = config
        self.on_shortcut_found = on_shortcut_found
        
        self.shortcuts_cache: List[Dict] = []
        self.is_enabled = config.get('enabled', True)
        self.is_replacing = False
        self.current_text = ""
        self.last_key_time = 0
        self.check_timeout = None
        
        # Buffer pour capturer le texte tapé au fur et à mesure (commence par /)
        self.text_buffer = ""
        self.is_capturing = False  # Indique si on est en train de capturer un raccourci
        self.buffer_start_time = 0
        self.max_buffer_length = 50  # Limite du buffer pour les raccourcis
        
        # Créer le dossier pour stocker les images localement
        self.images_dir = self._get_images_directory()
        os.makedirs(self.images_dir, exist_ok=True)
        
        # Charger les raccourcis au démarrage
        self.reload_shortcuts()
    
    def _get_images_directory(self) -> Path:
        """Retourne le chemin du dossier images selon le mode d'exécution"""
        if getattr(sys, 'frozen', False):
            # Mode exécutable - utiliser le dossier de l'exécutable
            base_dir = Path(sys.executable).parent
        elif Path(__file__).parent.name == 'app':
            # Mode portable
            base_dir = Path(__file__).parent.parent
        else:
            # Mode développement
            base_dir = Path(__file__).parent
        
        return base_dir / 'images'
    
    def reload_shortcuts(self):
        """Recharge les raccourcis depuis l'API"""
        client_id = self.config.get('client_id')
        group_id = self.config.get('group_id')
        self.shortcuts_cache = self.api_client.get_shortcuts(client_id, group_id)
        print(f"[MyMind] {len(self.shortcuts_cache)} raccourcis chargés")
        if self.shortcuts_cache:
            print(f"[MyMind Debug] Exemples de raccourcis: {[s.get('keyword') for s in self.shortcuts_cache[:3]]}")
    
    def set_enabled(self, enabled: bool):
        """Active ou désactive le gestionnaire"""
        self.is_enabled = enabled
        self.config.set('enabled', enabled)
    
    def has_potential_shortcut_prefix(self, text: str) -> bool:
        """Vérifie si le texte pourrait contenir un préfixe de raccourci"""
        if not text or not self.shortcuts_cache:
            return False
        
        # Vérifier si le texte se termine par un préfixe de raccourci
        # Par exemple, si on a tapé "/a", on vérifie si un raccourci commence par "/a"
        text_clean = text.rstrip()
        
        # Vérifier si le texte se termine par un préfixe de n'importe quel raccourci
        for shortcut in self.shortcuts_cache:
            keyword = shortcut.get('keyword', '')
            if not keyword:
                continue
            
            # Si le texte se termine par le début d'un mot-clé, c'est un préfixe potentiel
            if text_clean.endswith(keyword):
                return True
            # Vérifier si le texte se termine par un préfixe du mot-clé
            if len(text_clean) > 0 and keyword.startswith(text_clean):
                return True
            # Vérifier si le texte contient le mot-clé à la fin (avec des caractères avant)
            if text_clean.endswith(keyword):
                return True
        
        return False
    
    def find_shortcut(self, text: str) -> Optional[Dict]:
        """Trouve le raccourci correspondant dans le texte"""
        if not text:
            return None
        
        if not self.shortcuts_cache:
            print("[MyMind Debug] Aucun raccourci dans le cache")
            return None
        
        # Chercher le raccourci le plus long qui correspond
        matched_shortcut = None
        max_length = 0
        
        # Nettoyer le texte (enlever les espaces en fin)
        text_clean = text.rstrip()
        
        for shortcut in self.shortcuts_cache:
            keyword = shortcut.get('keyword', '')
            if not keyword:
                continue
            
            # Vérifier si le texte se termine par le mot-clé
            # Essayer avec et sans espace à la fin
            if text_clean.endswith(keyword) and len(keyword) > max_length:
                # Vérifier qu'il n'y a pas de caractère alphanumérique juste avant
                before_keyword = text_clean[:len(text_clean) - len(keyword)]
                last_char = before_keyword[-1] if before_keyword else None
                if not last_char or not re.match(r'[a-zA-Z0-9]', last_char):
                    matched_shortcut = shortcut
                    max_length = len(keyword)
        
        return matched_shortcut
    
    def replace_special_commands(self, text: str) -> str:
        """Remplace les commandes spéciales (date, heure) dans le texte"""
        if not text:
            return text
        
        now = datetime.now()
        
        # Format de date : JJ/MM/AAAA
        date_str = now.strftime('%d/%m/%Y')
        
        # Format d'heure : HH:MM
        time_str = now.strftime('%H:%M')
        
        # Format date et heure : JJ/MM/AAAA HH:MM
        date_time_str = f"{date_str} {time_str}"
        
        # Remplacer les commandes (insensible à la casse)
        result = text
        result = re.sub(r'\{date\}', date_str, result, flags=re.IGNORECASE)
        result = re.sub(r'\{heure\}|\{time\}', time_str, result, flags=re.IGNORECASE)
        result = re.sub(r'\{datetime\}', date_time_str, result, flags=re.IGNORECASE)
        
        return result
    
    def save_base64_image(self, base64_data: str) -> Optional[str]:
        """Sauvegarde une image base64 localement"""
        if not base64_data:
            return None
        
        try:
            # Détecter le format (data:image/png;base64,... ou juste base64)
            if base64_data.startswith('data:'):
                # Extraire le type MIME et les données
                header, data = base64_data.split(',', 1)
                mime_match = re.search(r'data:image/([^;]+)', header)
                if mime_match:
                    image_type = mime_match.group(1)
                else:
                    image_type = 'png'
            else:
                # Données base64 brutes
                data = base64_data
                image_type = 'png'
            
            # Décoder les données base64
            image_bytes = base64.b64decode(data)
            
            # Créer un hash des données pour le nom de fichier
            data_hash = hashlib.md5(image_bytes).hexdigest()
            file_ext = f'.{image_type}' if image_type else '.png'
            
            # Nom du fichier local
            local_filename = f"{data_hash}{file_ext}"
            local_path = self.images_dir / local_filename
            
            # Si l'image existe déjà, retourner le chemin
            if local_path.exists():
                print(f"[MyMind Debug] Image base64 deja sauvegardee: {local_path}")
                return str(local_path)
            
            # Sauvegarder l'image
            with open(local_path, 'wb') as f:
                f.write(image_bytes)
            
            print(f"[MyMind Debug] Image base64 sauvegardee: {local_path}")
            return str(local_path)
        
        except Exception as e:
            print(f"[MyMind Debug] Erreur lors de la sauvegarde de l'image base64: {e}")
            return None
    
    def download_image(self, image_url: str) -> Optional[str]:
        """Télécharge une image depuis l'URL et la stocke localement"""
        if not image_url:
            return None
        
        # Vérifier si c'est une image base64
        if image_url.startswith('data:image/'):
            return self.save_base64_image(image_url)
        
        try:
            # Créer un hash de l'URL pour le nom de fichier
            url_hash = hashlib.md5(image_url.encode()).hexdigest()
            parsed_url = urlparse(image_url)
            file_ext = os.path.splitext(parsed_url.path)[1] or '.png'
            
            # Nom du fichier local
            local_filename = f"{url_hash}{file_ext}"
            local_path = self.images_dir / local_filename
            
            # Si l'image existe déjà, retourner le chemin
            if local_path.exists():
                print(f"[MyMind Debug] Image deja telechargee: {local_path}")
                return str(local_path)
            
            # Télécharger l'image
            print(f"[MyMind Debug] Telechargement de l'image: {image_url}")
            response = requests.get(image_url, timeout=10)
            response.raise_for_status()
            
            # Sauvegarder l'image
            with open(local_path, 'wb') as f:
                f.write(response.content)
            
            print(f"[MyMind Debug] Image telechargee: {local_path}")
            return str(local_path)
        
        except Exception as e:
            print(f"[MyMind Debug] Erreur lors du telechargement de l'image: {e}")
            return None
    
    def copy_html_to_clipboard(self, html_text: str, plain_text: str = None) -> bool:
        """
        Copie du HTML formaté dans le presse-papiers Windows (préserve la mise en forme)
        Copie aussi le texte brut pour compatibilité avec les applications comme Notepad
        """
        try:
            import win32clipboard
        except ImportError as e:
            print(f"[MyMind Debug] ERREUR: pywin32 non disponible pour copier le HTML formaté!")
            print(f"[MyMind Debug] Pour installer: pip install pywin32")
            print(f"[MyMind Debug] Erreur detaillee: {e}")
            return False
        
        try:
            # Extraire le texte brut du HTML si non fourni
            if plain_text is None:
                # Utiliser strip_html pour extraire le texte brut
                plain_text = self.strip_html(html_text)
                # Normaliser les sauts de ligne
                plain_text = plain_text.replace('\r\n', '\n').replace('\r', '\n')
                plain_text = re.sub(r'\n{3,}', '\n\n', plain_text)
            
            # Format HTML du presse-papiers Windows nécessite un en-tête spécial
            # Format: Version:0.9\r\nStartHTML:0000000000\r\nEndHTML:0000000000\r\nStartFragment:0000000000\r\nEndFragment:0000000000\r\n<html>...fragment...</html>
            
            # Créer le fragment HTML
            html_body = f"<html><body>{html_text}</body></html>"
            
            # Créer l'en-tête avec des placeholders (10 chiffres pour chaque position)
            header_template = "Version:0.9\r\nStartHTML:0000000000\r\nEndHTML:0000000000\r\nStartFragment:0000000000\r\nEndFragment:0000000000\r\n"
            
            # Calculer les positions réelles
            start_html = len(header_template)
            start_fragment = start_html + len("<html><body>")
            end_fragment = start_fragment + len(html_text)
            end_html = end_fragment + len("</body></html>")
            
            # Créer l'en-tête avec les positions réelles
            header = f"Version:0.9\r\nStartHTML:{start_html:010d}\r\nEndHTML:{end_html:010d}\r\nStartFragment:{start_fragment:010d}\r\nEndFragment:{end_fragment:010d}\r\n"
            
            # Assembler le tout
            clipboard_data = header + html_body
            
            # Copier dans le presse-papiers avec plusieurs formats
            win32clipboard.OpenClipboard()
            win32clipboard.EmptyClipboard()
            
            # Copier le format HTML (pour Word/Outlook/etc.)
            win32clipboard.SetClipboardData(win32clipboard.RegisterClipboardFormat("HTML Format"), clipboard_data.encode('utf-8'))
            
            # Copier aussi le texte brut (pour Notepad/etc.)
            win32clipboard.SetClipboardData(win32clipboard.CF_TEXT, plain_text.encode('utf-8'))
            
            # Copier aussi en Unicode (CF_UNICODETEXT = 13)
            win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, plain_text)
            
            win32clipboard.CloseClipboard()
            
            print(f"[MyMind Debug] HTML formate et texte brut copies dans le presse-papiers (HTML: {len(html_text)}, texte: {len(plain_text)})")
            return True
        except Exception as e:
            print(f"[MyMind Debug] Erreur lors de la copie du HTML formate: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def copy_image_to_clipboard(self, image_path: str) -> bool:
        """Copie une image dans le presse-papiers Windows"""
        try:
            from PIL import Image
            import win32clipboard
            from io import BytesIO
        except ImportError as e:
            print(f"[MyMind Debug] ERREUR: Pillow ou pywin32 non disponible!")
            print(f"[MyMind Debug] Pour installer: pip install Pillow pywin32")
            print(f"[MyMind Debug] Erreur detaillee: {e}")
            return False
        
        try:
            # Ouvrir l'image
            img = Image.open(image_path)
            
            # Convertir en format compatible avec le presse-papiers Windows
            output = BytesIO()
            img.convert("RGB").save(output, "BMP")
            data = output.getvalue()[14:]  # Enlever l'en-tête BMP
            output.close()
            
            # Copier dans le presse-papiers
            win32clipboard.OpenClipboard()
            win32clipboard.EmptyClipboard()
            win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
            win32clipboard.CloseClipboard()
            
            print(f"[MyMind Debug] Image copiee dans le presse-papiers: {image_path}")
            return True
        except Exception as e:
            print(f"[MyMind Debug] Erreur lors de la copie de l'image: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def clean_html_preserve_formatting(self, html_text: str) -> str:
        """
        Nettoie le HTML en préservant la mise en forme (gras, italique, couleur, etc.)
        Retire seulement les attributs de <p> mais garde toutes les balises de formatage
        """
        if not html_text:
            return ""
        
        processed_html = html_text
        
        # Retirer les attributs des balises <p> (comme class="MsoNormal") mais garder <p></p>
        processed_html = re.sub(r'<p[^>]*>', '<p>', processed_html, flags=re.IGNORECASE)
        
        # Préserver les espaces vides intentionnels
        processed_html = re.sub(r'</p>\s*<p>\s*<br\s*/?>\s*</p>\s*<p>', '</p><p><br></p><p>', processed_html, flags=re.IGNORECASE)
        
        # Supprimer les balises <p> vides complètement (sans <br>)
        processed_html = re.sub(r'<p>\s*</p>', '', processed_html, flags=re.IGNORECASE)
        
        return processed_html
    
    def process_html_with_images(self, html_text: str, shortcut: Dict) -> Tuple[str, str, List[Tuple[int, str]]]:
        """
        Traite le HTML en téléchargeant les images et en convertissant en texte formaté
        Retourne (texte formaté, HTML nettoyé avec mise en forme, liste de positions d'images)
        """
        if not html_text:
            return "", "", []
        
        try:
            import html2text
        except ImportError:
            print("[MyMind Debug] html2text non disponible, utilisation de la conversion simple")
            cleaned_html = self.clean_html_preserve_formatting(html_text)
            return self.strip_html(html_text), cleaned_html, []
        
        # Télécharger toutes les images trouvées dans le HTML
        processed_html = html_text
        image_positions = []  # Liste de (position_dans_html, chemin_local)
        
        # Trouver toutes les balises <img> dans le HTML (y compris avec data URI)
        img_tag_pattern = r'<img[^>]*>'
        img_matches = list(re.finditer(img_tag_pattern, processed_html, re.IGNORECASE))
        
        # Traiter les matches en ordre inverse pour préserver les positions lors du remplacement
        for match in reversed(img_matches):
            img_tag = match.group(0)
            # Extraire le src
            src_match = re.search(r'src\s*=\s*(["\']?)([^\s>]+?)\1', img_tag, re.IGNORECASE)
            if src_match:
                img_url = src_match.group(2)
                if img_url:
                    local_path = self.download_image(img_url)
                    if local_path:
                        # Stocker la position dans le HTML original (avant conversion)
                        position_in_html = match.start()
                        image_positions.append((position_in_html, local_path))
                        # Remplacer la balise <img> par un marqueur temporaire
                        marker = f"__IMAGE_MARKER_{len(image_positions)-1}__"
                        start, end = match.span()
                        processed_html = processed_html[:start] + marker + processed_html[end:]
        
        # Télécharger aussi l'image du raccourci si elle existe et n'est pas déjà dans le HTML
        image_url = shortcut.get('image_url')
        if image_url and image_url not in html_text:
            local_image_path = self.download_image(image_url)
            if local_image_path:
                # Ajouter un marqueur à la fin pour l'image du raccourci
                marker = f"__IMAGE_MARKER_{len(image_positions)}__"
                processed_html += marker
                image_positions.append((len(processed_html), local_image_path))
        
        # Nettoyer le HTML en préservant la mise en forme
        cleaned_html = self.clean_html_preserve_formatting(processed_html)
        
        # Utiliser le HTML nettoyé pour la conversion en texte
        processed_html = cleaned_html
        
        # Convertir le HTML en texte formaté avec html2text
        h = html2text.HTML2Text()
        h.ignore_links = False
        h.ignore_images = True
        h.body_width = 0
        h.unicode_snob = True
        # Essayer de configurer pour des sauts de ligne simples (si l'option existe)
        try:
            h.single_line_break = True
        except AttributeError:
            # L'option n'existe pas dans cette version, on post-traitera après
            pass
        
        # Convertir
        formatted_text = h.handle(processed_html)
        
        # Trouver les positions des marqueurs dans le texte converti
        image_positions_in_text = []
        marker_pattern = r'__IMAGE_MARKER_(\d+)__'
        
        # Trouver tous les marqueurs avec leurs positions (en ordre croissant)
        markers_found = []
        for marker in re.finditer(marker_pattern, formatted_text):
            marker_num = int(marker.group(1))
            if marker_num < len(image_positions):
                position = marker.start()
                image_path = image_positions[marker_num][1]
                marker_length = len(marker.group(0))
                markers_found.append((position, image_path, marker_length))
        
        # Supprimer les marqueurs du texte en ordre inverse pour préserver les positions
        markers_found.sort(key=lambda x: x[0], reverse=True)
        offset = 0
        for position, image_path, marker_length in markers_found:
            # Position ajustée après suppression des marqueurs précédents
            adjusted_position = position - offset
            image_positions_in_text.append((adjusted_position, image_path))
            # Supprimer le marqueur
            formatted_text = formatted_text[:position] + formatted_text[position + marker_length:]
            offset += marker_length
        
        # Trier par position (du début à la fin)
        image_positions_in_text.sort(key=lambda x: x[0])
        
        # Normaliser les sauts de ligne pour éviter les triples et plus, mais garder les simples et doubles
        try:
            if formatted_text:
                # Remplacer tous les types de sauts de ligne par \n
                formatted_text = formatted_text.replace('\r\n', '\n').replace('\r', '\n')
                # Réduire les sauts de ligne multiples (3+ sauts de ligne) à 2 sauts de ligne maximum
                # Cela préserve les sauts de ligne simples (\n) et doubles (\n\n) pour la structure
                formatted_text = re.sub(r'\n{3,}', '\n\n', formatted_text)
                # Réduire les doubles sauts de ligne consécutifs entre paragraphes en simples
                # Mais garder les doubles sauts de ligne qui séparent vraiment des sections
                # On remplace \n\n par \n seulement s'il y a beaucoup de \n\n consécutifs
                # (on garde les doubles sauts de ligne normaux entre paragraphes)
                # En fait, on garde les doubles sauts de ligne car ils séparent bien les paragraphes
        except Exception as e:
            print(f"[MyMind Debug] Erreur lors de la normalisation des sauts de ligne: {e}")
            # En cas d'erreur, retourner le texte tel quel
        
        return formatted_text.strip() if formatted_text else "", cleaned_html, image_positions_in_text
    
    def strip_html(self, html_text: str) -> str:
        """Extrait le texte brut d'un HTML (méthode simple de fallback)"""
        # Simple extraction de texte (pour les champs texte simples)
        # Les champs texte ne supportent généralement pas le HTML
        import re
        text = re.sub(r'<[^>]+>', '', html_text)
        # Décoder les entités HTML simples
        text = text.replace('&nbsp;', ' ')
        text = text.replace('&lt;', '<')
        text = text.replace('&gt;', '>')
        text = text.replace('&amp;', '&')
        return text.strip()
    
    def replace_shortcut(self, shortcut: Dict, text_before_keyword: str):
        """Remplace le raccourci par le texte correspondant"""
        if self.is_replacing:
            print("[MyMind Debug] Remplacement deja en cours, ignore")
            return
        
        self.is_replacing = True
        
        try:
            import pyperclip
            
            # Récupérer le texte original du raccourci
            original_text = shortcut.get('text', '')
            keyword = shortcut.get('keyword', '')
            
            print(f"[MyMind Debug] === DEBUT REMPLACEMENT ===")
            print(f"[MyMind Debug] Mot-cle: '{keyword}'")
            print(f"[MyMind Debug] Texte original (longueur: {len(original_text)}): '{original_text[:100]}...'")
            
            # Traiter le texte du raccourci
            processed_text = self.replace_special_commands(original_text)
            print(f"[MyMind Debug] Apres commandes speciales (longueur: {len(processed_text)}): '{processed_text[:100]}...'")
            
            # Pour les applications desktop, convertir le HTML en texte formaté
            # et télécharger les images localement
            had_html = self.contains_html(processed_text)
            has_image = shortcut.get('image_url') is not None
            image_positions = []
            formatted_html = None  # HTML avec mise en forme préservée
            
            if had_html or has_image:
                print(f"[MyMind Debug] Texte contient du HTML ou une image, conversion en texte formate...")
                processed_text, formatted_html, image_positions = self.process_html_with_images(processed_text, shortcut)
                print(f"[MyMind Debug] Apres conversion HTML/Image (longueur texte: {len(processed_text)}, longueur HTML: {len(formatted_html) if formatted_html else 0})")
                print(f"[MyMind Debug] {len(image_positions)} image(s) a inserer")
            else:
                # Normaliser les sauts de ligne même pour le texte sans HTML
                try:
                    if processed_text:
                        # Remplacer tous les types de sauts de ligne par \n
                        processed_text = processed_text.replace('\r\n', '\n').replace('\r', '\n')
                        # Réduire les sauts de ligne multiples (3+ sauts de ligne) à 2 sauts de ligne maximum
                        # Cela préserve les sauts de ligne simples (\n) et doubles (\n\n) pour la structure
                        processed_text = re.sub(r'\n{3,}', '\n\n', processed_text)
                except Exception as e:
                    print(f"[MyMind Debug] Erreur lors de la normalisation des sauts de ligne (texte sans HTML): {e}")
                    # En cas d'erreur, continuer avec le texte original
            
            print(f"[MyMind Debug] Texte final a inserer (longueur: {len(processed_text)}): '{processed_text}'")
            
            # Calculer le nombre de caractères à supprimer (le mot-clé)
            chars_to_delete = len(keyword)
            print(f"[MyMind Debug] Nombre de caracteres a supprimer: {chars_to_delete}")
            
            # Sauvegarder le presse-papiers actuel
            old_clipboard = pyperclip.paste()
            print(f"[MyMind Debug] Presse-papiers actuel sauvegarde (longueur: {len(old_clipboard)}): '{old_clipboard[:50]}...'")
            
            # S'assurer que le curseur est à la fin (après avoir récupéré le texte)
            # Appuyer sur End pour aller à la fin de la ligne
            print(f"[MyMind Debug] Deplacement du curseur a la fin...")
            keyboard.press_and_release('end')
            time.sleep(0.1)
            
            # Supprimer le mot-clé en appuyant sur Backspace
            print(f"[MyMind Debug] Suppression de {chars_to_delete} caracteres (mot-cle)...")
            for i in range(chars_to_delete):
                keyboard.press_and_release('backspace')
                time.sleep(0.03)  # Délai un peu plus long pour la stabilité
            print(f"[MyMind Debug] Suppression terminee")
            
            # Si on a des images, insérer le texte par parties avec les images
            if image_positions:
                print(f"[MyMind Debug] Insertion du texte avec images...")
                last_pos = 0
                for img_pos, img_path in image_positions:
                    # Insérer le texte jusqu'à la position de l'image
                    if img_pos > last_pos:
                        text_part = processed_text[last_pos:img_pos]
                        if text_part:
                            pyperclip.copy(text_part)
                            time.sleep(0.1)
                            keyboard.press_and_release('ctrl+v')
                            time.sleep(0.2)
                    
                    # Copier et coller l'image
                    print(f"[MyMind Debug] Insertion de l'image: {img_path}")
                    if self.copy_image_to_clipboard(img_path):
                        time.sleep(0.2)
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.3)  # Attendre que l'image soit insérée
                    else:
                        print(f"[MyMind Debug] ATTENTION: Impossible de copier l'image, insertion ignoree")
                        # Ajouter une note textuelle à la place
                        note = f"\n[Image: {os.path.basename(img_path)}]"
                        pyperclip.copy(note)
                        time.sleep(0.1)
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.2)
                    
                    last_pos = img_pos
                
                # Insérer le reste du texte
                if last_pos < len(processed_text):
                    text_part = processed_text[last_pos:]
                    if text_part:
                        pyperclip.copy(text_part)
                        time.sleep(0.1)
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.2)
            else:
                # Pas d'images, insertion du texte ou du HTML formaté
                if formatted_html and had_html:
                    # Utiliser le HTML formaté pour préserver la mise en forme
                    # Le HTML est copié avec le texte brut pour compatibilité (Notepad, etc.)
                    print(f"[MyMind Debug] Copie du HTML formate dans le presse-papiers (preservation de la mise en forme)...")
                    if self.copy_html_to_clipboard(formatted_html, processed_text):
                        time.sleep(0.15)  # Attendre que le presse-papiers soit mis à jour
                        # Insérer avec Ctrl+V (l'application choisira le format qu'elle supporte)
                        print(f"[MyMind Debug] Insertion avec Ctrl+V (HTML pour Word/Outlook, texte brut pour Notepad)...")
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.3)  # Attendre que le texte soit inséré
                    else:
                        # Fallback sur le texte brut si la copie HTML échoue
                        print(f"[MyMind Debug] Echec de la copie HTML, utilisation du texte brut...")
                        pyperclip.copy(processed_text)
                        time.sleep(0.15)
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.3)
                else:
                    # Pas de HTML, insertion normale du texte
                    print(f"[MyMind Debug] Copie du texte dans le presse-papiers...")
                    print(f"[MyMind Debug] Texte a copier: '{processed_text}'")
                    pyperclip.copy(processed_text)
                    time.sleep(0.15)  # Attendre que le presse-papiers soit mis à jour
                    
                    # Insérer le texte avec Ctrl+V (plus fiable que keyboard.write())
                    print(f"[MyMind Debug] Insertion du texte avec Ctrl+V...")
                    keyboard.press_and_release('ctrl+v')
                    time.sleep(0.3)  # Attendre que le texte soit inséré
            
            print(f"[MyMind Debug] Insertion terminee")
            
            # Restaurer le presse-papiers
            try:
                pyperclip.copy(old_clipboard)
                print(f"[MyMind Debug] Presse-papiers restaure")
            except Exception as e:
                print(f"[MyMind Debug] Erreur lors de la restauration du presse-papiers: {e}")
            
            print(f"[MyMind Debug] === FIN REMPLACEMENT ===")
            
            if self.on_shortcut_found:
                self.on_shortcut_found(shortcut)
        
        except Exception as e:
            print(f"[MyMind Debug] ERREUR lors du remplacement du raccourci: {e}")
            import traceback
            traceback.print_exc()
        finally:
            time.sleep(0.1)
            self.is_replacing = False
            print(f"[MyMind Debug] Flag is_replacing remis a False")
    
    def contains_html(self, text: str) -> bool:
        """Vérifie si le texte contient du HTML"""
        if not text:
            return False
        return bool(re.search(r'<[^>]+>', text))
    
    def get_current_text_from_field(self) -> str:
        """Récupère le texte actuel du champ de saisie actif"""
        try:
            import pyperclip
            print(f"[MyMind Debug] === DEBUT RECUPERATION TEXTE ===")
            
            # Sauvegarder le presse-papiers actuel
            old_clipboard = pyperclip.paste()
            print(f"[MyMind Debug] Presse-papiers initial: '{old_clipboard[:50]}...' (longueur: {len(old_clipboard)})")
            
            # Méthode améliorée : utiliser plusieurs tentatives
            current_text = ""
            
            # Tentative 1: Sélectionner depuis le curseur jusqu'au début de la ligne
            print(f"[MyMind Debug] Tentative 1: Shift+Home (depuis curseur jusqu'au debut de ligne)...")
            try:
                keyboard.press_and_release('shift+home')
                time.sleep(0.2)
                keyboard.press_and_release('ctrl+c')
                time.sleep(0.3)  # Délai plus long
                current_text = pyperclip.paste()
                print(f"[MyMind Debug] Tentative 1 resultat: '{current_text[:100]}...' (longueur: {len(current_text)})")
                
                # Si on a récupéré du texte, l'utiliser
                if current_text and len(current_text.strip()) > 0:
                    print(f"[MyMind Debug] Tentative 1 reussie, utilisation de ce texte")
                    # Restaurer le presse-papiers et retourner
                    try:
                        pyperclip.copy(old_clipboard)
                    except:
                        pass
                    print(f"[MyMind Debug] === FIN RECUPERATION TEXTE (tentative 1) ===")
                    return current_text
                
                # Annuler la sélection
                print(f"[MyMind Debug] Tentative 1 echouee, annulation de la selection...")
                keyboard.press_and_release('right')
                time.sleep(0.1)
            except Exception as e:
                print(f"[MyMind Debug] Erreur tentative 1: {e}")
            
            # Tentative 2: Sélectionner tout le champ (Ctrl+A)
            print(f"[MyMind Debug] Tentative 2: Ctrl+A (selectionner tout)...")
            try:
                keyboard.press_and_release('ctrl+a')
                time.sleep(0.2)
                keyboard.press_and_release('ctrl+c')
                time.sleep(0.3)
                current_text = pyperclip.paste()
                print(f"[MyMind Debug] Tentative 2 resultat: '{current_text[:100]}...' (longueur: {len(current_text)})")
                
                if current_text and len(current_text.strip()) > 0:
                    print(f"[MyMind Debug] Tentative 2 reussie, utilisation de ce texte")
                    # Restaurer le presse-papiers
                    try:
                        pyperclip.copy(old_clipboard)
                    except:
                        pass
                    print(f"[MyMind Debug] === FIN RECUPERATION TEXTE (tentative 2) ===")
                    return current_text
                else:
                    print(f"[MyMind Debug] Tentative 2 echouee (texte vide)")
            except Exception as e:
                print(f"[MyMind Debug] Erreur tentative 2: {e}")
            
            # Si aucune méthode n'a fonctionné, restaurer le presse-papiers
            print(f"[MyMind Debug] Aucune tentative n'a reussi, retour texte vide")
            try:
                pyperclip.copy(old_clipboard)
            except:
                pass
            
            print(f"[MyMind Debug] === FIN RECUPERATION TEXTE (echec) ===")
            return current_text or ""
        except Exception as e:
            print(f"[MyMind Debug] ERREUR lors de la recuperation du texte: {e}")
            import traceback
            traceback.print_exc()
            print(f"[MyMind Debug] === FIN RECUPERATION TEXTE (exception) ===")
            return ""
    
    def clear_buffer(self):
        """Vide le buffer de texte"""
        self.text_buffer = ""
        self.is_capturing = False
        self.buffer_start_time = 0
    
    def key_to_char(self, key_name: str, is_shift: bool = False) -> Optional[str]:
        """Convertit un nom de touche en caractère"""
        # Mapping des touches aux caractères
        key_map = {
            'space': ' ',
            'enter': '\n',
            'tab': '\t',
            'slash': '/',
            '/': '/',
            'backslash': '\\',
            'minus': '-',
            '-': '-',
            'equals': '=',
            '=': '=',
            'comma': ',',
            ',': ',',
            'period': '.',
            '.': '.',
            'semicolon': ';',
            ';': ';',
            'quote': "'",
            "'": "'",
            'left bracket': '[',
            '[': '[',
            'right bracket': ']',
            ']': ']',
        }
        
        # Si c'est une touche spéciale, retourner le caractère correspondant
        if key_name in key_map:
            return key_map[key_name]
        
        # Si c'est une lettre
        if len(key_name) == 1 and key_name.isalpha():
            return key_name.upper() if is_shift else key_name.lower()
        
        # Si c'est un chiffre
        if key_name.startswith('digit ') or key_name in [str(i) for i in range(10)]:
            if key_name.startswith('digit '):
                num = key_name.replace('digit ', '')
            else:
                num = key_name
            if is_shift:
                # Chiffres avec Shift
                shift_map = {'1': '!', '2': '@', '3': '#', '4': '$', '5': '%', 
                           '6': '^', '7': '&', '8': '*', '9': '(', '0': ')'}
                return shift_map.get(num, num)
            return num
        
        return None
    
    def get_shortcut_prefixes(self) -> set:
        """Récupère tous les premiers caractères des raccourcis"""
        prefixes = set()
        for shortcut in self.shortcuts_cache:
            keyword = shortcut.get('keyword', '')
            if keyword and len(keyword) > 0:
                prefixes.add(keyword[0].lower())
        return prefixes
    
    def on_key_press(self, event):
        """Gère les frappes de touches"""
        if not self.is_enabled or self.is_replacing:
            return
        
        key_name = event.name
        
        # Gérer le backspace pendant la capture
        if key_name == 'backspace' and self.is_capturing:
            if len(self.text_buffer) > 0:
                self.text_buffer = self.text_buffer[:-1]
                print(f"[MyMind Debug] Backspace, buffer: '{self.text_buffer}'")
                if len(self.text_buffer) == 0:
                    self.clear_buffer()
            # Laisser le backspace s'exécuter normalement
            return
        
        # Ignorer les touches spéciales (sauf celles qui déclenchent le remplacement)
        modifier_keys = ['ctrl', 'alt', 'shift', 'cmd', 'windows', 'left', 'right', 'up', 'down', 'home', 'end', 'page up', 'page down']
        if key_name in modifier_keys:
            # Si on est en train de capturer et qu'on appuie sur une touche de navigation, arrêter la capture
            if self.is_capturing and key_name in ['left', 'right', 'up', 'down', 'home', 'end']:
                self.clear_buffer()
            return
        
        # Détecter si Shift est pressé (pour les majuscules)
        is_shift = keyboard.is_pressed('shift')
        
        # Convertir la touche en caractère
        char = self.key_to_char(key_name, is_shift)
        
        # Récupérer les préfixes de raccourcis (premiers caractères)
        shortcut_prefixes = self.get_shortcut_prefixes()
        
        # Si on n'est pas en train de capturer et qu'on tape un préfixe de raccourci, commencer la capture
        if not self.is_capturing and char and char.lower() in shortcut_prefixes:
            print(f"[MyMind Debug] Prefixe detecte: '{char}', debut de la capture")
            self.is_capturing = True
            self.text_buffer = char
            self.buffer_start_time = time.time()
            return  # Laisser le caractère s'afficher normalement
        
        # Si on est en train de capturer
        if self.is_capturing:
            # Vérifier si c'est une touche de déclenchement
            trigger_keys = self.config.get('trigger_keys', ['space', 'enter', 'tab'])
            is_trigger = key_name in trigger_keys
            
            if is_trigger:
                # Vérifier si le buffer correspond à un raccourci
                print(f"[MyMind Debug] Touche de declenchement detectee, buffer: '{self.text_buffer}'")
                
                # Trouver le raccourci correspondant au buffer
                shortcut = None
                for s in self.shortcuts_cache:
                    if s.get('keyword', '').lower() == self.text_buffer.lower():
                        shortcut = s
                        break
                
                if shortcut:
                    print(f"[MyMind Debug] ===== RACCOURCI TROUVE: '{shortcut.get('keyword')}' =====")
                    
                    # Supprimer la touche de déclenchement qui vient d'être ajoutée
                    if key_name in ['space', 'tab']:
                        keyboard.press_and_release('backspace')
                        time.sleep(0.05)
                    
                    # Supprimer le mot-clé qui a été tapé (en utilisant backspace)
                    keyword_length = len(self.text_buffer)
                    for _ in range(keyword_length):
                        keyboard.press_and_release('backspace')
                        time.sleep(0.02)
                    
                    # Remplacer le raccourci
                    self.replace_shortcut(shortcut, "")
                    
                    # Si c'était un espace ou tab, l'ajouter après
                    if key_name in ['space', 'tab']:
                        time.sleep(0.1)
                        import pyperclip
                        old_clip = pyperclip.paste()
                        pyperclip.copy(' ')
                        keyboard.press_and_release('ctrl+v')
                        time.sleep(0.05)
                        try:
                            pyperclip.copy(old_clip)
                        except:
                            pass
                    
                    # Vider le buffer
                    self.clear_buffer()
                    return  # Empêcher la touche de déclenchement de s'exécuter
                else:
                    # Pas de raccourci trouvé, vider le buffer et laisser la touche s'exécuter
                    print(f"[MyMind Debug] Aucun raccourci trouve pour '{self.text_buffer}', vider le buffer")
                    self.clear_buffer()
                    return  # Laisser la touche de déclenchement s'exécuter normalement
            
            # Si ce n'est pas une touche de déclenchement, ajouter le caractère au buffer
            # On continue à capturer jusqu'à la touche de déclenchement
            if char:
                # Ajouter le caractère au buffer
                self.text_buffer += char
                print(f"[MyMind Debug] Buffer mis a jour: '{self.text_buffer}'")
                # Laisser le caractère s'afficher normalement
                
                # Vérifier si le buffer dépasse la longueur maximale d'un raccourci
                max_keyword_length = max([len(s.get('keyword', '')) for s in self.shortcuts_cache] + [50])
                if len(self.text_buffer) > max_keyword_length:
                    print(f"[MyMind Debug] Buffer trop long, vider le buffer")
                    self.clear_buffer()
            else:
                # Caractère non reconnu ou touche spéciale, vider le buffer seulement si ce n'est pas une touche de navigation
                if key_name not in ['left', 'right', 'up', 'down', 'home', 'end']:
                    print(f"[MyMind Debug] Caractere non reconnu ou touche speciale, vider le buffer")
                    self.clear_buffer()
        
        # Si on n'est pas en train de capturer et que ce n'est pas un préfixe, ne rien faire
        # (le caractère s'affichera normalement)
    
    def start(self):
        """Démarre l'écoute des raccourcis"""
        # Enregistrer le hook global avec suppression optionnelle
        # Note: keyboard.on_press ne supporte pas suppress directement
        # On gère le blocage manuellement dans on_key_press
        keyboard.on_press(self.on_key_press)
        print("[MyMind] Gestionnaire de raccourcis demarre")
    
    def stop(self):
        """Arrête l'écoute des raccourcis"""
        keyboard.unhook_all()
        print("[MyMind] Gestionnaire de raccourcis arrêté")

