"""
Orchestrateur - Boucle cognitive principale pour RPA Vision V2
Implémente le paradigme Observer → Réfléchir → Agir → Apprendre
"""

import time
import signal
import threading
from typing import Dict, Any, Optional, List
from datetime import datetime
import numpy as np

from .learning_manager import LearningManager
from .utils.vision_utils import VisionUtils
from .llm_manager import LLMManager
from .utils.image_utils import capture_screen, get_active_window
from .utils.input_utils import InputUtils
from .logger import Logger
from .models import Action, Detection
from .config import get_config, get_performance_config
from .whitelist_manager import WhitelistManager
from .event_capture import EventCapture
from .vision_analysis import VisionAnalysis
from .vision_search import VisionSearch
from .suggestion_manager import SuggestionManager
from .task_replay import TaskReplayEngine
from .session_manager import SessionManager
from .workflow_detector import WorkflowDetector
from .enhanced_workflow_matcher import EnhancedWorkflowMatcher
from .multimodal_embedding_manager import MultiModalEmbeddingManager
from .enriched_screen_capture import EnrichedScreenCapture

# New embedding system
from .embedders import (
    EmbeddingManager as NewEmbeddingManager,
    FAISSIndex,
    LightweightFineTuner
)
from PIL import Image
import cv2
import os


class Orchestrator:
    """
    Orchestrateur principal implémentant la boucle cognitive
    Observer → Réfléchir → Agir → Apprendre → Répéter
    """
    
    def __init__(
        self,
        learning_manager: LearningManager,
        vision_utils: VisionUtils,
        llm_manager: LLMManager,
        logger: Logger,
        gui=None,
        config: Optional[Dict[str, Any]] = None,
        whitelist_manager: Optional[WhitelistManager] = None,
        input_utils: Optional[InputUtils] = None
    ):
        """
        Initialise l'orchestrateur avec tous les composants nécessaires
        
        Args:
            learning_manager: Gestionnaire d'apprentissage
            vision_utils: Utilitaires de vision pour détection UI
            llm_manager: Gestionnaire LLM pour raisonnement
            logger: Logger pour journalisation
            gui: Interface graphique (optionnel)
            config: Configuration globale (utilise CONFIG par défaut si None)
            whitelist_manager: Gestionnaire de liste blanche (crée un nouveau si None)
            input_utils: Utilitaires d'entrée (crée un nouveau si None)
        """
        self.learning_manager = learning_manager
        self.vision = vision_utils
        self.llm = llm_manager
        self.logger = logger
        self.gui = gui
        self.config = config or get_config()
        self.perf_config = get_performance_config()
        
        # Utilitaires d'entrée pour exécution d'actions
        self.input_utils = input_utils or InputUtils(logger, self.config)
        
        # État de la boucle cognitive
        self.running = False
        self.paused = False
        self._stop_event = threading.Event()
        self._pause_event = threading.Event()
        
        # Contexte actuel
        self.current_context: Dict[str, Any] = {}
        self.current_frame: Optional[np.ndarray] = None
        self.current_window: str = ""
        
        # Gestionnaire de liste blanche
        self.whitelist_manager = whitelist_manager or WhitelistManager(logger=logger)
        self.enforce_whitelist = self.config["security"].get("enforce_whitelist", True)
        
        # Gestionnaire de sessions pour segmentation d'actions
        self.session_manager = SessionManager(
            logger=logger,
            config=self.config
        )
        self.session_manager.on_session_completed = self._on_session_completed
        
        # Détecteur de workflows pour patterns répétitifs
        self.workflow_detector = WorkflowDetector(
            logger=logger,
            config=self.config
        )
        self.workflow_detector.on_workflow_detected = self._on_workflow_detected
        
        # Capture d'événements utilisateur (utilise nos instances)
        self.event_capture = EventCapture(
            logger=logger,
            config=self.config
        )
        # Remplacer les instances par défaut par les nôtres
        self.event_capture.session_manager = self.session_manager
        self.event_capture.workflow_detector = self.workflow_detector
        self.event_capture.register_pattern_callback(self._on_pattern_detected)
        
        # Analyse visuelle des actions
        self.vision_analyzer = VisionAnalysis(
            embeddings_manager=learning_manager.embeddings_manager,
            vision_utils=vision_utils,
            llm_manager=llm_manager,
            logger=logger
        )
        
        # Recherche visuelle pour le rejeu
        self.vision_search = VisionSearch(
            embeddings_manager=learning_manager.embeddings_manager,
            logger=logger
        )
        
        # Nouveau système d'embeddings
        self._init_new_embedding_system()
        
        # Gestionnaire d'embeddings multi-modaux pour le matching amélioré
        self.multimodal_manager = MultiModalEmbeddingManager(
            logger=logger,
            data_dir=self.config.get("data_dir", "data")
        )
        
        # Système de capture d'écran enrichi (UI Element Detection)
        ui_detection_mode = self.config.get("ui_detection", {}).get("mode", "light")
        self.enriched_capture = EnrichedScreenCapture(
            logger=logger,
            data_dir=self.config.get("data_dir", "data"),
            mode=ui_detection_mode,
            config={
                "ui_detector": self.config.get("ui_detection", {}).get("detector", {}),
                "multimodal_embedding": self.config.get("multimodal_embedding", {}),
                "enhanced_matcher": self.config.get("enhanced_matcher", {})
            }
        )
        logger.log_action({
            "action": "enriched_capture_initialized",
            "mode": ui_detection_mode
        })
        
        # Matcher de workflows amélioré avec embeddings multi-modaux
        matcher_config = {
            "screen_weight": self.config.get("enhanced_matcher", {}).get("screen_weight", 0.6),
            "elements_weight": self.config.get("enhanced_matcher", {}).get("elements_weight", 0.4),
            "min_similarity_threshold": self.config.get("enhanced_matcher", {}).get("min_similarity_threshold", 0.3),
            "min_confidence_threshold": self.config.get("enhanced_matcher", {}).get("min_confidence_threshold", 0.5)
        }
        self.enhanced_matcher = EnhancedWorkflowMatcher(
            multimodal_manager=self.multimodal_manager,
            logger=logger,
            config=matcher_config
        )
        
        # Gestionnaire de suggestions (Mode Assisté)
        self.suggestion_manager = SuggestionManager(
            learning_manager=learning_manager,
            embeddings_manager=learning_manager.embeddings_manager,
            logger=logger,
            config=self.config
        )
        
        # Moteur de rejeu pour exécution des suggestions
        self.replay_engine = TaskReplayEngine(
            learning_manager=learning_manager,
            embeddings_manager=learning_manager.embeddings_manager,
            vision_utils=vision_utils,
            input_utils=self.input_utils,
            logger=logger,
            config=self.config
        )
        
        # Connecter les callbacks du suggestion_manager
        self.suggestion_manager.on_suggestion_created = self._on_suggestion_created
        self.suggestion_manager.on_suggestion_accepted = self._on_suggestion_accepted
        self.suggestion_manager.on_suggestion_rejected = self._on_suggestion_rejected
        self.suggestion_manager.on_suggestion_timeout = self._on_suggestion_timeout
        
        # Métriques de performance
        self.metrics = {
            "total_cycles": 0,
            "avg_latency_ms": 0.0,
            "detections_count": 0,
            "actions_executed": 0,
            "actions_suggested": 0,
            "sessions_created": 0,
            "workflows_detected": 0,
        }
        
        # Configurer l'arrêt d'urgence (Ctrl+Pause)
        self._setup_emergency_stop()
        
        # Mode progressif (démarre en shadow, propose assist après patterns)
        self._progressive_mode = False
        self._assist_proposed = False
        
        self.logger.log_action({
            "action": "orchestrator_initialized",
            "enforce_whitelist": self.enforce_whitelist,
            "whitelist_size": len(self.whitelist_manager.get_whitelist())
        })
    
    def _init_new_embedding_system(self):
        """
        Initialise le nouveau système d'embeddings avec FAISS et fine-tuning.
        """
        # Initialiser à None par défaut
        self.new_embedding_manager = None
        self.faiss_index = None
        self.fine_tuner = None
        
        try:
            # 1. Initialiser l'EmbeddingManager
            model_name = self.config.get("embedding", {}).get("model", "clip")
            cache_size = self.config.get("embedding", {}).get("cache_size", 1000)
            device = self.config.get("embedding", {}).get("device", "cpu")
            
            self.new_embedding_manager = NewEmbeddingManager(
                model_name=model_name,
                cache_size=cache_size,
                device=device,
                fallback_enabled=True
            )
            
            self.logger.log_action({
                "action": "new_embedding_manager_initialized",
                "model": model_name,
                "dimension": self.new_embedding_manager.get_dimension(),
                "device": device
            })
            
            # 2. Initialiser l'index FAISS
            try:
                index_path = self.config.get("faiss", {}).get("index_path", "data/workflow_embeddings")
                dimension = self.new_embedding_manager.get_dimension()
                
                self.faiss_index = FAISSIndex(dimension=dimension)
            
                # Charger l'index existant si disponible
                if os.path.exists(f"{index_path}.index"):
                    try:
                        self.faiss_index.load(index_path)
                        self.logger.log_action({
                            "action": "faiss_index_loaded",
                            "path": index_path,
                            "num_embeddings": self.faiss_index.get_stats()["num_embeddings"]
                        })
                    except Exception as e:
                        self.logger.log_action({
                            "action": "faiss_index_load_failed",
                            "error": str(e),
                            "creating_new": True
                        })
                else:
                    self.logger.log_action({
                        "action": "faiss_index_created_new",
                        "dimension": dimension
                    })
                
                # Sauvegarder le chemin pour cleanup
                self._faiss_index_path = index_path
                
            except Exception as e:
                self.logger.log_action({
                    "action": "faiss_index_init_failed",
                    "error": str(e)
                })
                self.faiss_index = None
            
            # 3. Initialiser le fine-tuner
            try:
                fine_tuning_config = self.config.get("fine_tuning", {})
                if fine_tuning_config.get("enabled", True):
                    trigger_threshold = fine_tuning_config.get("trigger_threshold", 10)
                    max_examples = fine_tuning_config.get("max_examples", 1000)
                    checkpoint_dir = fine_tuning_config.get("checkpoint_dir", "data/fine_tuning")
                    
                    # Créer le répertoire si nécessaire
                    os.makedirs(checkpoint_dir, exist_ok=True)
                    
                    self.fine_tuner = LightweightFineTuner(
                        embedder=self.new_embedding_manager.embedder,
                        trigger_threshold=trigger_threshold,
                        max_examples=max_examples
                    )
                    
                    # Charger le checkpoint si disponible
                    checkpoint_name = "orchestrator_finetuning"
                    checkpoint_path = os.path.join(checkpoint_dir, f"{checkpoint_name}.pkl")
                    if os.path.exists(checkpoint_path):
                        try:
                            self.fine_tuner.load_checkpoint(checkpoint_name)
                            self.logger.log_action({
                                "action": "fine_tuner_checkpoint_loaded",
                                "path": checkpoint_path,
                                "stats": self.fine_tuner.get_stats()
                            })
                        except Exception as e:
                            self.logger.log_action({
                                "action": "fine_tuner_checkpoint_load_failed",
                                "error": str(e)
                            })
                    
                    # Sauvegarder le nom pour cleanup
                    self._fine_tuner_checkpoint_name = checkpoint_name
                    
                    self.logger.log_action({
                        "action": "fine_tuner_initialized",
                        "trigger_threshold": trigger_threshold,
                        "max_examples": max_examples
                    })
                else:
                    self.fine_tuner = None
                    self._fine_tuner_checkpoint_name = None
                    self.logger.log_action({
                        "action": "fine_tuner_disabled"
                    })
            except Exception as e:
                self.logger.log_action({
                    "action": "fine_tuner_init_failed",
                    "error": str(e)
                })
                self.fine_tuner = None
                self._fine_tuner_checkpoint_name = None
            
        except Exception as e:
            self.logger.log_action({
                "action": "new_embedding_system_init_failed",
                "error": str(e)
            })
            import traceback
            traceback.print_exc()
            # Fallback: désactiver le nouveau système
            self.new_embedding_manager = None
            self.faiss_index = None
            self.fine_tuner = None
            self._faiss_index_path = None
            self._fine_tuner_checkpoint_name = None
    
    def _setup_emergency_stop(self):
        """Configure le gestionnaire d'arrêt d'urgence Ctrl+Pause"""
        def emergency_stop_handler(signum, frame):
            """Gestionnaire pour arrêt d'urgence"""
            self.logger.log_security_event({
                "event_type": "emergency_stop",
                "window": self.current_window,
                "details": "Arrêt d'urgence déclenché par l'utilisateur"
            })
            self.stop()
        
        # Sur Linux/Mac, utiliser SIGINT (Ctrl+C) comme arrêt d'urgence
        # Note: Ctrl+Pause n'est pas standard sur tous les OS
        try:
            signal.signal(signal.SIGINT, emergency_stop_handler)
        except Exception as e:
            self.logger.log_action({
                "action": "emergency_stop_setup_failed",
                "error": str(e)
            })
    
    def _generate_task_description_with_llm(
        self,
        events: List[Dict[str, Any]],
        signatures: List[Dict[str, Any]],
        pattern: Dict[str, Any]
    ) -> str:
        """
        Utilise Qwen3-VL pour générer une description intelligente de la tâche.
        
        Args:
            events: Liste des événements capturés
            signatures: Signatures visuelles créées
            pattern: Pattern détecté
            
        Returns:
            Description générée par le LLM
        """
        try:
            # Préparer les screenshots pour le LLM
            screenshots = [event['screenshot'] for event in events if event.get('screenshot') is not None]
            
            if not screenshots:
                return f"Tâche dans {pattern.get('window', 'Unknown')}"
            
            # Vérifier que le LLM est disponible
            if not self.llm or not self.llm.is_available():
                print(f"      ⚠️  Qwen3-VL non disponible (Ollama arrêté ?)")
                return f"Tâche dans {pattern.get('window', 'Unknown Window')}"
            
            # Créer un prompt pour Qwen3-VL
            window = pattern.get('window', 'Unknown Window')
            if not window or window.strip() == "":
                window = "Unknown Window"
            
            repetitions = pattern.get('repetitions', 0)
            action_type = events[0].get('type', 'action') if events else 'action'
            
            print(f"      📝 Contexte: {window}, {action_type}, {repetitions}x")
            
            # Génération avec vision (Gemma3 n'a pas de mode thinking)
            action_fr = "Clic" if action_type == "mouse_click" else "Défilement" if action_type == "scroll" else "Touche" if action_type == "key_press" else action_type
            
            # Prompt simple et direct pour Gemma3
            prompt = f"""Regarde cette image. L'utilisateur a fait: {action_fr} ({repetitions} fois) dans {window}

Donne un nom court pour cette action en 2-3 mots maximum.
Exemples: "Rafraîchir page", "Ouvrir menu", "Fermer fenêtre"

Nom:"""
            
            print(f"      🔄 Appel Gemma3 avec vision...")
            # Appeler avec une image
            response = self.llm.generate_with_vision(
                prompt=prompt,
                images=screenshots[:1]  # Une image suffit
            )
            print(f"      ✅ Réponse reçue (brute): '{response}'")
            print(f"      📏 Longueur: {len(response)} caractères")
            print(f"      🔤 Type: {type(response)}")
            
            # Nettoyer la réponse
            description = response.strip()
            print(f"      🧹 Après strip: '{description}' (longueur: {len(description)})")
            
            # Enlever la ponctuation finale
            description = description.rstrip('.!?')
            
            # Prendre seulement les premiers mots si trop long
            if len(description) > 60:
                words = description.split()
                description = ' '.join(words[:5])  # Garder 5 premiers mots max
                if len(description) > 60:
                    description = description[:57] + "..."
            
            # Si la réponse est vide ou invalide, fallback
            if not description or len(description) < 3:
                window_name = pattern.get('window', 'Unknown Window')
                if not window_name or window_name.strip() == "":
                    window_name = "Unknown Window"
                description = f"Tâche dans {window_name}"
                print(f"      ⚠️  Réponse LLM invalide, fallback: {description}")
            else:
                print(f"      ✓ Description générée: {description}")
            
            return description
            
        except Exception as e:
            print(f"      ❌ Erreur LLM: {e}")
            import traceback
            traceback.print_exc()
            # Fallback vers une description simple
            window_name = pattern.get('window', 'Unknown Window')
            if not window_name or window_name.strip() == "":
                window_name = "Unknown Window"
            return f"Tâche dans {window_name}"
    
    def _on_session_completed(self, session):
        """
        Callback appelé quand une session est complétée.
        Passe la session au WorkflowDetector pour analyse.
        
        Args:
            session: Session complétée
        """
        self.logger.log_action({
            "action": "session_completed",
            "session_id": session.session_id,
            "action_count": session.action_count,
            "duration_seconds": session.duration.total_seconds(),
            "window": session.window
        })
        
        self.metrics["sessions_created"] += 1
        
        # Analyser la session pour détecter des workflows
        try:
            self.workflow_detector.analyze_session(session)
        except Exception as e:
            self.logger.log_action({
                "action": "workflow_analysis_failed",
                "session_id": session.session_id,
                "error": str(e)
            })
    
    def _on_workflow_detected(self, workflow: Dict[str, Any]):
        """
        Callback appelé quand un workflow est détecté.
        Notifie le SuggestionManager et persiste le workflow.
        
        Args:
            workflow: Workflow détecté
        """
        self.logger.log_action({
            "action": "workflow_detected",
            "workflow_id": workflow.get("workflow_id"),
            "workflow_name": workflow.get("name"),
            "pattern_length": len(workflow.get("pattern", [])),
            "confidence": workflow.get("confidence"),
            "repetitions": workflow.get("repetitions")
        })
        
        self.metrics["workflows_detected"] += 1
        
        # Indexer le workflow dans FAISS (nouveau système)
        self._index_workflow_in_faiss(workflow)
        
        # Notifier le SuggestionManager
        try:
            self.suggestion_manager.on_workflow_detected(workflow)
        except Exception as e:
            self.logger.log_action({
                "action": "workflow_notification_failed",
                "workflow_id": workflow.get("workflow_id"),
                "error": str(e)
            })
        
        # Afficher notification dans la GUI
        if self.gui:
            workflow_name = workflow.get("name", "Workflow détecté")
            confidence = workflow.get("confidence", 0.0)
            if hasattr(self, 'log_to_gui'):
                self.log_to_gui(
                    "🎯",
                    f"Workflow détecté: {workflow_name} (confiance: {confidence:.0%})",
                    "success"
                )
    
    def _on_pattern_detected(self, pattern: Dict[str, Any]):
        """
        Callback appelé quand un pattern répétitif est détecté.
        Analyse les actions et crée une tâche apprise.
        """
        print(f"\n{'='*60}")
        print(f"🎯 PATTERN DÉTECTÉ DANS ORCHESTRATOR !")
        print(f"{'='*60}")
        print(f"Répétitions: {pattern['repetitions']}")
        print(f"Longueur: {pattern['length']}")
        print(f"Fenêtre: {pattern.get('window', 'Unknown')}")
        
        # LOG GUI: Pattern détecté
        if hasattr(self, 'log_to_gui'):
            self.log_to_gui("🎯", f"Pattern détecté ! ({pattern['repetitions']} répétitions)", "success")
        
        try:
            # 1. Récupérer les derniers événements avec screenshots
            print(f"🔍 Récupération des screenshots...")
            last_events = self.event_capture.get_last_screenshots(3)
            print(f"📊 Screenshots trouvés: {len(last_events)}")
            
            if len(last_events) < 3:
                print(f"⚠️  Pas assez de screenshots capturés (besoin de 3, trouvé {len(last_events)})")
                print(f"💡 Astuce: Les screenshots sont capturés seulement pour les clics et certaines touches")
                return
            
            print(f"📸 Analyse de {len(last_events)} actions...")
            
            # 2. Analyser chaque action avec l'IA
            signatures = []
            for i, event in enumerate(last_events):
                print(f"   🔬 Analyse action {i+1}/{len(last_events)}...")
                try:
                    signature = self.vision_analyzer.analyze_action(
                        screenshot=event['screenshot'],
                        x=event.get('x', 0),
                        y=event.get('y', 0),
                        action_type=event['type'],
                        window=event['window']
                    )
                    signatures.append(signature)
                    print(f"      ✓ Signature créée")
                except Exception as e:
                    print(f"      ❌ Erreur: {e}")
                    import traceback
                    traceback.print_exc()
            
            if not signatures:
                print("   ❌ Échec de l'analyse - aucune signature créée")
                return
            
            print(f"   ✅ {len(signatures)} signatures créées")
            
            # 3. Utiliser Qwen3-VL pour générer une description intelligente
            print(f"   🤖 Analyse avec Qwen3-VL...")
            task_description = self._generate_task_description_with_llm(
                last_events,
                signatures,
                pattern
            )
            
            # 4. Créer une tâche
            task = self.learning_manager.create_task_from_signatures(
                signatures,
                description=task_description
            )
            
            print(f"   ✅ Tâche créée : {task.task_id}")
            print(f"   📝 Description : {task.task_name}")
            
            # LOG GUI: Tâche créée
            if hasattr(self, 'log_to_gui'):
                self.log_to_gui("📚", f"Tâche apprise : {task.task_name}", "info")
                # Mettre à jour les stats (on compte les tâches comme workflows)
                if hasattr(self, 'update_gui_stats'):
                    workflows_count = len(self.learning_manager.get_all_tasks())
                    self.update_gui_stats(workflows_count=workflows_count)
            
            # 4. Proposer de basculer en mode assist si en mode progressif
            current_mode = self.learning_manager.get_mode()
            print(f"\n🔍 DEBUG: Mode actuel = {current_mode}")
            print(f"🔍 DEBUG: Progressive mode = {getattr(self, '_progressive_mode', False)}")
            print(f"🔍 DEBUG: Assist proposed = {getattr(self, '_assist_proposed', False)}")
            
            if current_mode == "shadow" and hasattr(self, '_progressive_mode') and self._progressive_mode:
                print(f"✅ Conditions remplies pour proposer le mode assist!")
                self._propose_assist_mode(task)
            else:
                print(f"❌ Conditions NON remplies pour proposer le mode assist")
            
            # 5. Notifier la GUI
            if hasattr(self, 'log_to_gui'):
                remaining = max(0, 20 - task.observation_count)
                self.log_to_gui(
                    "🎉",
                    f"Tâche apprise : {task.task_name} (encore {remaining} répétitions pour rejeu)",
                    "success"
                )
        
        except Exception as e:
            print(f"\n❌ ERREUR DANS LE CALLBACK:")
            print(f"   {e}")
            import traceback
            traceback.print_exc()
    
    def _extract_intent_from_pattern(self, pattern: Dict[str, Any]) -> Optional[str]:
        """
        Extrait une intention détectable du pattern.
        
        Returns:
            Terme détectable par les modèles de vision (button, icon, text, etc.)
        """
        sequence = pattern.get("sequence", [])
        
        if not sequence:
            return None
        
        # Analyser le type d'actions dans la séquence
        has_clicks = any(e["type"] == "click" for e in sequence)
        has_keys = any(e["type"] == "key" for e in sequence)
        
        # Déterminer l'intention basée sur les actions
        if has_clicks and not has_keys:
            return "button"  # Probablement des clics sur des boutons
        elif has_keys and not has_clicks:
            return "text field"  # Probablement de la saisie de texte
        elif has_clicks and has_keys:
            return "form"  # Probablement un formulaire
        
        return "button"  # Par défaut
    
    def replay_task(self, task_id: str) -> bool:
        """
        Rejoue une tâche apprise.
        
        Args:
            task_id: ID de la tâche à rejouer
        
        Returns:
            True si succès, False sinon
        """
        # Charger la tâche
        task = self.learning_manager.load_task(task_id)
        
        if not task or not hasattr(task, 'signatures'):
            print(f"❌ Tâche {task_id} introuvable")
            return False
        
        print(f"🎬 Rejeu de la tâche : {task.description}")
        
        # Rejouer chaque action
        for i, signature in enumerate(task.signatures):
            print(f"   Action {i+1}/{len(task.signatures)}...")
            
            # 1. Capturer l'écran actuel
            current_screenshot = capture_screen()
            
            # 2. Trouver l'élément visuellement
            result = self.vision_search.find_element(
                current_screenshot,
                signature,
                confidence_threshold=0.8
            )
            
            if result:
                x, y, confidence = result
                print(f"   ✓ Élément trouvé à ({x}, {y}) - confiance: {confidence:.2f}")
                
                # 3. Exécuter l'action
                action_type = signature.get('action_type', 'mouse_click')
                
                try:
                    if action_type == 'mouse_click':
                        self.input_utils.click(x, y)
                    elif action_type == 'scroll':
                        dy = signature.get('dy', 0)
                        self.input_utils.scroll(dy)
                    elif action_type == 'key_press':
                        key = signature.get('key', '')
                        self.input_utils.type_text(key)
                    
                    print(f"   ✓ Action exécutée")
                    
                    # Petite pause entre les actions
                    import time
                    time.sleep(0.5)
                
                except Exception as e:
                    print(f"   ❌ Échec de l'exécution: {e}")
                    return False
            else:
                print(f"   ❌ Élément introuvable")
                
                # Demander confirmation utilisateur
                if hasattr(self, 'log_to_gui'):
                    self.log_to_gui(
                        "⚠️",
                        f"Élément introuvable pour l'action {i+1}",
                        "warning"
                    )
                
                return False
        
        print(f"✅ Tâche rejouée avec succès !")
        return True
    
    def run(self):
        """
        Boucle cognitive principale
        Exécute continuellement: Observer → Réfléchir → Agir → Apprendre
        """
        self.running = True
        self._stop_event.clear()
        
        # Démarrer la capture d'événements
        self.event_capture.start()
        
        self.logger.log_action({
            "action": "orchestrator_started",
            "mode": self.learning_manager.get_mode()
        })
        
        # LOG GUI: Démarrage de l'observation
        if hasattr(self, 'log_to_gui'):
            self.log_to_gui("🚀", "Démarrage de l'observation...", "info")
            self.log_to_gui("👀", "Observation active - En attente d'actions...", "success")
        
        # Compteur pour mise à jour périodique de la GUI
        last_event_count = 0
        gui_update_counter = 0
        
        try:
            while self.running and not self._stop_event.is_set():
                # Vérifier si en pause
                if self.paused:
                    time.sleep(0.1)
                    continue
                
                cycle_start = time.time()
                
                # Mettre à jour les stats GUI périodiquement (toutes les 10 itérations)
                gui_update_counter += 1
                if gui_update_counter >= 10 and hasattr(self, 'update_gui_stats'):
                    current_event_count = len(self.event_capture.events)
                    if current_event_count != last_event_count:
                        # Nouvelles actions détectées
                        workflows_count = len(self.learning_manager.get_all_tasks())
                        patterns_count = len(self.workflow_detector.detected_workflows) if hasattr(self.workflow_detector, 'detected_workflows') else 0
                        
                        self.update_gui_stats(
                            actions_count=current_event_count,
                            patterns_count=patterns_count,
                            workflows_count=workflows_count
                        )
                        
                        # Log seulement si de nouvelles actions
                        if current_event_count > last_event_count and hasattr(self, 'log_to_gui'):
                            new_actions = current_event_count - last_event_count
                            if new_actions > 0:
                                window = self.current_window or "Application"
                                self.log_to_gui("👀", f"{new_actions} action(s) observée(s) dans {window}", "info")
                        
                        last_event_count = current_event_count
                    gui_update_counter = 0
                
                try:
                    # 1. OBSERVER - Capturer le contexte actuel
                    context = self.capture_context()
                    
                    if not context:
                        # Pas de contexte valide, attendre
                        time.sleep(0.5)
                        continue
                    
                    # 1.5. VÉRIFIER LES SUGGESTIONS (Mode Assisté)
                    # Vérifier périodiquement s'il faut suggérer une action
                    try:
                        self.check_for_suggestions()
                    except Exception as e:
                        self.logger.log_action({
                            "action": "suggestion_check_error",
                            "error": str(e)
                        })
                    
                    # 2. RÉFLÉCHIR - Détecter les éléments et raisonner
                    intent = self.learning_manager.get_current_intent()
                    
                    # En mode Shadow sans intention, on observe passivement
                    # Les actions utilisateur seront capturées via les hooks système
                    # et enregistrées pour l'apprentissage
                    if not intent:
                        # Pas d'intention définie, on attend les actions utilisateur
                        # En mode Shadow, on n'essaie pas de détecter proactivement
                        time.sleep(0.1)
                        continue
                    
                    # Si une intention est définie, détecter et agir
                    if intent:
                        detections = self.detect_elements(context["frame"], intent)
                        
                        if detections:
                            decision = self.reason_about_action(detections, context)
                            
                            # 3. AGIR - Exécuter ou suggérer selon le mode
                            self.execute_or_suggest(decision)
                            
                            # 4. APPRENDRE - Mettre à jour l'état d'apprentissage
                            # (géré dans execute_or_suggest via les callbacks)
                    
                    # Calculer la latence du cycle
                    cycle_time = (time.time() - cycle_start) * 1000  # ms
                    self._update_metrics(cycle_time)
                    
                    # Vérifier les seuils de performance
                    if cycle_time > self.perf_config["max_latency_ms"]:
                        self.logger.log_action({
                            "action": "performance_warning",
                            "cycle_time_ms": cycle_time,
                            "threshold_ms": self.perf_config["max_latency_ms"]
                        })
                    
                    # Petite pause pour éviter de surcharger le CPU
                    time.sleep(0.1)
                
                except Exception as e:
                    self.logger.log_action({
                        "action": "cycle_error",
                        "error": str(e),
                        "window": self.current_window
                    })
                    time.sleep(1)  # Pause plus longue en cas d'erreur
        
        finally:
            self.running = False
            self.logger.log_action({
                "action": "orchestrator_stopped",
                "total_cycles": self.metrics["total_cycles"],
                "avg_latency_ms": self.metrics["avg_latency_ms"]
            })

    
    def capture_context(self) -> Optional[Dict[str, Any]]:
        """
        Capture le contexte actuel (écran et fenêtre active)
        
        Returns:
            Dictionnaire contenant frame, window_title et timestamp
            ou None si la capture échoue
        """
        try:
            # Capturer l'écran
            frame = capture_screen()
            
            # Obtenir la fenêtre active
            window_title = get_active_window()
            
            # Mettre à jour l'état actuel
            self.current_frame = frame
            self.current_window = window_title
            
            # Créer le contexte
            context = {
                "frame": frame,
                "window_title": window_title,
                "timestamp": datetime.now(),
                "frame_shape": frame.shape
            }
            
            self.current_context = context
            
            return context
        
        except Exception as e:
            self.logger.log_action({
                "action": "capture_context_error",
                "error": str(e)
            })
            return None
    
    def detect_elements(self, frame: np.ndarray, intent: str) -> List[Detection]:
        """
        Détecte les éléments UI dans le frame basé sur l'intention
        
        Args:
            frame: Image de l'écran
            intent: Intention utilisateur (ex: "cliquer sur valider")
            
        Returns:
            Liste de détections trouvées
        """
        try:
            # Extraire les mots-clés de l'intention pour la détection
            # Pour l'instant, utiliser l'intention complète comme prompt
            prompt = intent
            
            # Détecter avec fallback automatique entre modèles
            detections = self.vision.detect(prompt, frame)
            
            # Filtrer et fusionner les détections
            if detections:
                detections = self.vision.filter_detections(
                    detections,
                    min_confidence=0.3
                )
                detections = self.vision.merge_overlapping_detections(
                    detections,
                    iou_threshold=0.5
                )
            
            self.metrics["detections_count"] += len(detections)
            
            self.logger.log_action({
                "action": "elements_detected",
                "intent": intent,
                "num_detections": len(detections),
                "window": self.current_window
            })
            
            return detections
        
        except Exception as e:
            self.logger.log_action({
                "action": "detection_error",
                "error": str(e),
                "intent": intent
            })
            return []
    
    def reason_about_action(
        self,
        detections: List[Detection],
        context: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Utilise le LLM pour raisonner sur quelle action effectuer
        
        Args:
            detections: Liste de détections d'éléments UI
            context: Contexte actuel
            
        Returns:
            Dictionnaire décrivant la décision d'action
        """
        try:
            # Préparer les détections pour le LLM
            detections_data = [
                {
                    "label": d.label,
                    "confidence": d.confidence,
                    "bbox": d.bbox,
                    "roi_image": d.roi_image,
                    "model_source": d.model_source
                }
                for d in detections
            ]
            
            # Obtenir l'intention actuelle
            intent = self.learning_manager.get_current_intent()
            
            # Raisonner avec le LLM
            llm_result = self.llm.reason_about_detections(
                detections_data,
                context,
                intent
            )
            
            # Construire la décision
            selected_element = llm_result.get("selected_element")
            llm_score = llm_result.get("llm_score", 0.5)
            
            if selected_element:
                # Trouver la détection correspondante
                selected_detection = None
                for d in detections:
                    if (d.label == selected_element.get("label") and
                        d.bbox == tuple(selected_element.get("bbox", []))):
                        selected_detection = d
                        break
                
                if selected_detection:
                    # Calculer la confiance globale
                    vision_conf = selected_detection.confidence
                    task_id = self.learning_manager.current_task_id
                    
                    confidence = self.learning_manager.calculate_confidence(
                        vision_conf,
                        llm_score,
                        task_id or "default"
                    )
                    
                    # Créer l'action
                    action = Action(
                        action_type="click",  # Par défaut, sera affiné plus tard
                        target_element=selected_detection.label,
                        bbox=selected_detection.bbox,
                        confidence=confidence,
                        embedding=selected_detection.embedding,
                        timestamp=datetime.now(),
                        window_title=context.get("window_title", ""),
                        parameters={}
                    )
                    
                    decision = {
                        "action": action,
                        "detection": selected_detection,
                        "confidence": confidence,
                        "llm_reasoning": llm_result.get("reasoning", ""),
                        "task_id": task_id
                    }
                    
                    self.logger.log_action({
                        "action": "decision_made",
                        "target_element": action.target_element,
                        "confidence": confidence,
                        "llm_score": llm_score,
                        "vision_conf": vision_conf
                    })
                    
                    return decision
            
            # Aucune décision valide
            return {
                "action": None,
                "confidence": 0.0,
                "reasoning": "Aucun élément approprié trouvé"
            }
        
        except Exception as e:
            self.logger.log_action({
                "action": "reasoning_error",
                "error": str(e)
            })
            return {
                "action": None,
                "confidence": 0.0,
                "reasoning": f"Erreur: {str(e)}"
            }
    
    def execute_or_suggest(self, decision: Dict[str, Any]):
        """
        Exécute ou suggère une action selon le mode opérationnel
        
        Args:
            decision: Dictionnaire décrivant la décision d'action
        """
        action = decision.get("action")
        
        if not action:
            return
        
        # Obtenir le mode actuel
        mode = self.learning_manager.get_mode()
        
        if mode == "shadow":
            # Mode Shadow: Observer uniquement
            self.learning_manager.observe(action)
            
            if hasattr(self, 'log_to_gui'):
                self.log_to_gui(
                    "👀",
                    f"Observation: {action.target_element}",
                    "info"
                )
        
        elif mode == "assist":
            # Mode Assisté: Suggérer et attendre validation
            self.metrics["actions_suggested"] += 1
            
            if self.gui:
                feedback = self.gui.show_suggestion(decision)
                self.learn_from_feedback(feedback, decision)
            else:
                # Sans GUI, logger la suggestion
                self.logger.log_action({
                    "action": "suggestion_made",
                    "target_element": action.target_element,
                    "confidence": decision.get("confidence", 0.0),
                    "mode": "assist"
                })
        
        elif mode == "auto":
            # Mode Autopilot: Vérifier liste blanche puis exécuter
            if self._check_whitelist(action.window_title):
                self.execute_action(decision)
            else:
                # Violation de liste blanche
                self.logger.log_security_event({
                    "event_type": "whitelist_violation",
                    "window": action.window_title,
                    "action_attempted": action.action_type,
                    "target_element": action.target_element,
                    "details": "Fenêtre non autorisée dans la liste blanche"
                })
                
                if hasattr(self, 'log_to_gui'):
                    self.log_to_gui(
                        "⚠️",
                        "Action bloquée: fenêtre non autorisée",
                        "warning"
                    )
    
    def _check_whitelist(self, window_title: str) -> bool:
        """
        Vérifie si une fenêtre est dans la liste blanche
        
        Args:
            window_title: Titre de la fenêtre à vérifier
            
        Returns:
            True si autorisée ou si la liste blanche n'est pas appliquée
        """
        if not self.enforce_whitelist:
            return True
        
        # Utiliser le WhitelistManager pour la vérification
        return self.whitelist_manager.is_window_allowed(window_title)
    
    def execute_action(self, decision: Dict[str, Any]):
        """
        Exécute réellement une action (mode Autopilot)
        
        Args:
            decision: Dictionnaire décrivant la décision d'action
        """
        action = decision.get("action")
        
        if not action:
            return
        
        try:
            execution_start = time.time()
            
            # Préparer les données d'action pour InputUtils
            action_data = {
                "action_type": action.action_type,
                "bbox": action.bbox,
                "parameters": action.parameters
            }
            
            # Exécuter l'action via InputUtils
            success = self.input_utils.execute_action(action_data)
            
            # Calculer la latence d'exécution
            execution_time = (time.time() - execution_start) * 1000  # ms
            
            # Logger le résultat
            self.logger.log_action({
                "action": "action_executed",
                "window": action.window_title,
                "action_type": action.action_type,
                "target_element": action.target_element,
                "bbox": list(action.bbox),
                "confidence": action.confidence,
                "mode": "auto",
                "result": "success" if success else "failed",
                "latency_ms": execution_time
            })
            
            if success:
                # Enregistrer l'exécution dans le learning manager
                self.learning_manager.record_execution({
                    "action": action,
                    "confidence": decision.get("confidence", 0.0),
                    "task_id": decision.get("task_id"),
                    "latency_ms": execution_time
                })
                
                self.metrics["actions_executed"] += 1
                
                # Afficher notification de succès
                if hasattr(self, 'log_to_gui'):
                    self.log_to_gui(
                        "✔️",
                        f"Action exécutée: {action.target_element}",
                        "success"
                    )
            else:
                # Échec d'exécution - déclencher rollback
                self.logger.log_action({
                    "action": "execution_failed_rollback_triggered",
                    "target_element": action.target_element
                })
                
                # Rollback des 3 dernières actions
                self.rollback_last_actions(count=3)
                
                if hasattr(self, 'log_to_gui'):
                    self.log_to_gui(
                        "⚠️",
                        "Échec d'exécution - Rollback effectué",
                        "warning"
                    )
        
        except Exception as e:
            self.logger.log_action({
                "action": "execution_error",
                "error": str(e),
                "target_element": action.target_element,
                "mode": "auto",
                "result": "failed"
            })
            
            # Rollback en cas d'erreur
            self.rollback_last_actions(count=3)
            
            if hasattr(self, 'log_to_gui'):
                self.log_to_gui(
                    "❌",
                    f"Erreur d'exécution: {str(e)}",
                    "error"
                )
    
    def learn_from_feedback(self, feedback: Dict[str, Any], decision: Dict[str, Any]):
        """
        Met à jour l'apprentissage basé sur le retour utilisateur
        
        Args:
            feedback: Retour utilisateur (accept/reject/correct)
            decision: Décision originale
        """
        try:
            # Ajouter des informations de contexte au feedback
            feedback["task_id"] = decision.get("task_id")
            feedback["action"] = decision.get("action")
            
            # Transmettre au learning manager
            self.learning_manager.confirm_action(feedback)
            
            self.logger.log_action({
                "action": "feedback_processed",
                "feedback_type": feedback.get("type"),
                "task_id": feedback.get("task_id")
            })
        
        except Exception as e:
            self.logger.log_action({
                "action": "feedback_error",
                "error": str(e)
            })
    
    def _update_metrics(self, cycle_time_ms: float):
        """
        Met à jour les métriques de performance
        
        Args:
            cycle_time_ms: Temps du cycle en millisecondes
        """
        self.metrics["total_cycles"] += 1
        
        # Moyenne mobile de la latence
        alpha = 0.1  # Facteur de lissage
        self.metrics["avg_latency_ms"] = (
            alpha * cycle_time_ms +
            (1 - alpha) * self.metrics["avg_latency_ms"]
        )
    
    def stop(self):
        """Arrête la boucle cognitive"""
        self.running = False
        self._stop_event.set()
        
        # Arrêter la capture d'événements
        self.event_capture.stop()
        
        # Sauvegarder le système d'embeddings
        self._save_embedding_system_on_shutdown()
        
        self.logger.log_action({
            "action": "orchestrator_stop_requested"
        })
    
    def pause(self):
        """Met en pause la boucle cognitive"""
        self.paused = True
        self._pause_event.set()
        
        self.logger.log_action({
            "action": "orchestrator_paused"
        })
    
    def resume(self):
        """Reprend la boucle cognitive"""
        self.paused = False
        self._pause_event.clear()
        
        self.logger.log_action({
            "action": "orchestrator_resumed"
        })
    
    def add_to_whitelist(self, window_pattern: str, admin_confirmed: bool = False):
        """
        Ajoute une fenêtre à la liste blanche
        
        Args:
            window_pattern: Pattern de titre de fenêtre à autoriser
            admin_confirmed: Si True, bypass la confirmation admin
        """
        return self.whitelist_manager.add_to_whitelist(
            window_pattern,
            admin_confirmed=admin_confirmed,
            added_by="orchestrator"
        )
    
    def remove_from_whitelist(self, window_pattern: str):
        """
        Retire une fenêtre de la liste blanche
        
        Args:
            window_pattern: Pattern de titre de fenêtre à retirer
        """
        return self.whitelist_manager.remove_from_whitelist(window_pattern)
    
    def get_whitelist(self) -> List[str]:
        """Retourne la liste blanche actuelle"""
        return self.whitelist_manager.get_whitelist()
    
    def set_whitelist_enforcement(self, enforce: bool):
        """
        Active ou désactive l'application de la liste blanche
        
        Args:
            enforce: True pour activer, False pour désactiver
        """
        self.enforce_whitelist = enforce
        
        self.logger.log_action({
            "action": "whitelist_enforcement_changed",
            "enforce": enforce
        })
    
    def get_metrics(self) -> Dict[str, Any]:
        """Retourne les métriques de performance actuelles"""
        return self.metrics.copy()
    
    def rollback_last_actions(self, count: int = 3):
        """
        Effectue un rollback des dernières actions
        
        Args:
            count: Nombre d'actions à annuler (défaut: 3)
        """
        try:
            # Obtenir les dernières actions
            recent_actions = self.input_utils.get_action_history(limit=count)
            
            if not recent_actions:
                self.logger.log_action({
                    "action": "rollback_skipped",
                    "reason": "no_actions_in_history"
                })
                return
            
            self.logger.log_action({
                "action": "rollback_started",
                "actions_count": len(recent_actions)
            })
            
            # Exécuter les actions inverses dans l'ordre inverse
            rollback_success = 0
            rollback_failed = 0
            
            for action in reversed(recent_actions):
                success = self.input_utils.execute_inverse_action(action)
                if success:
                    rollback_success += 1
                else:
                    rollback_failed += 1
            
            self.logger.log_action({
                "action": "rollback_completed",
                "success_count": rollback_success,
                "failed_count": rollback_failed
            })
            
            if hasattr(self, 'log_to_gui'):
                self.log_to_gui(
                    "🔄",
                    f"Rollback: {rollback_success}/{len(recent_actions)} actions annulées",
                    "info"
                )
        
        except Exception as e:
            self.logger.log_action({
                "action": "rollback_error",
                "error": str(e)
            })
    
    def get_status(self) -> Dict[str, Any]:
        """
        Retourne l'état actuel de l'orchestrateur
        
        Returns:
            Dictionnaire avec l'état complet
        """
        return {
            "running": self.running,
            "paused": self.paused,
            "mode": self.learning_manager.get_mode(),
            "current_window": self.current_window,
            "current_task": self.learning_manager.current_task_id,
            "whitelist_size": len(self.whitelist_manager.get_whitelist()),
            "enforce_whitelist": self.enforce_whitelist,
            "metrics": self.get_metrics(),
            "action_history_size": len(self.input_utils.get_action_history())
        }
    
    def enable_progressive_mode(self):
        """Active le mode progressif (démarre en shadow, propose assist)."""
        print("\n" + "="*60)
        print("🎓 ACTIVATION DU MODE PROGRESSIF")
        print("="*60)
        
        self._progressive_mode = True
        self._assist_proposed = False
        self.learning_manager.mode = "shadow"
        
        print(f"✅ Mode progressif activé:")
        print(f"   - _progressive_mode = {self._progressive_mode}")
        print(f"   - _assist_proposed = {self._assist_proposed}")
        print(f"   - learning_manager.mode = {self.learning_manager.mode}")
        print("="*60 + "\n")
        
        self.logger.log_action({
            "action": "progressive_mode_enabled",
            "initial_mode": "shadow"
        })
        
        if hasattr(self, 'log_to_gui'):
            self.log_to_gui(
                "🎓",
                "Mode Progressif activé - Observation des patterns",
                "info"
            )
    
    def _propose_assist_mode(self, task):
        """
        Propose de basculer en mode assist après détection d'un pattern.
        
        Args:
            task: Tâche détectée
        """
        # Ne proposer qu'une seule fois
        if self._assist_proposed:
            return
        
        self._assist_proposed = True
        
        self.logger.log_action({
            "action": "assist_mode_proposed",
            "task_id": task.task_id,
            "task_name": task.task_name
        })
        
        # Afficher la proposition dans la GUI
        if self.gui and hasattr(self.gui, 'show_mode_switch_proposal'):
            self.gui.show_mode_switch_proposal(
                task_name=task.task_name,
                on_accept=self._switch_to_assist_mode,
                on_reject=self._stay_in_shadow_mode
            )
        else:
            # Fallback: notification simple
            print(f"\n{'='*60}")
            print(f"💡 PROPOSITION DE MODE ASSIST")
            print(f"{'='*60}")
            print(f"J'ai détecté un pattern répétitif: {task.task_name}")
            print(f"")
            print(f"Est-ce que je peux essayer de vous aider en suggérant")
            print(f"automatiquement cette action la prochaine fois ?")
            print(f"")
            print(f"→ Basculer en mode Assist maintenant")
            print(f"  (Les suggestions apparaîtront automatiquement)")
            print(f"{'='*60}\n")
            
            # Auto-accepter après 10 secondes si pas de GUI
            import threading
            def auto_accept():
                time.sleep(10)
                if not self._assist_proposed:
                    return
                print("⏱️  Pas de réponse, je bascule en mode Assist...")
                self._switch_to_assist_mode()
            
            threading.Thread(target=auto_accept, daemon=True).start()
    
    def _switch_to_assist_mode(self):
        """Bascule en mode assist."""
        self.learning_manager.mode = "assist"
        
        self.logger.log_action({
            "action": "switched_to_assist_mode",
            "from_progressive": True
        })
        
        print(f"\n✅ Mode Assist activé !")
        print(f"   Les suggestions apparaîtront automatiquement\n")
        
        if hasattr(self, 'log_to_gui'):
            self.log_to_gui(
                "✅",
                "Mode Assist activé - Suggestions automatiques activées",
                "success"
            )
            if hasattr(self, 'change_mode_gui'):
                self.change_mode_gui("assist")
    
    def _stay_in_shadow_mode(self):
        """Reste en mode shadow."""
        self.logger.log_action({
            "action": "stayed_in_shadow_mode",
            "user_declined_assist": True
        })
        
        print(f"\n👀 Je continue en mode observation")
        
        if hasattr(self, 'log_to_gui'):
            self.log_to_gui(
                "👀",
                "Mode Shadow maintenu - Observation continue",
                "info"
            )
    
    # Méthodes pour le Mode Assisté
    def _capture_context_for_suggestion(self) -> Dict[str, Any]:
        """
        Capture le contexte actuel pour générer des suggestions.
        
        Returns:
            Dictionnaire de contexte
        """
        try:
            # Capturer l'écran
            screenshot = capture_screen()
            
            # Fenêtre active
            window = get_active_window()
            
            # Générer embedding avec le système d'embeddings
            embedding = None
            if screenshot is not None:
                try:
                    # Utiliser le nouveau système d'embeddings si disponible
                    if self.new_embedding_manager:
                        # Convertir numpy BGR → PIL RGB
                        screenshot_rgb = cv2.cvtColor(screenshot, cv2.COLOR_BGR2RGB)
                        pil_image = Image.fromarray(screenshot_rgb)
                        embedding = self.new_embedding_manager.embed(pil_image)
                    else:
                        # Fallback vers l'ancien système
                        embedding = self.learning_manager.embeddings_manager.encode_image(screenshot)
                except Exception as e:
                    self.logger.log_action({
                        "action": "embedding_generation_failed",
                        "error": str(e)
                    })
            
            return {
                "screenshot": screenshot,
                "window": window,
                "embedding": embedding,
                "event_capture": self.event_capture,  # Ajouter l'event_capture pour les workflows
                "timestamp": datetime.now()
            }
        
        except Exception as e:
            self.logger.log_action({
                "action": "context_capture_error",
                "error": str(e)
            })
            return {}
    
    def _on_suggestion_created(self, suggestion: Dict[str, Any]):
        """
        Callback appelé quand une suggestion est créée.
        
        Args:
            suggestion: Suggestion créée
        """
        suggestion_type = suggestion.get("type", "action")
        
        if suggestion_type == "workflow":
            # Suggestion de workflow
            self.logger.log_action({
                "action": "workflow_suggestion_created",
                "workflow_id": suggestion["workflow_id"],
                "workflow_name": suggestion["workflow_name"],
                "step": suggestion["current_step"],
                "confidence": suggestion["confidence"]
            })
        else:
            # Suggestion d'action classique
            self.logger.log_action({
                "action": "suggestion_created_callback",
                "task_id": suggestion.get("task_id"),
                "confidence": suggestion["confidence"]
            })
        
        # Afficher dans la GUI si disponible
        if self.gui and hasattr(self.gui, "show_suggestion"):
            self.gui.show_suggestion(suggestion)
        
        # Mettre à jour les métriques
        self.metrics["actions_suggested"] += 1
    
    def _on_suggestion_accepted(self, suggestion: Dict[str, Any]):
        """
        Callback appelé quand une suggestion est acceptée.
        
        Args:
            suggestion: Suggestion acceptée
        """
        self.logger.log_action({
            "action": "suggestion_accepted_callback",
            "task_id": suggestion["task_id"]
        })
        
        # Ajouter comme exemple positif pour le fine-tuning
        self._add_positive_example_for_finetuning(suggestion)
        
        # Exécuter la suggestion
        self._execute_suggestion(suggestion)
    
    def _on_suggestion_rejected(self, suggestion: Dict[str, Any]):
        """
        Callback appelé quand une suggestion est rejetée.
        
        Args:
            suggestion: Suggestion rejetée
        """
        self.logger.log_action({
            "action": "suggestion_rejected_callback",
            "task_id": suggestion["task_id"]
        })
        
        # Ajouter comme exemple négatif pour le fine-tuning
        self._add_negative_example_for_finetuning(suggestion)
        
        # Masquer dans la GUI si disponible
        if self.gui and hasattr(self.gui, "hide_suggestion"):
            self.gui.hide_suggestion()
    
    def _on_suggestion_timeout(self, suggestion: Dict[str, Any]):
        """
        Callback appelé quand une suggestion expire.
        
        Args:
            suggestion: Suggestion expirée
        """
        self.logger.log_action({
            "action": "suggestion_timeout_callback",
            "task_id": suggestion["task_id"]
        })
        
        # Masquer dans la GUI si disponible
        if self.gui and hasattr(self.gui, "hide_suggestion"):
            self.gui.hide_suggestion()
    
    def _execute_suggestion(self, suggestion: Dict[str, Any]):
        """
        Exécute une suggestion acceptée.
        
        Args:
            suggestion: Suggestion à exécuter
        """
        import asyncio
        
        task_id = suggestion["task_id"]
        
        self.logger.log_action({
            "action": "executing_suggestion",
            "task_id": task_id
        })
        
        try:
            # Exécuter le rejeu de manière asynchrone
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            
            result = loop.run_until_complete(
                self.replay_engine.replay_task(task_id, interactive=False)
            )
            
            loop.close()
            
            # Logger le résultat
            self.logger.log_action({
                "action": "suggestion_executed",
                "task_id": task_id,
                "success": result.get("success", False),
                "executed_actions": result.get("executed_actions", 0),
                "failed_actions": result.get("failed_actions", 0)
            })
            
            # Mettre à jour les métriques
            if result.get("success"):
                self.metrics["actions_executed"] += 1
            
            # Notifier la GUI
            if self.gui and hasattr(self.gui, "show_execution_result"):
                self.gui.show_execution_result(result)
        
        except Exception as e:
            self.logger.log_action({
                "action": "suggestion_execution_error",
                "task_id": task_id,
                "error": str(e)
            })
    
    def check_for_suggestions(self):
        """
        Vérifie s'il faut créer une suggestion basée sur le contexte actuel.
        À appeler périodiquement ou après une action utilisateur.
        """
        # Vérifier le mode
        mode = self.learning_manager.get_mode()
        
        if mode != "assist":
            return
        
        # Vérifier qu'il n'y a pas déjà une suggestion active
        if self.suggestion_manager.get_current_suggestion() is not None:
            # Vérifier le timeout
            self.suggestion_manager.check_timeout()
            return
        
        # 1. D'abord, vérifier les workflows (priorité haute)
        workflow_match = self._check_workflow_match()
        if workflow_match:
            # Créer une suggestion de workflow
            suggestion = self.suggestion_manager.create_workflow_suggestion(workflow_match)
            if suggestion:
                self.logger.log_action({
                    "action": "workflow_suggestion_created_in_orchestrator",
                    "workflow_id": workflow_match.workflow_id,
                    "confidence": workflow_match.confidence
                })
                return
        
        # 2. Sinon, vérifier les suggestions d'actions classiques
        # Capturer le contexte
        context = self._capture_context_for_suggestion()
        
        if not context:
            return
        
        # Créer une suggestion si applicable
        self.suggestion_manager.create_suggestion(context)
    
    def _check_workflow_match(self) -> Optional[Any]:
        """
        Vérifie si les actions courantes correspondent à un workflow connu.
        
        Returns:
            WorkflowMatch si trouvé, None sinon
        """
        try:
            # Récupérer la session courante
            current_session = self.session_manager.current_session
            
            if not current_session or not current_session.actions:
                return None
            
            # Récupérer les workflows connus
            workflows = self.workflow_detector.get_workflows()
            
            if not workflows:
                return None
            
            # Convertir les workflows en format dict pour le matcher
            workflows_dict = []
            for workflow in workflows:
                workflow_dict = {
                    "workflow_id": workflow.workflow_id,
                    "name": workflow.name,
                    "steps": [
                        {
                            "action_type": step.action_type,
                            "position": step.position,
                            "window": step.window,
                            "target_description": step.target_description
                        }
                        for step in workflow.steps
                    ]
                }
                workflows_dict.append(workflow_dict)
            
            # Utiliser le SuggestionManager pour vérifier les correspondances
            workflow_match = self.suggestion_manager.check_workflow_match(
                current_session.actions,
                workflows_dict
            )
            
            return workflow_match
        
        except Exception as e:
            self.logger.log_action({
                "action": "workflow_match_check_error",
                "error": str(e)
            })
            return None
    
    def find_matching_workflows_enhanced(
        self,
        screen_state: Optional[Any] = None,
        screenshot: Optional[np.ndarray] = None,
        top_k: int = 5
    ) -> List[Any]:
        """
        Trouve les workflows qui matchent avec l'écran actuel en utilisant
        l'EnhancedWorkflowMatcher (matching multi-modal amélioré).
        
        Args:
            screen_state: État d'écran enrichi (utilise l'état actuel si None)
            screenshot: Screenshot numpy array (capture l'écran si None)
            top_k: Nombre de meilleurs matches à retourner
            
        Returns:
            Liste des WorkflowMatch triés par score
        """
        try:
            # Capturer l'écran si nécessaire
            if screenshot is None:
                screenshot = self.current_frame
            
            if screenshot is None:
                screenshot = capture_screen()
            
            # Créer un screen_state si nécessaire
            if screen_state is None:
                # Utiliser EnrichedScreenCapture pour créer un état enrichi complet
                window_title = self.current_window or "Unknown"
                app_name = self.current_window or "Unknown"
                session_id = self.session_manager.current_session.session_id if self.session_manager.current_session else "unknown"
                
                # Capturer et enrichir avec le système UI Element Detection
                screen_state = self.enriched_capture.capture_and_enrich(
                    screenshot=screenshot,
                    session_id=session_id,
                    window_title=window_title,
                    app_name=app_name,
                    screen_resolution=(screenshot.shape[1], screenshot.shape[0]) if screenshot is not None else (1920, 1080),
                    detected_text=[],  # Pourrait être enrichi avec OCR
                    context_tags=[],
                    workflow_candidate=None,
                    save=False  # Ne pas sauvegarder automatiquement
                )
            
            # Récupérer les workflows connus
            workflows = self.workflow_detector.get_workflows()
            
            if not workflows:
                self.logger.log_action({
                    "action": "no_workflows_to_match",
                    "message": "Aucun workflow disponible pour le matching"
                })
                return []
            
            # Utiliser l'EnhancedWorkflowMatcher
            matches = self.enhanced_matcher.find_matching_workflows(
                screen_state=screen_state,
                screenshot=screenshot,
                workflows=workflows,
                top_k=top_k
            )
            
            # Logger les résultats
            if matches:
                self.logger.log_action({
                    "action": "enhanced_workflow_matching_completed",
                    "matches_found": len(matches),
                    "top_match": {
                        "workflow_id": matches[0].workflow_id,
                        "workflow_name": matches[0].workflow_name,
                        "composite_score": float(matches[0].composite_score),
                        "confidence": float(matches[0].confidence)
                    }
                })
                
                # Logger le feedback détaillé si disponible
                if matches[0].differences:
                    feedback_summary = matches[0].get_feedback_summary()
                    self.logger.log_action({
                        "action": "workflow_match_feedback",
                        "workflow_id": matches[0].workflow_id,
                        "feedback": feedback_summary
                    })
            else:
                self.logger.log_action({
                    "action": "no_workflow_matches_found",
                    "workflows_evaluated": len(workflows)
                })
            
            return matches
        
        except Exception as e:
            self.logger.log_action({
                "action": "enhanced_workflow_matching_error",
                "error": str(e)
            })
            import traceback
            self.logger.log_action({
                "action": "enhanced_workflow_matching_traceback",
                "traceback": traceback.format_exc()
            })
            return []
    
    def accept_current_suggestion(self):
        """Accepte la suggestion actuelle (appelé par la GUI sur Entrée)."""
        suggestion = self.suggestion_manager.accept_suggestion()
        
        if suggestion:
            self.logger.log_action({
                "action": "user_accepted_suggestion",
                "task_id": suggestion["task_id"]
            })
    
    def reject_current_suggestion(self):
        """Rejette la suggestion actuelle (appelé par la GUI sur Échap)."""
        rejected = self.suggestion_manager.reject_suggestion()
        
        if rejected:
            self.logger.log_action({
                "action": "user_rejected_suggestion"
            })
    
    def _index_workflow_in_faiss(self, workflow: Dict[str, Any]):
        """
        Indexe un workflow dans FAISS pour recherche de similarité.
        
        Args:
            workflow: Workflow à indexer
        """
        if self.new_embedding_manager is None or self.faiss_index is None:
            return
        
        try:
            # Récupérer les screenshots du workflow
            screenshots = workflow.get("screenshots", [])
            
            if not screenshots:
                return
            
            # Générer des embeddings pour chaque screenshot
            embeddings = []
            for screenshot in screenshots:
                # Convertir numpy BGR → PIL RGB
                if isinstance(screenshot, np.ndarray):
                    screenshot_rgb = cv2.cvtColor(screenshot, cv2.COLOR_BGR2RGB)
                    pil_image = Image.fromarray(screenshot_rgb)
                else:
                    pil_image = screenshot
                
                # Générer embedding
                embedding = self.new_embedding_manager.embed(pil_image)
                embeddings.append(embedding)
            
            # Indexer dans FAISS
            if embeddings:
                embeddings_array = np.array(embeddings)
                metadata = [{
                    "workflow_id": workflow.get("workflow_id"),
                    "workflow_name": workflow.get("name"),
                    "step_index": i
                } for i in range(len(embeddings))]
                
                self.faiss_index.add(embeddings_array, metadata)
                
                self.logger.log_action({
                    "action": "workflow_indexed_in_faiss",
                    "workflow_id": workflow.get("workflow_id"),
                    "num_embeddings": len(embeddings)
                })
        
        except Exception as e:
            self.logger.log_action({
                "action": "workflow_indexing_failed",
                "workflow_id": workflow.get("workflow_id"),
                "error": str(e)
            })
    
    def _add_positive_example_for_finetuning(self, suggestion: Dict[str, Any]):
        """
        Ajoute un exemple positif pour le fine-tuning quand une suggestion est acceptée.
        
        Args:
            suggestion: Suggestion acceptée
        """
        if not self.fine_tuner:
            return
        
        try:
            # Récupérer le screenshot de la suggestion
            screenshot = suggestion.get("screenshot")
            
            if screenshot is None:
                return
            
            # Convertir numpy BGR → PIL RGB si nécessaire
            if isinstance(screenshot, np.ndarray):
                screenshot_rgb = cv2.cvtColor(screenshot, cv2.COLOR_BGR2RGB)
                pil_image = Image.fromarray(screenshot_rgb)
            else:
                pil_image = screenshot
        
            # Ajouter comme exemple positif
            workflow_id = suggestion.get("workflow_id") or suggestion.get("task_id")
            
            self.fine_tuner.add_positive_example(
                image=pil_image,
                workflow_id=workflow_id,
                metadata={
                    "timestamp": time.time(),
                    "confidence": suggestion.get("confidence", 0.0)
                }
            )
            
            self.logger.log_action({
                "action": "positive_example_added_for_finetuning",
                "workflow_id": workflow_id,
                "total_examples": self.fine_tuner.get_stats()["total_examples"]
            })
        
        except Exception as e:
            self.logger.log_action({
                "action": "positive_example_add_failed",
                "error": str(e)
            })
    
    def _add_negative_example_for_finetuning(self, suggestion: Dict[str, Any]):
        """
        Ajoute un exemple négatif pour le fine-tuning quand une suggestion est rejetée.
        
        Args:
            suggestion: Suggestion rejetée
        """
        if not self.fine_tuner:
            return
        
        try:
            # Récupérer le screenshot de la suggestion
            screenshot = suggestion.get("screenshot")
            
            if screenshot is None:
                return
            
            # Convertir numpy BGR → PIL RGB si nécessaire
            if isinstance(screenshot, np.ndarray):
                screenshot_rgb = cv2.cvtColor(screenshot, cv2.COLOR_BGR2RGB)
                pil_image = Image.fromarray(screenshot_rgb)
            else:
                pil_image = screenshot
            
            # Ajouter comme exemple négatif
            workflow_id = suggestion.get("workflow_id") or suggestion.get("task_id")
            
            self.fine_tuner.add_negative_example(
                image=pil_image,
                workflow_id=workflow_id,
                metadata={
                    "timestamp": time.time(),
                    "confidence": suggestion.get("confidence", 0.0)
                }
            )
            
            self.logger.log_action({
                "action": "negative_example_added_for_finetuning",
                "workflow_id": workflow_id,
                "total_examples": self.fine_tuner.get_stats()["total_examples"]
            })
        
        except Exception as e:
            self.logger.log_action({
                "action": "negative_example_add_failed",
                "error": str(e)
            })
    
    def _save_embedding_system_on_shutdown(self):
        """
        Sauvegarde l'état du système d'embeddings à l'arrêt.
        """
        try:
            # Attendre la fin du fine-tuning en cours
            if self.fine_tuner:
                self.logger.log_action({
                    "action": "waiting_for_finetuning_completion"
                })
                self.fine_tuner.wait_for_training(timeout=30)
                
                # Sauvegarder le checkpoint
                self.fine_tuner.save_checkpoint(self._fine_tuner_checkpoint_name)
                
                self.logger.log_action({
                    "action": "fine_tuner_checkpoint_saved",
                    "name": self._fine_tuner_checkpoint_name,
                    "stats": self.fine_tuner.get_stats()
                })
            
            # Sauvegarder l'index FAISS
            if self.faiss_index:
                self.faiss_index.save(self._faiss_index_path)
                
                self.logger.log_action({
                    "action": "faiss_index_saved",
                    "path": self._faiss_index_path,
                    "stats": self.faiss_index.get_stats()
                })
            
            # Logger les statistiques du cache
            if self.new_embedding_manager:
                cache_stats = self.new_embedding_manager.get_stats()
                self.logger.log_action({
                    "action": "embedding_cache_stats",
                    "stats": cache_stats
                })
        
        except Exception as e:
            self.logger.log_action({
                "action": "embedding_system_save_failed",
                "error": str(e)
            })


if __name__ == "__main__":
    """Tests basiques de l'orchestrateur"""
    print("Test de l'Orchestrateur RPA Vision V2")
    print("=" * 50)
    
    # Créer des composants mock pour les tests
    from .embeddings_manager import EmbeddingsManager
    from .config import ensure_directories
    
    # S'assurer que les répertoires existent
    ensure_directories()
    
    # Initialiser les composants
    print("\n1. Initialisation des composants...")
    logger = Logger()
    embeddings_manager = EmbeddingsManager()
    learning_manager = LearningManager(embeddings_manager, logger, get_config())
    vision_utils = VisionUtils()
    llm_manager = LLMManager(logger=logger, fallback_to_vision=True)
    
    print("   ✓ Composants initialisés")
    
    # Créer l'orchestrateur
    print("\n2. Création de l'orchestrateur...")
    orchestrator = Orchestrator(
        learning_manager=learning_manager,
        vision_utils=vision_utils,
        llm_manager=llm_manager,
        logger=logger,
        gui=None  # Pas de GUI pour les tests
    )
    print("   ✓ Orchestrateur créé")
    
    # Test de capture de contexte
    print("\n3. Test capture_context()...")
    try:
        context = orchestrator.capture_context()
        if context:
            print(f"   ✓ Contexte capturé")
            print(f"     - Fenêtre: {context.get('window_title', 'N/A')}")
            print(f"     - Frame shape: {context.get('frame_shape', 'N/A')}")
        else:
            print("   ⚠ Capture de contexte a échoué (normal sans environnement graphique)")
    except Exception as e:
        print(f"   ⚠ Erreur: {e}")
    
    # Test de liste blanche
    print("\n4. Test de liste blanche...")
    orchestrator.add_to_whitelist("Dolibarr*")
    orchestrator.add_to_whitelist("Firefox*")
    whitelist = orchestrator.get_whitelist()
    print(f"   ✓ Liste blanche: {whitelist}")
    
    # Test de vérification liste blanche
    print("\n5. Test de vérification liste blanche...")
    test_windows = [
        "Dolibarr - Facturation",
        "Firefox - Mozilla",
        "Unknown Application"
    ]
    for window in test_windows:
        allowed = orchestrator._check_whitelist(window)
        status = "✓ Autorisé" if allowed else "✗ Bloqué"
        print(f"   {status}: {window}")
    
    # Test de métriques
    print("\n6. Test de métriques...")
    metrics = orchestrator.get_metrics()
    print(f"   ✓ Métriques:")
    for key, value in metrics.items():
        print(f"     - {key}: {value}")
    
    # Test de statut
    print("\n7. Test de statut...")
    status = orchestrator.get_status()
    print(f"   ✓ Statut:")
    print(f"     - Running: {status['running']}")
    print(f"     - Mode: {status['mode']}")
    print(f"     - Whitelist enforcement: {status['enforce_whitelist']}")
    
    print("\n✓ Tests basiques terminés!")
    print("\nNote: Pour tester la boucle cognitive complète, lancez orchestrator.run()")
    print("      dans un environnement avec interface graphique.")


