fais une vérification approfondie de tout les scenarios de cette partie du script et analyse les incohérence  et les erreurs :
#DEBUT DU SCRIPT
class CustomFileSystemModelForList(QFileSystemModel):
    """Modèle de système de fichiers personnalisé pour afficher les vignettes dans une vue de liste."""
    searchFinished = pyqtSignal(list) # results: List[str]
    rootPathChanged = pyqtSignal(str) # new_root_path: str
    #dataChanged = pyqtSignal(QModelIndex, QModelIndex) # index: QModelIndex 
    modelUpdated = pyqtSignal(QModelIndex)  # index: QModelIndex # Signal to notify that the model has been updated
    directoryLoaded = pyqtSignal(str) # path: str
    #thumbnailUpdated = pyqtSignal(str, QPixmap, bool) # file_path: str, thumbnail: QPixmap, success: bool
    thumbnailRequested = pyqtSignal(str)  #

    def __init__(self, parent=None, indexer=None, max_workers=10, cache_size=5000): 
        logger.debug("Beginning of __init__ function in CustomFileSystemModelForList")
        super().__init__(parent)
        self.allowed_extensions = ALLOWED_EXTENSIONS
        self.setRootPath(os.path.normpath(WATCHED_DIR))  
        # Connexion des signaux pour le débogage
        #self.searchFinished.connect(lambda results: print(f"Search finished signal received with results: {results}"))
        #self.rootPathChanged.connect(lambda path: print(f"Root path changed signal received with path: {path}"))
        #self.dataChanged.connect(lambda top_left, bottom_right: print(f"Data changed signal received for indexes: {top_left}, {bottom_right}"))
        #self.rootPathChanged.connect(lambda path: logger.debug(f"Root path changed: {path}"))
        #self.modelUpdated.connect(lambda index: print(f"Model updated signal received for index: {index}"))

        self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size)
        # Connexion du signal thumbnailReady au slot correspondant de ThumbnailManager
        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)
        # Connexion explicite du signal thumbnailRequested
        self.thumbnailRequested.connect(self.thumbnailManager.start_worker)
        # Connexion du signal requestProcessed au slot correspondant de FileViewer
        #self.processing_thread.requestProcessed.connect(self.handle_request_processed)

        self.parent = parent
        self.file_list = QListView()
        self.file_list.setStyleSheet("QListView::item { margin: 5px; }")
        self.criteria = ""    
        self._search_timer = QTimer(self)
        self._search_timer.setSingleShot(True)
        self._last_criteria = ""
        self.indexer = indexer
        if self.indexer:
            # Connect the indexUpdated signal with an anonymous lambda function to provide rootPath
            self.indexer.indexUpdated.connect(lambda: QTimer.singleShot(1000,  # Délai de 1 seconde
                                                                self.add_files_to_watcher))
        # Cache resources
        script_directory = os.path.dirname(os.path.abspath(__file__))
        icon_path = os.path.join(script_directory, "dossier.png")
        self.folder_icon = QIcon(QPixmap(icon_path))
        if self.folder_icon.isNull():
            logger.error(f"Failed to load folder icon from {icon_path}")

        #self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size) # Créer une instance de ThumbnailManager
        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)
        logger.debug("Successfully connected thumbnailReady signal to on_thumbnail_ready slot.")
        self.file_watcher = QFileSystemWatcher(self)  # Initialize file_watcher
        self.directoryLoaded.connect(self.on_directory_loaded)
        #self.file_watcher.fileChanged.connect(self.handle_file_change) # Connect to the signal
        logger.debug("End of __init__ function in CustomFileSystemModelForList")

    def event(self, event):
        return super().event(event)  # Revenir à la méthode par défaut

    def rowCount(self, parent=QModelIndex()):
        #logger.debug(f"rowCount called with parent: {parent}")
        return super().rowCount(parent)

    def index(self, *args, **kwargs):  
        #logger.debug(f"index called with row: {row}, column: {column}, parent: {parent}")
        try:
            #result = super().index(row, column, parent)
            #logger.debug(f"CustomFileSystemModelForList.index: Returning index: {result}")
            if len(args) == 1 and isinstance(args[0], str):
                # Appel avec path  
                path = args[0]  
                column = kwargs.get('column', 0)  
                #logger.debug(f"index called with path: {path}, column: {column}")  
                return super().index(path, column)  
                #return super().index(row, column, parent)
            elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], int):
                # Appel avec path et column  
                path = args[0]  
                column = args[1]  
                #logger.debug(f"index called with path: {path}, column: {column}")  
                return super().index(path, column)  
            else:
                # Appel avec row, column, parent  
                return super().index(*args, **kwargs)  

        except Exception as e:
            logger.error(f"CustomFileSystemModelForList.index: An error occurred: {e}")
            raise  # Remonte l'exception pour une gestion plus haut dans le code si nécessaire
    def indexFromPath(self, path):
        """Helper function to get the index from a path."""
        #logger.debug(f"Entering CustomFileSystemModelForList.indexFromPath with path: {path}")
        result = super().index(path)
        #logger.debug(f"CustomFileSystemModelForList.indexFromPath: Returning index: {result}")
        return result
    def add_files_to_watcher(self):
        """
        Ajoute les fichiers du répertoire surveillé (WATCHED_DIR) et de ses sous-répertoires 
        au `QFileSystemWatcher` pour surveiller les modifications.
        """
        logger.debug(f"Entering CustomFileSystemModelForList.add_files_to_watcher")
        try:
            if not self.file_watcher:
                logger.warning("file_watcher is not initialized.")
                return

            # Assurez-vous que le répertoire surveillé est correctement normalisé
            normalized_watched_dir = os.path.normpath(WATCHED_DIR)

            # Utiliser QDir pour lister les fichiers dans le répertoire surveillé
            dir = QDir(normalized_watched_dir)
            dir.setNameFilters([f"*{ext}" for ext in self.allowed_extensions])
            dir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot)

            # Obtenir tous les fichiers correspondants dans le répertoire et ses sous-répertoires
            file_info_list = dir.entryInfoList(QDir.AllEntries | QDir.NoDotAndDotDot, QDir.Name)

            allowed_file_paths = []
            for file_info in file_info_list:
                file_path_str = file_info.absoluteFilePath()
                #logger.debug(f"Checking file path: {file_path_str}")

                # Vérifiez si le fichier est caché ou système
                if file_info.isHidden() or (win32api.GetFileAttributes(file_path_str) & win32con.FILE_ATTRIBUTE_SYSTEM):
                    logger.debug(f"  - Skipped: Is hidden or system file")
                    continue

                # Vérifier l'extension du fichier
                extension = os.path.splitext(file_path_str)[1].lower()
                if extension not in self.allowed_extensions:
                    logger.debug(f"  - Skipped: Extension {extension} not allowed")
                    continue

                allowed_file_paths.append(file_path_str)

            if not allowed_file_paths:
                logger.warning("No valid file paths found to add to QFileSystemWatcher.")
                logger.warning(f"  - Root path: {WATCHED_DIR}")
                logger.warning(f"  - Allowed extensions: {self.allowed_extensions}")
            else:
                self.file_watcher.addPaths(allowed_file_paths)
                logger.info(f"{len(allowed_file_paths)} files added to QFileSystemWatcher.")

        except Exception as e:
            logger.critical(f"Unhandled exception in add_files_to_watcher: {e}", exc_info=True)
        logger.debug("Exiting CustomFileSystemModelForList.add_files_to_watcher")
    
    def on_directory_loaded(self, path):
        """
        Gère les actions à effectuer après le chargement d'un répertoire.
        """
        logger.info(f"on_directory_loaded / Répertoire chargé (CustomFileSystemModelForList) : {path}")
        if self.indexer: 
            normalized_path = os.path.normpath(path)
            if normalized_path.startswith(os.path.normpath(WATCHED_DIR)):
                self.indexer.buildIndex()
                logger.debug("add_files_to_watcher.on_directory_loaded(self, path) / self.indexer.buildIndex()  called")
            #self.add_files_to_watcher(path) 
        else:
            logger.warning("CustomFileSystemModelForList/ on_directory_loaded; Indexer non disponible dans CustomFileSystemModelForList.")

    def data(self, index, role=Qt.DisplayRole):
        #logger.debug(f"CustomFileSystemModelForList.data called for index {index} with role {role}")  
        if role in (Qt.SizeHintRole, Qt.FontRole):
            return None
        try:
            if not index.isValid():
                return None
            if role not in [Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole]:
                return None
            return self.standard_data_method(index, role)
        except Exception as e:
            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")
            return None
        finally:
            pass
            #logger.debug(f"CustomFileSystemModelForList.data finished for index {index} with role {role}")

    def standard_data_method(self, index, role):
        """Méthode standard pour fournir les données du modèle."""

        if role in (Qt.SizeHintRole, Qt.FontRole):  # Optimisation : ignorer ces rôles
            return None
        
        file_path = self.filePath(index)
        #logger.debug(f"CustomFileSystemModelForList.standard_data_method called for {file_path}, role: {role}")
        #logger.debug(f"standard_data_method: Current thread: {QThread.currentThread()}")
        try:
            if not index.isValid():
                return None
            #logger.debug(f"Entering role check for {file_path}")
            if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):
               #logger.debug(f"Unsupported role {role} for {file_path}, returning None")
               return None 
            #logger.debug(f"Processing role {role} for {file_path}")
            if role == Qt.DisplayRole:
                filename = os.path.basename(file_path)
                #logger.debug(f"DisplayRole: Original filename {filename}")
                if file_path in QDir.drives():
                    filename = file_path
                    #logger.debug(f"DisplayRole: Filename is a drive {filename}")
                longueur_max_titre = 25
                if len(filename) > longueur_max_titre:
                    filename = filename[:longueur_max_titre - 3] + "..."
                return filename

            elif role == Qt.ToolTipRole:
                #logger.debug(f"ToolTipRole: Returning file path {file_path}")
                return file_path

            elif role == Qt.DecorationRole and index.column() == 0:
                #logger.debug(f"CustomFileSystemModelForList: Standard data method called ; Requesting thumbnail for {file_path} , role: {role}")
                if os.path.isdir(file_path):
                    #logger.debug(f"DecorationRole: {file_path} is a directory, returning folder icon")
                    return self.folder_icon
                elif file_path.lower().endswith(('.dxf', '.png', '.jpg', '.jpeg')):
                    thumbnail = self.thumbnailManager.cache.get(file_path)  # Vérifier le cache
                    if thumbnail is None:
                        with QMutexLocker(self.thumbnailManager.processing_thread.pending_requests_lock):  # Utiliser le verrou spécifique
                            # Vérifier les requêtes en attente dans ThumbnailManager
                            if file_path not in self.thumbnailManager.processing_thread.pending_requests: 
                                self.thumbnailManager.processing_thread.pending_requests.add(file_path)
                                if QThread.currentThread() == QApplication.instance().thread():
                                    self.thumbnailRequested.emit(file_path)
                                else:
                                    # Émettre le signal depuis le thread principal en utilisant un timer
                                    QTimer.singleShot(0, lambda: self.thumbnailRequested.emit(file_path))
                        return QPixmap()  # Retourner une image vide en attendant
                    else:
                        return thumbnail.scaled(160, 120, Qt.KeepAspectRatio, Qt.SmoothTransformation)  # Redimensionner la vignette
                else:
                    return None  # Aucun icône pour les autres types de fichiers
        except Exception as e:
            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")
            return None
 
    @pyqtSlot(str, QPixmap, bool)
    def on_thumbnail_ready(self, file_path, thumbnail, success):
        """Slot appelé lorsque la vignette est prête."""
        #logger.debug(f"CustomFileSystemModelForList: on_thumbnail_ready() - file_path: {file_path}, success: {success}")
        if success:
            self.update_thumbnail_cache(file_path, thumbnail)
        else:
            logger.warning(f"on_thumbnail_ready: Échec de la génération de la vignette pour {file_path}")
    @pyqtSlot(str, bool)
    def handle_request_processed(self, file_path, success):
        """Gère le signal requestProcessed du ProcessingThread."""
        if not success:
            # Gérez l'échec de la génération de la vignette (réessayez, affichez un message d'erreur, etc.)
            logger.error(f"CustomFileSystemModelForList: Failed to generate thumbnail for {file_path}")
        else:
            logger.debug(f"CustomFileSystemModelForList: Thumbnail successfully generated for {file_path}")

    @pyqtSlot(str, QPixmap)
    def update_thumbnail_cache(self, file_path, thumbnail):
        """Met à jour le cache des vignettes et émet le signal dataChanged dans le thread principal."""
        #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Entrée avec file_path: {file_path}")
        
        # Utiliser QMutexLocker pour assurer le déverrouillage automatique
        with QMutexLocker(self.thumbnailManager.lock):
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Verrou acquis")
            if file_path not in self.thumbnailManager.cache or self.thumbnailManager.cache[file_path] != thumbnail:
                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette absente du cache ou différente, mise à jour en cours...")
                self.thumbnailManager.cache[file_path] = thumbnail
                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette mise à jour dans le cache pour {file_path}")
            else:
                logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette déjà présente dans le cache, aucune mise à jour nécessaire.")

        # Émettre le signal dataChanged pour indiquer que les données ont changé
        index = self.indexFromPath(file_path)
        if index.isValid():
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Émission du signal dataChanged pour {file_path}")  
            self.dataChanged.emit(index, index, [Qt.DecorationRole])  # Émettre le signal dataChanged pour l'élément spécifique
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Signal dataChanged émis pour {file_path}")
        else:
            logger.warning(f"CustomFileSystemModelForList: update_thumbnail_cache() - Index invalide pour {file_path}")

class CustomFileSystemModelForTree(QFileSystemModel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Cache the folder icon for performance
        script_directory = os.path.dirname(os.path.abspath(__file__))
        icon_path = os.path.join(script_directory, "dossier.png")
        self.folder_icon = QIcon(QPixmap(icon_path))
        if self.folder_icon.isNull():
            logger.error(f"Failed to load folder icon from {icon_path}")

    def data(self, index, role):
        if role == Qt.DecorationRole and index.column() == 0:
            # Get the full path of the file or directory at the index
            file_path = self.filePath(index)
            # Check if it's a directory
            if os.path.isdir(file_path):
                return self.folder_icon
        elif role == Qt.DisplayRole:
            # Get the default display text
            original_text = super().data(index, role)
            return original_text
        return None


class TreeProxyModel(QSortFilterProxyModel):
    def filterAcceptsRow(self, source_row, source_parent):
        index = self.sourceModel().index(source_row, 0, source_parent)
        if self.sourceModel().isDir(index):
            #logger.debug(f"Folder accepted for display: {index.data()}")
            return True  # Accept the entry as it is a folder
        #logger.debug(f"Entry rejected (not a folder): {index.data()}")
        return False  # Reject the entry as it is not a folder

class FileListProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(FileListProxyModel, self).__init__(parent)
        self.filter_text = ""
        self.filters = ['.dxf', '.png', '.jpg', '.jpeg']
        self.recursiveSearchEnabled = True  # Enabled by default   
        self.searchPaths = []  # Paths to use for filtering results     

    def setFilterText(self, text):
        """Set the filter text and invalidate the current filter."""
        self.filter_text = text.lower()
        self.invalidateFilter()
        logger.debug(f"Filter text set: {self.filter_text}")   

    def setSearchPaths(self, paths):
        """Set the search paths for filtering."""
        self.searchPaths = paths
        self.invalidateFilter()
        logger.debug(f"Search paths set: {self.searchPaths}") 

    def setRecursiveSearchEnabled(self, enabled):
        """Enable or disable recursive search."""
        self.recursiveSearchEnabled = enabled
        self.invalidateFilter()
        logger.debug(f"Recursive search {'enabled' if enabled else 'disabled'}.")
    
    def filterAcceptsRow(self, sourceRow, sourceParent):
        sourceModel = self.sourceModel()
        if not sourceModel:
            return False

        sourceIndex = sourceModel.index(sourceRow, 0, sourceParent)
        if not sourceIndex.isValid():
            return False

        # Standard filtering logic
        filepath = sourceModel.filePath(sourceIndex).lower()
        isDirectory = sourceModel.isDir(sourceIndex)
        isAcceptableFile = isDirectory or any(filepath.endswith(ext) for ext in self.filters)
        filename = sourceModel.fileName(sourceIndex).lower()

        if self.searchPaths:
            if not any(filepath.startswith(path.lower()) for path in self.searchPaths):
                #logger.debug(f"Path {filepath} rejected by search paths.")
                return False

        if self.filter_text:
            filename = self.sourceModel().data(sourceIndex, Qt.DisplayRole).lower()
            textMatch = self.filter_text in filename or self.filter_text in filepath
        else:
            textMatch = True

        # Final decision for row acceptance
        if isAcceptableFile and textMatch:
            return True

        # If recursive search is enabled and the index is a directory, check subdirectories
        if self.recursiveSearchEnabled and isDirectory:
            rowCount = sourceModel.rowCount(sourceIndex)
            logger.debug(f"Recursive search enabled, number of subdirectories for {filepath}: {rowCount}")
            for childRow in range(rowCount):
                if self.filterAcceptsRow(childRow, sourceIndex):
                    logger.debug(f"Recursive acceptance for {filepath} due to subdirectory at row {childRow}")
                    return True

        #logger.debug(f"Rejected {filepath}")
        return False

class SearchResultsProxyModel(QAbstractProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._search_results = []  # Stocke les chemins des fichiers trouvés
        self._path_to_row = {}     # Mapping from path to row index
        self._cached_source_indexes = {}  # Cache pour les index source

    def setSearchResults(self, results):
        """Met à jour les résultats de la recherche et signale les changements au modèle."""
        logger.debug(f"SearchResultsProxyModel.setSearchResults called with {len(results)} results.")
        self.beginResetModel()
        self._search_results = [os.path.normcase(r) for r in results]
        self._path_to_row = {path: idx for idx, path in enumerate(self._search_results)}
        self._cached_source_indexes.clear()  # Vider le cache car les résultats ont changé
        self.endResetModel()
        logger.debug("setSearchResults: Model reset completed.") 
    def clearSearchResults(self):
        """Nettoie les résultats de recherche et réinitialise le modèle."""
        logger.debug("SearchResultsProxyModel.clearSearchResults called.")
        self.beginResetModel()
        self._search_results = []
        self._path_to_row = {}
        self._cached_source_indexes.clear()
        self.endResetModel()
        logger.debug("clearSearchResults: Model reset completed.")

    def rowCount(self, parent=QModelIndex()):
        """Retourne le nombre de résultats de recherche."""
        if parent.isValid():  # Si l'index parent est valide, on est dans un noeud enfant, donc 0 enfant
            return 0
        return len(self._search_results)

    def filePath(self, index):
        """Retourne le chemin du fichier pour l'index donné, en déléguant au modèle source."""
        source_index = self.mapToSource(index)
        return self.sourceModel().filePath(source_index)

    def columnCount(self, parent=QModelIndex()):
        """Retourne le nombre de colonnes du modèle source."""
        return self.sourceModel().columnCount(parent) if self.sourceModel() else 0

    def index(self, row, column, parent=QModelIndex()):
        """Retourne l'index du proxy pour la ligne, la colonne et le parent donnés."""
        if not parent.isValid() and 0 <= row < len(self._search_results):
            source_index = self.sourceModel().indexFromPath(self._search_results[row])
            if source_index.isValid():
                return self.createIndex(row, column, source_index)
            else:
                logger.error(f"Invalid source index for path: {self._search_results[row]}")                     
        return QModelIndex()

    def parent(self, index):
        """Retourne l'index parent, toujours invalide pour ce modèle."""
        return QModelIndex()

    def mapFromSource(self, sourceIndex):
        """Retourne l'index du proxy correspondant à l'index source."""
        logger.debug(f"SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: {sourceIndex}")
        
        if sourceIndex.isValid():
            source_file_path = os.path.normcase(os.path.normpath(self.sourceModel().filePath(sourceIndex)))
            logger.debug(f"Source File Path: {source_file_path}")
            row = self._path_to_row.get(source_file_path)
            logger.debug(f"Mapped Row: {row}")
            if row is not None:
                return self.createIndex(row, sourceIndex.column())
            else:
                logger.warning("SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.")
                return QModelIndex()
        return QModelIndex()

    def mapToSource(self, proxyIndex):
        """Retourne l'index source correspondant à l'index du proxy."""
        #logger.debug(f"FileListProxyModel.mapToSource appelé avec proxyIndex: {proxyIndex}")
        if proxyIndex.isValid() and 0 <= proxyIndex.row() < len(self._search_results):
            source_path = self._search_results[proxyIndex.row()]
            if source_path in self._cached_source_indexes:
                return self._cached_source_indexes[source_path]
            source_index = self.sourceModel().indexFromPath(source_path)
            if source_index.isValid():
                self._cached_source_indexes[source_path] = source_index
            return source_index
        return QModelIndex()
    
    def data(self, index, role=Qt.DisplayRole):
        """Retourne les données pour l'index donné et le rôle."""
        if not index.isValid():
            logger.debug("Invalid index in data method.")
            return None

        if not self.sourceModel():
            logger.error("Source model is not set.")
            return None

        source_index = self.mapToSource(index)  # Obtenir un nouvel index source valide
        if not source_index.isValid():
            logger.debug("Invalid source index in data method.")
            return None

        return self.sourceModel().data(source_index, role)
    def setSourceModel(self, source_model):
        logger.debug("SearchResultsProxyModel: setSourceModel called")
        super().setSourceModel(source_model)
        if source_model is not None:
            source_model.dataChanged.connect(self.sourceDataChanged)

    def sourceDataChanged(self, sourceTopLeft, sourceBottomRight, roles):
        logger.debug("Start SearchResultsProxyModel.sourceDataChanged")
        if not self._search_results:  # Vérifie si le modèle proxy est actif
            logger.debug("SearchResultsProxyModel.sourceDataChanged: Skipping dataChanged emission, model is not active.")
            return
        #logger.debug(f"SearchResultsProxyModel: sourceDataChanged called with sourceTopLeft={sourceTopLeft}, sourceBottomRight={sourceBottomRight}, roles={roles}")
        # Mapper les index source vers les index du proxy
        proxyTopLeft = self.mapFromSource(sourceTopLeft)
        proxyBottomRight = self.mapFromSource(sourceBottomRight)
        #logger.debug(f"SearchResultsProxyModel: Mapped proxyTopLeft={proxyTopLeft}, proxyBottomRight={proxyBottomRight}")

        if proxyTopLeft.isValid() and proxyBottomRight.isValid():
            #logger.debug("SearchResultsProxyModel: Emitting dataChanged from proxy model")
            # Émettre le signal dataChanged du proxy avec les rôles appropriés
            self.dataChanged.emit(proxyTopLeft, proxyBottomRight, roles)
        else:
            logger.debug("SearchResultsProxyModel.sourceDataChanged: Skipping dataChanged emission, invalid mapping.")
    
class ThumbnailWorkerSignals(QObject):
    """Classe pour regrouper les signaux du ThumbnailWorker."""
    thumbnailReady = pyqtSignal(str, QPixmap, bool)  # Chemin du fichier, vignette, succès
    finished = pyqtSignal()
    errorOccurred = pyqtSignal(str, Exception)  # Chemin du fichier, exception
 
class ThumbnailWorker(QRunnable):
    """Worker pour générer les vignettes de manière asynchrone."""

    def __init__(self, file_path, cache):
        super().__init__()
        self.signals = ThumbnailWorkerSignals()  # Instance pour gérer les signaux
        self.file_path = file_path
        self.cache = cache  # Référence au cache partagé
        self._cancel_requested = False

    @pyqtSlot()
    def cancel(self):
        """Annule la génération de la vignette si elle est en cours."""
        self._cancel_requested = True

    @pyqtSlot()  # Supprimer l'argument file_path
    def run(self):  # Méthode exécutée dans un thread séparé pour générer la vignette
        #logger.debug(f"ThumbnailWorker: run() Démarrage pour {self.file_path}")
        if self._cancel_requested:
            logger.warning(f"ThumbnailWorker: run() cancelled for {self.file_path}")
            return

        # Vérifier si la vignette est déjà dans le cache
        if self.file_path in self.cache:
            #logger.debug(f"ThumbnailWorker: Thumbnail found in cache for {self.file_path}")
            self.signals.thumbnailReady.emit(self.file_path, self.cache[self.file_path], True)
            self.signals.finished.emit()
            return
        
        try:
            #logger.debug(f"ThumbnailWorker: Generating thumbnail for {self.file_path}")
            thumbnail = self.generate_thumbnail(self.file_path)
            success = thumbnail is not None and not thumbnail.isNull()
            #logger.debug(f"ThumbnailWorker: Thumbnail generated for {self.file_path}, success: {success}")

            if success:
                self.signals.thumbnailReady.emit(self.file_path, thumbnail, success)
        except (FileNotFoundError, ValueError, CancelledError) as e:
            logger.error(f"ThumbnailWorker: Error generating thumbnail for {self.file_path}: {e}")
            self.signals.errorOccurred.emit(self.file_path, e)
        finally:
            self.signals.finished.emit()

#SUITE DE LA CLASS
class FileViewer(QMainWindow):
    folderChanged  = pyqtSignal(str)
    fileSelected = pyqtSignal(str)
    rechercheChanged = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.scale_factor = 1  # Initialiser avec une valeur par défaut
        self.indexeur = FileIndexer()
        self.setWindowTitle("Souf Dxf Explorer")
        self.setGeometry(100, 100, 800, 600)

        self.model = CustomFileSystemModelForList(self)
        self.isSearchThreadRunning = False
        self._in_search_mode = False

        # Search indicator
        self.search_indicator = QLabel("Recherche en cours...", self)
        self.statusBar().addWidget(self.search_indicator)
        self.search_indicator.hide()

        self.current_folder = os.path.normpath(os.path.expanduser("~")) 
        self.previous_folder = None
        #self.current_folders = [QDir.homePath()]  # Initialize the current_folders attribute
        #self.rootPaths = ["Y:/"]
        #logger.debug(f"[Init] Current folders initialized to: {self.current_folders}")
        self.setupModelsAndView()
        self.setupUIComponents()
        self.setupObservers()
        self.connect_signals()
        self.indexeur.indexUpdated.connect(self.on_indexing_finished) 
        self.indexeur.indexerError.connect(self.on_indexing_error) # connecter au slot
        self.indexeur.indexingProgress.connect(self.update_indexing_progress)  # Connecter le signal
        # Démarrer l'indexation asynchrone après l'initialisation de l'interface
        #QTimer.singleShot(3000, lambda: (self.indexeur.buildIndexAsync(), logger.info(" Fileviewer.__init__(self) (self.indexeur.buildIndexAsync())Lambda function in QTimer.singleShot executed")))
        #logger.info("FileViewer / Asynchronous indexing initiated.") 
    
    def connect_signals(self):
        if not hasattr(self, 'signals_connected'):
            # Connexions pour la mise à jour de la vue de la liste de fichiers
            self.file_list.model().dataChanged.connect(self.update_file_list_view)
            self.model.modelUpdated.connect(self.update_file_list_view)
            logger.info("Connection: file_list.clicked -> display_selected_file")
            self.file_list.clicked.connect(self.display_selected_file)
            logger.info("Connection: file_list.doubleClicked -> on_folder_double_clicked")
            self.file_list.doubleClicked.connect(self.on_folder_double_clicked)

            # Connexions pour la recherche
            self.SearchWorker.searchFinished.connect(self.onSearchFinished)
            self.searchThread.started.connect(self.SearchWorker.performSearch)

            # Connexions pour les temporisateurs et les champs de texte
            self.search_timer.timeout.connect(self.launchSearch)
            self.search_box.textChanged.connect(self.launch_delayed_search)

            # Configuration du proxy de recherche
            self.search_results_proxy_model = SearchResultsProxyModel(self)
            self.search_results_proxy_model.setSourceModel(self.model)  # Définir le modèle source du proxy

            self.signals_connected = True

    def setupUIComponents(self):
        self.pathLabel = QLabel("Path:")
        self.setup_ui()
        self.setupMenu()
        self.setupToolbar()

    def setupModelsAndView(self):
        #self.indexeur = FileIndexer()
        self.rootPaths = [os.path.normpath("")]
        for path in self.rootPaths:
            if os.path.exists(path):
                print(f"Le chemin {path} est valide.")
            else:
                #print(f"Le chemin {path} n'est pas valide.")
                print(f"Le chemin path est:{path} ")
        self.tree_model = CustomFileSystemModelForTree()
        self.file_list_model = CustomFileSystemModelForList(self, self.indexeur)
        self.file_list = QListView()
        self.setup_tree_dock_widget()
        self.setup_proxy_tree_model()

        # Add SearchResultsProxyModel
        self.search_results_proxy_model = SearchResultsProxyModel(self)
        self.search_results_proxy_model.setSourceModel(self.file_list_model)

        # Connect signal modelReset
        self.search_results_proxy_model.modelReset.connect(self.file_list.reset)
        # Connect signals layoutChanged and dataChanged
        self.search_results_proxy_model.layoutChanged.connect(self.file_list.reset)
        self.search_results_proxy_model.dataChanged.connect(self.file_list.reset)

        # Configure FileListProxyModel to use SearchResultsProxyModel as source
        self.file_list_proxy = FileListProxyModel()
        self.setup_file_list_proxy()
        self.file_list_proxy.setSourceModel(self.file_list_model)

        # Configure list view to use the proxy file_list_proxy
        self.file_list.setModel(self.file_list_proxy)

        self.file_list_model.directoryLoaded.connect(self.file_list_model.on_directory_loaded)

    def setupObservers(self):
        self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)
        self.searchThread = QThread()
        self.SearchWorker.moveToThread(self.searchThread)

        self.FileMonitor = FileMonitor(self.indexeur, self.SearchWorker.allowed_extensions) 
        self.observer = Observer()
        self.observer.schedule(self.FileMonitor, path=os.path.normpath(WATCHED_DIR), recursive=True)   # Surveiller WATCHED_DIR
        self.observer.start()

        self.search_timer = QTimer(self)
        self.search_timer.setSingleShot(True)

        self.update_directory_label(f"Current folder path: {self.tree_model.rootPath()}")

    def displayResults(self, success, results):
        logger.debug("Beginning of displayResults function.")

        # Arrêter le profilage et afficher les résultats
        #if hasattr(self, 'profiler'):  # Vérifier si le profilage a été activé
            #self.profiler.disable()
            #s = io.StringIO()
            #ps = pstats.Stats(self.profiler, stream=s).sort_stats('cumulative')
            #ps.print_stats()
           # logger.debug(f"Profiling results:\n{s.getvalue()}")

        # Eviter les appels récursifs ou redondants
        if hasattr(self, 'display_results_called') and self.display_results_called:
            logger.debug("displayResults already called, skipping.")
            return

        self.display_results_called = True
        self.isSearchThreadRunning = False
        self.search_indicator.hide()

        # Gérer les différents cas de figure
        if not success:
            logger.debug("Handling search failure.")
            self.handleSearchFailure()
            self._in_search_mode = False  # Assurez de quitter le mode recherche en cas d'échec
        elif not results:
            logger.debug("Handling no results.")
            self.handleNoResults()
            self._in_search_mode = False
        elif not self.search_box.text().strip():
            logger.debug("Search box is empty, resetting list view.")
            self.resetListView()
            self._in_search_mode = False
        else:
            # Afficher les résultats de la recherche
            self._in_search_mode = True
            logger.debug("Updating proxy model with search results.")
            self.search_results_proxy_model.setSearchResults(results)  # Mettre à jour le proxy
            # Si le modèle actuel de la vue n'est pas le modèle de résultats, le définir
            if self.file_list.model() != self.search_results_proxy_model:
                self.file_list.setModel(self.search_results_proxy_model)
            logger.debug("SearchResultsProxyModel set as the current model for file_list.")
            # Rafraîchir la vue
            #self.file_list.reset()
            #logger.debug(f"displayResults: ListView reset. Number of items in view: {self.file_list.model().rowCount()} ")
            # Forcer le focus et mettre à jour la vue
            #self.file_list.setFocus()
            #self.file_list.update()
            #self.file_list.repaint()
            # (Facultatif) Sélectionner le premier élément si souhaité
            if results:
                first_result_index = self.search_results_proxy_model.index(0, 0)
                if first_result_index.isValid():
                    self.file_list.setCurrentIndex(first_result_index)
            logger.debug("Logging search results.")
            self.logSearchResults(results)

        self.display_results_called = False
        logger.debug("End of displayResults function.")

    def launch_delayed_search(self):
        logger.debug("Slot activated: launch_delayed_search")
        search_criteria = self.search_box.text().strip()
        if search_criteria:
            logger.info("Search criteria modified. Waiting before launching search...")
            if not self.previous_folder: 
                self.previous_folder = self.current_folder  # Sauvegarder le dossier actuel 
            self.search_timer.stop()  # Stop the existing timer before restarting
            if self.search_timer.isActive():
                self.search_timer.stop()
            self.search_timer.start(1000)  # Delay of 1000 ms
        else:
            logger.info("Search bar empty. Displaying current folder.")
            self.exit_search_mode()
        logger.debug("End of launch_delayed_search")
    def launchSearch(self):
        logger.debug("Function launchSearch start")
        criteria = self.search_box.text().strip()
        criteria_type = self.search_criteria_type.currentText().lower().replace(" ", "_")
        logger.debug(f"Search criteria: {criteria}, criteria_type: {criteria_type}")
        logger.debug(f"Current root path: {self.file_list_model.rootPath()}")
        logger.debug(f"WATCHED_DIR: {WATCHED_DIR}")
        if not criteria and criteria_type == "filename":
            logger.debug("Search criteria is empty, exiting search.")
            #QMessageBox.warning(self, "Warning", "Search criteria cannot be empty for filename search.")
            self.exit_search_mode()
            return

        if self.isSearchThreadRunning:
            logger.info("Search already running, stopping current search.")
            self.SearchWorker.running = False  # Stop the thread gracefully
            self.searchThread.quit()
            self.searchThread.wait()
        # Sauvegarder le dossier actuel avant de lancer la recherche  
        if not self.previous_folder:  
            self.previous_folder = self.current_folder  
            logger.debug(f"Saved current folder to previous_folder: {self.previous_folder}") 

        self.isSearchThreadRunning = True
        self.SearchWorker.setCriteria(criteria, criteria_type)
        self.SearchWorker.setDateRange(self.start_date.date(), self.end_date.date())

        # Assurez-vous que rootPath est bien défini
        current_root_path = self.file_list_model.rootPath()
        if current_root_path:
            self.SearchWorker.setBasePath(current_root_path)
            #self.profiler = cProfile.Profile()
            #self.profiler.enable()
            #self.searchThread.started.connect(self.SearchWorker.performSearch)
            self.searchThread.start()
            self.search_indicator.show()
            logger.debug("Function launchSearch end")
        else:
            logger.error("Current root path is not set. Cannot start search.")
            self.isSearchThreadRunning = False
    def onSearchFinished(self, success, results):
        logger.debug("Search finished with success: {}".format(success))
        self.isSearchThreadRunning = False
        if self.searchThread.isRunning():
            logger.debug("Stopping search thread.")
            self.searchThread.quit()
            self.searchThread.wait()  # Ensure the thread is fully stopped
            logger.debug("Search thread stopped.")
        logger.debug("Search thread stopped and resources cleaned up.")
        self.handle_search_completion(success, results)
        logger.debug("End of onSearchFinished function.")

    def handle_search_completion(self, success, results):
        logger.debug("Function handle_search_completion start")
        if success:
            logger.info("Search completed successfully.")
            self.displayResults(success, results)
            # Connecter le proxy de recherche à la vue SI en mode recherche
            if self._in_search_mode:  
                self.file_list.setModel(self.search_results_proxy_model)
            else:
                # Revenir au proxy normal si on n'est plus en recherche
                self.file_list.setModel(self.file_list_proxy) 
        else:
            logger.error("Search failed, attempting to restart.")
            logger.debug(f"Search results (should be empty on failure): {results}")
            # Resetting the thread to allow for new searches
            self.searchThread.quit()
            self.searchThread.wait()  # Wait until the thread cleanly finishes
            self.searchThread = QThread()  # Recreate the thread for future use
            self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)
            self.SearchWorker.moveToThread(self.searchThread)
            self.searchThread.started.connect(self.SearchWorker.performSearch)
            self.SearchWorker.searchFinished.connect(self.onSearchFinished)

        logger.debug("Function handle_search_completion end")

    def handleSearchFailure(self):
        logger.debug("Beginning of handleSearchFailure function.")
        QMessageBox.warning(self, "Search Failed", "The search failed or was stopped without results.")
        self.resetListView()
        logger.debug("End of handleSearchFailure function.")

    def handleNoResults(self):
        logger.debug("Beginning of handleNoResults function.")
        QMessageBox.information(self, "No Results", "No results to display.")
        self.resetListView()
        logger.debug("End of handleNoResults function.")

    def logSearchResults(self, results):
        logger.debug("Beginning of logSearchResults function.")
        for result in results[:min(5, len(results))]:
            logger.debug(f"Search result: {result}")
        logger.debug("End of logSearchResults function.")
    
    def exit_search_mode(self):
        logger.debug("Function exit_search_mode start")
        self._in_search_mode = False
        # Déconnecter le SearchResultsProxyModel de la vue
        if self.file_list.model() == self.search_results_proxy_model:
            self.file_list.setModel(self.file_list_proxy)
            logger.debug("SearchResultsProxyModel disconnected from file_list view.")
        self.search_results_proxy_model.clearSearchResults()
        # Restaurer le modèle principal
        self.file_list.setModel(self.file_list_proxy)
        logger.debug("File_list view set to FileListProxyModel.")
        self.file_list_proxy.invalidateFilter()
        if self.previous_folder:  
            logger.debug(f"[exit_search_mode] Previous folder: {self.previous_folder}") 
            self.current_folder = self.previous_folder  # Restaurer le dossier précédent  
            self.previous_folder = None  # Réinitialiser previous_folder  
            self.file_list_model.setRootPath(self.current_folder)
            root_index = self.file_list_model.indexFromPath(self.current_folder)
            if root_index.isValid():
                if hasattr(self, 'file_list_proxy'):
                    self.file_list_proxy.setSourceModel(self.file_list_model)
                    self.file_list.setModel(self.file_list_proxy)  
                    proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                    if proxy_root_index.isValid():
                       self.file_list.setRootIndex(proxy_root_index)
                    else:
                        logger.error(f"Invalid proxy root index: {proxy_root_index}")
                else:
                    self.file_list.setModel(self.file_list_model) 
                    self.file_list.setRootIndex(root_index)
            else:
                logger.error(f"Invalid root index: {root_index}")
            self.update_directory_label(f"Current folder: {self.current_folder}")  
            logger.info(f"[exit_search_mode] Current folder updated to: {self.current_folder}") 
        else:
            logger.warning("[exit_search_mode] No previous folder stored. Using default path.")  
            default_folder = QDir.homePath()
            self.file_list_model.setRootPath(default_folder)  
            root_index = self.file_list_model.index(default_folder)

            if hasattr(self, 'file_list_proxy'):
                self.file_list_proxy.setSourceModel(self.file_list_model)
                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                self.file_list.setRootIndex(proxy_root_index)
            else:
                self.file_list.setRootIndex(root_index)

            self.update_directory_label(f"Current folder: {default_folder}")
            logger.info(f"[Update] Current folder updated to: {default_folder}")

        logger.debug("Function exit_search_mode end")

    def resetListView(self):
        """Réinitialise la vue de liste pour afficher le contenu du répertoire courant."""
        try:
            self._in_search_mode = False  # Sortir du mode recherche
            self.file_list.setModel(self.file_list_proxy)  # Rétablir le proxy principal

            # Vérifier si current_folder est valide avant de continuer
            if self.current_folder and os.path.exists(self.current_folder):
                logger.debug(f"[resetListView] Current folder: {self.current_folder}")
                self.updateFileListModel(self.current_folder)  
                self.update_file_list_view()
                self.checkDisplayedFiles(self.file_list_model)
                self.updateDirectoryLabel()
            else:
                self.handleNoCurrentFolder()

            self.file_list.reset()  # Rafraîchir la vue après les modifications

        except Exception as e:
            self.handleResetListViewError(e)
    def updateFileListModel(self, folder_path):
        """Met à jour le chemin racine du modèle de liste de fichiers.

        Args:
            folder_path (str): Le nouveau chemin racine pour le modèle.
        """
        logger.debug("Fonction updateFileListModel: Début")
        if not isinstance(folder_path, str) or not folder_path:
            logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")
            QMessageBox.warning(self, "Erreur", "Le chemin du dossier doit être une chaîne de caractères valide.")
            return
        if not os.path.isdir(folder_path):
            logger.error(f"Le chemin fourni n'est pas valide ou n'est pas un dossier: {folder_path}")
            QMessageBox.warning(self, "Erreur", "Le chemin fourni n'est pas un dossier valide.")
            return
        self.file_list_model.setRootPath(folder_path)
        logger.info(f"Chemin racine du modèle de liste de fichiers mis à jour: {folder_path}")
        logger.debug("Fonction updateFileListModel: Fin")

    def update_file_list_view(self, folder_path=None, index=None):
        logger.debug("Fonction update_file_list_view: Début")
        try:
            root_index = None
            if folder_path is not None:
                if isinstance(folder_path, str):
                    logger.debug(f"Chemin du dossier fourni: {folder_path}")
                    self.updateFileListModel(folder_path)
                    root_index = self.file_list_model.indexFromPath(folder_path)
                    if not root_index.isValid():
                        logger.error("Index racine obtenu à partir du chemin est invalide.")
                        return
                else:
                    logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")
                    return
            elif index is not None:
                logger.debug(f"Index fourni: {index}")
                if not index.isValid():
                    logger.error("Index non valide reçu pour la mise à jour.")
                    return
                root_index = index if not isinstance(index.model(), QSortFilterProxyModel) else index.model().mapToSource(index)
                if not root_index.isValid():
                    logger.error("Index racine obtenu à partir de l'index fourni est invalide.")
                    return
            else:
                if self.current_folder:  
                    folder_path = self.current_folder  
                    logger.debug(f"Utilisation du dossier actuel: {folder_path}")  
                    self.updateFileListModel(folder_path)  
                    root_index = self.file_list_model.indexFromPath(folder_path)
                    if not root_index.isValid():
                        logger.error("Index racine obtenu à partir du dossier actuel est invalide.")
                        return
                else:
                    logger.error("Aucun dossier courant défini.")  
                    return  
            if self._in_search_mode:
                # Mode recherche : le modèle est déjà SearchResultsProxyModel
                if self.file_list.model() != self.search_results_proxy_model:
                    self.file_list.setModel(self.search_results_proxy_model)
                    logger.debug("Switching to SearchResultsProxyModel in update_file_list_view.")
            else:
                # Mode normal
                if self.file_list.model() != self.file_list_proxy:
                    self.file_list.setModel(self.file_list_proxy)
                    logger.debug("Switching to FileListProxyModel in update_file_list_view.")

            if hasattr(self, 'file_list_proxy'):
                self.file_list_proxy.setSourceModel(self.file_list_model)
                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                if proxy_root_index.isValid():
                    self.file_list.setRootIndex(proxy_root_index)
                    logger.info(f"Vue de la liste mise à jour avec proxy. Index source: {root_index}, Index proxy: {proxy_root_index}")
                else:
                    logger.error("Erreur lors de la mise à jour de la vue avec proxy: L'index proxy n'est pas valide.")
            else:
                if root_index.isValid():
                    self.file_list.setRootIndex(root_index)
                    logger.info(f"Vue de la liste mise à jour sans proxy. Index source: {root_index}")
                else:
                    logger.error("Erreur lors de la mise à jour de la vue sans proxy: L'index racine n'est pas valide.")
        except Exception as e:
            logger.error(f"Erreur dans update_file_list_view: {e}")
            QMessageBox.warning(self, "Erreur", f"Une erreur s'est produite: {e}")

        logger.debug("Fonction update_file_list_view: Fin")

    def showModelInfo(self):
        logger.debug("Beginning of showModelInfo function.")
        try:
            self.logModelContents(self.file_list_model)
        except Exception as e:
            logger.error(f"An error occurred while displaying model information: {str(e)}")
            QMessageBox.critical(self, "Error", f"An error occurred: {str(e)}")
        logger.debug("End of showModelInfo function.")

    def logModelContents(self, model):
        logger.debug("Beginning of logModelContents function.")
        root_path = model.rootPath() 
        root_index = model.indexFromPath(root_path)
        for row in range(min(5, model.rowCount(root_index))):
            child_index = model.index(row, 0, root_index)
            if child_index.isValid():
                file_path = model.filePath(child_index)
                logger.debug(f"Element at index {row}: {file_path}")
            else:
                logger.warning(f"Invalid index at row {row}")
        logger.debug("End of logModelContents function.")

    def handleNoCurrentFolder(self):
        logger.info("No current folder set, not updating the file list.")
        self.current_folder = os.path.expanduser("~")  # Chemin par défaut vers le dossier utilisateur  
        self.updateFileListModel(self.current_folder)  
        self.updateDirectoryLabel()  
        self.update_file_list_view() 
        # You can add additional logic here, such as displaying a message to the user

    def setCurrentFolderPath(self):
        default_folder = os.path.expanduser("~")  # Ou un autre chemin par défaut
        self.current_folder = default_folder
        logger.debug(f"Current folder path reset to default: {self.current_folder}")

    def updateDirectoryLabel(self):
        self.update_directory_label(f"Current folder: {self.current_folder}")
        logger.debug("Directory label updated.")

    def handleResetListViewError(self, error):
        logger.error(f"Error resetting list view: {error}")
        QMessageBox.warning(self, "Error", "Problem resetting list view.")
        logger.warning("Reset list view error handled.")

    def checkDisplayedFiles(self, model):
        actual_files = [model.filePath(model.index(row, 0)) for row in range(model.rowCount())]
        logger.info("Files currently displayed in the view: " + str(actual_files))
    
    @pyqtSlot(int, int)
    def update_indexing_progress(self, processed_files, total_files):
        # Mettre à jour la barre de progression ou afficher un message de statut
        if total_files > 0:
            progress_percent = int(processed_files / total_files * 100)
            QMetaObject.invokeMethod(self, "update_status_bar", Qt.QueuedConnection,
                             Q_ARG(str, f"Indexing in progress... {progress_percent}%"))
        else:
            progress_percent = 0
    @pyqtSlot(str)
    def update_status_bar(self, message):
        self.statusBar().showMessage(message)
    @pyqtSlot()
    def on_indexing_finished(self):
        """Slot appelé lorsque l'indexation est terminée."""
        logger.info("FileViewer / Indexing finished successfully.")
        # Ajoutez ici le code que vous souhaitez exécuter à la fin de l'indexation
        #self.update_file_list_view(WATCHED_DIR)  # Mettre à jour la vue de liste après l'indexation
        # ... (autres actions)
        # Activez la recherche lorsque l'indexation est terminée
        self.search_button.setEnabled(True)
    @pyqtSlot(str)
    def on_indexing_error(self, error_message):
        """Slot appelé en cas d'erreur lors de l'indexation."""
        logger.error(f"FileViewer / Indexing error: {error_message}")
        # Gérez l'erreur ici (par exemple, affichez un message à l'utilisateur)
        self.is_indexing = False  # Reset the flag in case of an error
        QMessageBox.critical(self, "Erreur d'indexation", f"Une erreur est survenue lors de l'indexation : {error_message}")

    def get_proxy_model_for_source_model(self, source_model):
        logger.debug("Function get_proxy_model_for_source_model start")
        if source_model == self.file_list_model:
            logger.info("Found matching proxy model for file list model.")
            return self.file_list_proxy
        elif source_model == self.tree_model:
            logger.info("Found matching proxy model for tree model.")
            return self.tree_proxy
        logger.warning("No matching proxy model found for given source model.")
        logger.debug("Function get_proxy_model_for_source_model end")
        return None

    def get_view_for_model(self, model):
        logger.debug("Function get_view_for_model start")
        if model == self.file_list_model:
            logger.info("Found matching view for file list model.")
            return self.file_list
        elif model == self.tree_model:
            logger.info("Found matching view for tree model.")
            return self.tree_view
        logger.debug("Function get_view_for_model end")
        return None

    def on_root_path_changed(self, new_path):
        #self.current_folder = new_path  
        logger.debug("Function on_root_path_changed start with new path: {}".format(new_path))
        self.file_list_proxy.setSourceModel(self.file_list_model)  # Re-attach source model if needed
        self.update_file_list_view()
        logger.debug("Function on_root_path_changed end")

    def update_directory_label(self, text=None):
        logger.info("Function update_directory_label start")
        if text is None:  
            text = f"Current folder: {self.current_folder}" 
        logger.debug(f"Attempting to update directory label with text: {text}") 
        if hasattr(self, 'directory_label'):
            self.directory_label.setText(text)
            logger.info(f"Updated directory label: {text}")
        else:
            logger.error("Error: Directory label is not initialized.")
            # Optionally initialize the label here if needed
            self.directory_label = QLabel(text)
            logger.info(f"Initialized and updated directory label: {text}")
        logger.info("Function update_directory_label end")

    def onRecursiveSearchToggled(self, checked):
        logger.debug("Function onRecursiveSearchToggled start")
        self.file_list_proxy.setRecursiveSearchEnabled(checked)
        logger.info(f"Recursive search {'enabled' if checked else 'disabled'}.")
        logger.debug("Function onRecursiveSearchToggled end")
    
    def setup_proxy_tree_model(self):
        logger.debug("Starting setup of the proxy model for the tree/Function setup_proxy_tree_model")
        if self.tree_model is None:
            logger.error("Error: The source model (tree_model) is not initialized.")
            return
        if hasattr(self, 'tree_proxy_initialized') and self.tree_proxy_initialized:
            logger.debug("Proxy model for the tree is already set up.")
            return
        self.tree_proxy = TreeProxyModel() 
        self.tree_proxy.setSourceModel(self.tree_model)
        logger.info("Source model assigned to the tree proxy.")
        tree_filters = ['*']  # Specific filters for the tree
        self.tree_proxy.setFilterWildcard(' '.join(tree_filters))
        self.tree_proxy.invalidateFilter()
        logger.info("Filter of the tree proxy model invalidated to reflect the changes.")
        self.tree_view.setModel(self.tree_proxy)
        self.tree_proxy_initialized = True
        logger.debug("End of the setup of the proxy model for the tree/Function setup_proxy_tree_model")

    def setup_file_list_proxy(self):
        logger.debug("Starting setup of the proxy model for the file list./Function setup_file_list_proxy")
        if self.file_list_model is None:
            logger.error("Error: The source model (file_list_model) is not initialized.")
            return
        try:
            self.file_list_proxy = FileListProxyModel() 
            self.file_list_proxy.setSourceModel(self.file_list_model)
            logger.info("Source model assigned to the file list proxy.")
            # Configuring filters
            file_list_filters = ['*.dxf', '*.jpg', '*.jpeg', '*.png', '*.bmp']  # Filters for the file list view
            self.file_list_proxy.setFilterWildcard(' '.join(file_list_filters))
            logger.info(f"Filters applied for the file list proxy model: {' '.join(file_list_filters)}")
            # Applying filters and updating the view
            self.file_list_proxy.invalidateFilter()
            logger.info("Filter of the file list proxy model invalidated to reflect the changes.")
            self.file_list.setModel(self.file_list_proxy)
        except Exception as e:
            logger.error(f"Error during the configuration of the proxy model: {str(e)}")
        logger.debug("End of the setup of the proxy model for the file list./Function setup_file_list_proxy")

    def setup_ui(self):
        logger.debug("Starting the setup_ui function in FileViewer")
        self.setupTreeModelAndView()
        self.setupListModelAndView()
        self.central_layout = QVBoxLayout()
        self.main_splitter = QSplitter(Qt.Horizontal)
        self.setCentralWidget(self.main_splitter)
        self.tree_dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.setup_central_frame()  
        self.setup_preview_dock_widget()
        self.main_splitter.setSizes([445])

        logger.info("Connecting signals and slots:")
        self.central_layout.addWidget(self.file_list)  
        self.directory_label = QLabel("Current folder path:") 
        logger.debug("[Init] Directory label initialized") 
        self.central_layout.addWidget(self.directory_label) 
        logger.debug("[Layout] Directory label added to layout")
        self.file_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.file_list.customContextMenuRequested.connect(self.open_context_menu)   
        logger.debug("End of the setup_ui function in FileViewer")

    def handle_root_path_changed(self, new_root_path):
        logger.debug("Starting the handle_root_path_changed function")
        logger.info(f"New root path: {new_root_path}")
        current_root_path = self.file_list_model.rootPath()
        if new_root_path != current_root_path:
            self.current_folder = new_root_path  # Mettre à jour current_folder 
            self.update_file_list_view(new_root_path) 
        logger.debug("End of the handle_root_path_changed function")

    def setupTreeModelAndView(self):
        logger.debug("Starting the setupTreeModelAndView function")
        self.tree_model = CustomFileSystemModelForTree()
        self.tree_model.setRootPath("")
        self.tree_proxy = TreeProxyModel()
        self.tree_proxy.setSourceModel(self.tree_model)
        self.tree_view.setModel(self.tree_proxy)
        logger.debug("End of the setupTreeModelAndView function")

    def setupListModelAndView(self):
        logger.debug("Starting the setupListModelAndView function")
        self.file_list_model.setRootPath(self.current_folder)
        logger.info(f"Initial root path for the file list model: (empty)")
        # Configuring the proxy model
        self.file_list_proxy = FileListProxyModel()
        self.file_list_proxy.setSourceModel(self.file_list_model)
        logger.info("Proxy model configured with the file list model.")
        self.file_list_proxy.setFilterKeyColumn(0)
        self.file_list_proxy.setFilterWildcard("*.dxf *.jpg *.jpeg *.png *.bmp")
        # Associating the proxy model with the view
        self.file_list.setModel(self.file_list_proxy)
        logger.info("File list view configured with the proxy model.")
        self.file_list.setRootIndex(QModelIndex())
        logger.info("File list view configured to display empty content.")
        #self.showModelInfo()
        logger.info("Triggered showModelInfo:")
        logger.debug("End of the setupListModelAndView function")

    def setup_tree_dock_widget(self):
        logger.debug("Start of setup_tree_dock_widget function")
        self.tree_dock_widget = QDockWidget("Tree")
        self.tree_view = QTreeView(self.tree_dock_widget)
        self.tree_dock_widget.setWidget(self.tree_view)
        self.tree_dock_widget.setMinimumWidth(455)
        self.tree_view.clicked.connect(self.on_tree_clicked)
        logger.info("Connection: tree_view.clicked -> on_tree_clicked")
        #self.tree_model = CustomFileSystemModelForTree() 
        self.tree_model.setRootPath("")
        root_path = self.tree_model.rootPath()
        logger.info(f"Tree root path: {root_path}")

        self.setup_proxy_tree_model()
        header = self.tree_view.header()    
        header.setSectionHidden(1, True) 
        header.setSectionHidden(2, True) 
        header.setSectionHidden(3, True) 
        self.tree_view.setHeaderHidden(False)  # Hide header if desired
        self.addDockWidget(Qt.LeftDockWidgetArea, self.tree_dock_widget)
        logger.debug("End of setup_tree_dock_widget function")

    def setup_preview_dock_widget(self):
        logger.debug("Start of setup_preview_dock_widget function")
        self.preview_dock_widget = QDockWidget("Preview")
        self.preview_dock_widget.setFixedHeight(455)
        self.preview_dock_widget.setMinimumWidth(455)

        self.preview_scene = QGraphicsScene()
        self.preview_view = CustomGraphicsView(self.preview_scene)  
        self.preview_dock_widget.setWidget(self.preview_view)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.preview_dock_widget)

        # Barre d'outils pour le zoom et les modes de mesure
        self.toolbar = QToolBar("Zoom and Measure Tools", self)
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)
        
        zoom_in_action = QAction("Zoom In", self)
        zoom_in_action.triggered.connect(self.preview_view.zoom_in)
        self.toolbar.addAction(zoom_in_action)

        zoom_out_action = QAction("Zoom Out", self)
        zoom_out_action.triggered.connect(self.preview_view.zoom_out)
        self.toolbar.addAction(zoom_out_action)
        # Séparateur visuel
        self.toolbar.addSeparator()
        # Actions pour les modes de mesure
        action_measure_distance = QAction(QIcon("iconDistance.png"), "Distance Mode", self)
        action_measure_distance.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.DISTANCE))
        self.toolbar.addAction(action_measure_distance)

        action_measure_angle = QAction(QIcon("iconAngle.png"), "Angle Mode", self)
        action_measure_angle.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.ANGLE))
        self.toolbar.addAction(action_measure_angle)
        # Configuration de la vue et de la scène
        self.canvas = CustomGraphicsView()
        self.preview_view.setScene(self.preview_scene)
        self.preview_view.fitInView(self.preview_scene.sceneRect(), Qt.KeepAspectRatio)

        self.scene = QGraphicsScene()
        self.canvas.setScene(self.scene)

        logger.debug("End of setup_preview_dock_widget function")
    def setup_central_frame(self):
        logger.debug("Start of setup_central_frame function")
        self.central_frame = QWidget()
        self.central_layout = QVBoxLayout(self.central_frame)

        self.label_files = QLabel("File List:")
        self.central_layout.addWidget(self.label_files)

        self.search_box = QLineEdit()
        self.search_box.setPlaceholderText("Search files...")
        self.central_layout.addWidget(self.search_box)

        self.search_criteria_type = QComboBox(self)
        self.search_criteria_type.addItems(["Filename", "Creation Date", "Modification Date"])
        self.central_layout.addWidget(self.search_criteria_type)

        self.start_date = QDateEdit(self)
        self.start_date.setCalendarPopup(True)
        self.start_date.setDate(QDate.currentDate())
        self.central_layout.addWidget(self.start_date)

        self.end_date = QDateEdit(self)
        self.end_date.setCalendarPopup(True)
        self.end_date.setDate(QDate.currentDate())
        self.central_layout.addWidget(self.end_date)

        self.search_button = QPushButton("Search", self)
        self.central_layout.addWidget(self.search_button)

        #self.file_list = QListView()
        self.file_list.setIconSize(QSize(160, 120))
        logger.info(f"New icon size for QListView: {self.file_list.iconSize()}")
        self.file_list.setGridSize(QSize(175, 135))
        self.file_list.setModel(self.file_list_proxy )  # Use the file list proxy model here
        self.file_list.setViewMode(QListView.IconMode)
        #self.file_list_model.setRootPath(QDir.rootPath()) 
        root_index_model_files = self.file_list_proxy.sourceModel().index(0, 0) 
        proxy_root_index = self.file_list_proxy.mapFromSource(root_index_model_files)
        self.file_list.setRootIndex(proxy_root_index)
        
        self.central_layout.addWidget(self.file_list)
        self.main_splitter.addWidget(self.central_frame)

        logger.debug("End of setup_central_frame function")
    def on_signal_emitted(self, index):
        logger.debug(f"Start on_signal_emitted (self.update_file_list(index))")
        logger.debug(f"Signal emitted. Index: {index}")
        if index.isValid():
            folder_path = self.file_list_model.filePath(index)
            self.current_folder = folder_path  
            self.update_file_list_view(folder_path)
        else:
            logger.error("Invalid index received in on_signal_emitted")
        logger.debug(f"End on_signal_emitted")
    def on_folder_double_clicked(self, index):
        logger.debug("Start on_folder_double_clicked")
        if not index.isValid():
            logger.error("The index is not valid.")
            QMessageBox.warning(self, "Error", "Invalid index selected.")
            return
        # Détermination du modèle de base et de l'indice source
        if isinstance(index.model(), QSortFilterProxyModel):
            source_index = index.model().mapToSource(index)
            if index.model() == self.file_list_proxy:
                base_model = self.file_list_model
                logger.info("Index from file list proxy")
            elif index.model() == self.tree_proxy:
                base_model = self.tree_model
                logger.info("Index from tree proxy")
            else:
                QMessageBox.warning(self, "Error", "Unknown model type")
                return
        else:
            source_index = index
            base_model = index.model()
        folder_path = base_model.filePath(source_index)
        if os.path.isdir(folder_path) and os.access(folder_path, os.R_OK):
            logger.info(f"Selected folder path: {folder_path}")
            self.current_folder = folder_path  
            logger.debug(f"[on_folder_double_clicked] Current folder updated to: {self.current_folder}")  
            self.file_list_model.setRootPath(folder_path)
            logger.info(f"Root path of the file list model updated: {folder_path}")
            # Mise à jour de l'indice root de la vue
            updated_root_index = self.file_list_model.indexFromPath(folder_path)
            if hasattr(self, 'file_list_proxy'):
                proxy_root_index = self.file_list_proxy.mapFromSource(updated_root_index)
                if proxy_root_index.isValid():
                   self.file_list.setRootIndex(proxy_root_index)
                else:
                    logger.error("Invalid proxy root index after mapping.")
                    QMessageBox.warning(None, "Mapping Error", "Invalid proxy root index after mapping.")
            else:
                self.file_list.setRootIndex(updated_root_index)
            logger.info("List view updated to display the content of the selected folder.")
            self.update_directory_label(f"Current folder path: {self.current_folder}")  
        else:
            msg = "The selected path is not a directory." if not os.path.isdir(folder_path) else "The directory is not accessible."
            logger.error(f"{msg}: {folder_path}")
            QMessageBox.warning(self, "Error", msg)
        logger.debug("End on_folder_double_clicked")

    def on_tree_clicked(self, index):
        logger.debug("Start of on_tree_clicked")
        
        if self._in_search_mode:
            logger.info("Exiting search mode after tree click.")
            self._in_search_mode = False
            self.file_list.setModel(self.file_list_proxy)
            self.search_box.clear()
        logger.debug(f"Start processing index: {index}")
        if not index.isValid():
            logger.error("The provided index is invalid.")
            return

        # Map the proxy index to the source index
        source_index = self.tree_proxy.mapToSource(index) if isinstance(index.model(), QSortFilterProxyModel) else index
        if not source_index.isValid():
            logger.error("Index mapping failed. Attempting reset.")
            return

        # Get the directory path
        try:
            directory_path = self.tree_model.filePath(source_index)
            self.current_folder = directory_path  
        except IndexError:
            logger.error(f"Error getting directory path from index: {source_index}")
            return
        assert os.path.isdir(directory_path), f"Invalid directory path: {directory_path}"
        logger.debug(f"Directory path: {directory_path}")

        #Only update if the directory is different
        if directory_path == self.file_list_model.rootPath():
            logger.debug("Selected directory is already the active directory. Skipping update.")
            return

        # Update the file list model 
        logger.info(f"Updating file list model for directory: {directory_path}")
        self.file_list_model.setRootPath(directory_path)

        # Directly update the list view after setting the root path
        list_root_index = self.file_list_model.indexFromPath(directory_path)
        self.update_view_index(self.file_list_model, self.file_list, list_root_index)

        self.update_directory_label(f"Current folder path: {self.current_folder}")  
        logger.info(f"[on_tree_clicked] Current folder path updated to: {directory_path}")
        logger.debug("End of on_tree_clicked")

    def update_view_index(self, model, view, root_index):
        """Updates the view's index based on the model and root index."""
        logger.info("Start of FileViewer.update_view_index function")
        if hasattr(self, 'file_list_proxy') and model is self.file_list_model:
            index_proxy = self.file_list_proxy.mapFromSource(root_index)
            if index_proxy.isValid():
                view.setRootIndex(index_proxy)
            else:
                logger.error("Root proxy index is not valid after mapping.")
        else:
            view.setRootIndex(root_index)
        logger.info("end of FileViewer.update_view_index function")
    def load_folder(self):
        """Loads a folder selected by the user."""
        logger.info("Start of load_folder function")
        folder_path = QFileDialog.getExistingDirectory(self, "Choose a folder")
        if folder_path:
            try:
                # Check if the path is accessible (read and write)
                if not os.access(folder_path, os.R_OK | os.W_OK):
                    raise OSError("The selected folder is not accessible for read or write.")

                logger.info(f"Selected folder path: {folder_path}")
                self.current_folder = folder_path
                self.update_directory_label(f"Current folder path: {self.current_folder}")
                logger.debug(f"[load_folder] Current folder updated to: {self.current_folder}")

                # Update path in the list model
                self.file_list_model.setRootPath(self.current_folder)

                # Update list view index
                list_root_index = self.file_list_model.indexFromPath(self.current_folder)
                self.update_view_index(self.file_list_model, self.file_list, list_root_index)

                self.folderChanged.emit(folder_path)

            except OSError as e:
                QMessageBox.warning(self, "Error", str(e))
                logger.error(f"Error accessing folder: {e}")
        else:
            logger.warning("No folder selected.")
        logger.info("End of load_folder function")

    def navigate_up(self):
        logger.info("Start navigate_up")
        logger.debug("Attempting to navigate up one level in the file tree.")
        current_index = self.file_list.rootIndex()
        index_parent = self.file_list_proxy.mapToSource(current_index).parent()  # Use the correct proxy model here
        if index_parent.isValid():
            self.file_list.setRootIndex(self.file_list_proxy.mapFromSource(index_parent))
            folder_path = self.file_list_model.filePath(index_parent)
            self.current_folder = folder_path  
            self.update_directory_label(f"Current folder path: {self.current_folder}") 
            logger.info(f"Current directory updated to: {self.current_folder}")  
        else:
            logger.warning("Unable to navigate up, no valid parent found.")
        logger.info("End navigate_up")

    def on_tree_selection_changed(self, selected, deselected):
        logger.debug("Function on_tree_selection_changed start")
        index = self.tree_view.currentIndex()
        logger.debug(f"Current index in the tree: {index}")
        self.navigate_folder(index)
        logger.debug("Function on_tree_selection_changed end")

    def on_list_selection_changed(self, selected, deselected):
        logger.debug("Function on_list_selection_changed start")
        index_source = self.file_list.currentIndex()
        logger.debug(f"Current source index in the list: {index_source}")
        self.navigate_folder(index_source)
        logger.debug("Function on_list_selection_changed end")

    def navigate_folder(self, index):
        logger.debug("Function navigate_folder start")
        if not index.isValid():
            logger.error("Error: Invalid index provided for navigation.")
            return
        folder_path = self.file_list_model.filePath(index)
        if not os.path.exists(folder_path):
            logger.error(f"Error: Folder path does not exist - {folder_path}")
            return
        self.current_folder = folder_path  # Mise à jour du dossier courant  
        self.file_list_model.setRootPath(self.current_folder)
        logger.info(f"Updated root path of the file list model to: {self.current_folder}")
        self.update_file_list_view(folder_path)
        self.update_directory_label(f"Current folder: {self.current_folder}")
        logger.debug("Function navigate_folder end")

    def change_list_view(self):
        logger.info("Start change_list_view")
        logger.info("Switching view to List View")
        self.file_list.setViewMode(QListView.ListMode)
        logger.debug("File view switched to list.")
        logger.info("End change_list_view")

    def change_thumbnail_view(self):
        logger.info("Start change_thumbnail_view")
        logger.info("Switching view to Thumbnail View")
        self.file_list.setViewMode(QListView.IconMode)
        logger.debug("File view switched to thumbnails.")
        logger.info("End change_thumbnail_view")

#SUITE DU SCRITP