# LibreGED v2.5.0 - 27/07/2025
from PySide6.QtWidgets import (
    QMainWindow, QWidget, QPushButton, QLabel, QLineEdit, QTextEdit,
    QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout, QSplitter, QScrollArea,
    QListWidget, QListWidgetItem, QTreeWidget, QTreeWidgetItem, QMessageBox,
    QMenu, QToolTip, QSizePolicy, QSpacerItem, QStackedLayout, QGraphicsOpacityEffect, 
    QApplication, QStyleFactory, QDialog, QStackedWidget, QTabWidget, QTableWidget, 
    QTableWidgetItem, QAbstractItemView, QFileDialog, QProgressBar, QInputDialog, QComboBox, QToolButton
)
from PySide6.QtCore import (
    Qt, QTimer, QEvent, QUrl, QThread, QPropertyAnimation,
    QEasingCurve, QParallelAnimationGroup, QSize, QObject, Signal
)

from PySide6.QtGui import (
    QPixmap, QImage, QIcon, QTransform, QAction, QPalette, QColor, QFont,
    QTextOption, QDesktopServices, QGuiApplication
)

from PySide6.QtWebEngineWidgets import QWebEngineView
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt

# Importation des modules externes
import os
import sys
import subprocess
import qtawesome as qta
import fitz # PyMuPDF
import config
import pandas as pd
from PIL import Image, ExifTags
from PIL.ImageQt import ImageQt
from database import odf_utils
from database.db import fetch_all_documents, save_metadata_to_db, search_documents, matches_content, get_metadata_for_path, get_all_tags
from database.reindex import scan_and_insert_files, reindex_files_with_progress
from database.odf_utils import odt_to_html, ods_to_html, odp_to_html
from ebooklib import epub
from bs4 import BeautifulSoup
from datetime import datetime
from collections import Counter
import json
#from config import FILES_DIR, DB_PATH, ASSETS_DIR, LOGO_PATH, LANGUAGES_PATH, load_user_language, save_user_language, load_user_theme, save_user_theme
from pathlib import Path
from docx import Document
import mammoth
from odf.opendocument import load
from odf.text import P, H, Span
import sqlite3
import shutil
from shutil import copy2
import humanize
import platform
import ctypes
import random

TAG_COLORS = [
    "#007BFF", "#28a745", "#17a2b8", "#ffc107", "#6f42c1",
    "#fd7e14", "#20c997", "#6610f2", "#e83e8c", "#343a40"
]

def has_symlink_privileges():
    """Vérifie si l'utilisateur a les droits pour créer un lien symbolique (utile sous Windows)."""
    if os.name != 'nt':
        return True  # Sous Linux/macOS, pas besoin de privilèges spéciaux

    # Sous Windows, il faut généralement des privilèges administrateur
    try:
        return ctypes.windll.shell32.IsUserAnAdmin() != 0
    except Exception:
        return False

# Gestion des WebEngine si disponible
try:
    from PySide6.QtWebEngineWidgets import QWebEngineView
    WEB_ENGINE_AVAILABLE = True
except ImportError:
    WEB_ENGINE_AVAILABLE = False
    QWebEngineView = None

# Importation des styles
from styles import (
    LIGHT_THEME_STYLESHEET,
    DARK_THEME_STYLESHEET,
    apply_common_styles,
    get_uniform_button_style, 
    get_save_button_style, 
    get_tag_add_button_style,
    get_save_confirmation_label_style,
    get_icon_button_style_light, 
    get_icon_button_style_dark,
    get_zoom_button_style,
    get_text_preview_style,
    get_metadata_toggle_button_style,
    get_metadata_form_style,
    get_bottom_button_style,
    get_message_box_style,
    get_round_button_style,
    get_language_button_style,
    get_dark_button_style,
    get_light_button_style,
    get_theme_button_style,
    get_tag_widget_style,
    get_tag_input_style
)


def get_flag_icon(lang_code):
    flag_path = config.ASSETS_DIR / "flags" / f"{lang_code}.png"
    return QIcon(str(flag_path)) if flag_path.exists() else QIcon()


def resource_path(relative_path):
    """Retourne le chemin des ressources après compilation."""
    try:
        if getattr(sys, 'frozen', False):
            # Si l'application est exécutée depuis PyInstaller
            base_path = sys._MEIPASS
        else:
            # Sinon, utilise le répertoire courant
            base_path = os.path.abspath(".")
        return os.path.join(base_path, relative_path)
    except Exception as e:
        print(f"[ERREUR] chemin des ressources: {e}")
        return relative_path

def matches_content(file_path, query, translate):
    import logging
    logger = logging.getLogger("libreged")

    ext = os.path.splitext(file_path)[1].lower()
    query = query.lower()

    try:
        if ext in [".txt", ".md", ".html", ".htm", ".py"]:
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                return query in f.read().lower()

        elif ext == ".pdf":
            try:
                doc = fitz.open(file_path)
                for page in doc:
                    if query in page.get_text().lower():
                        return True
            except Exception as e:
                print(f"[MuPDF] Erreur sur {file_path}: {e}")

        elif ext in [".doc", ".docx"]:
            try:
                import mammoth
                with open(file_path, "rb") as docx_file:
                    result = mammoth.convert_to_html(docx_file)
                    return query in result.value.lower()
            except Exception:
                try:
                    from docx import Document
                    from docx.opc.exceptions import PackageNotFoundError
                    doc = Document(file_path)
                    return any(query in p.text.lower() for p in doc.paragraphs)
                except PackageNotFoundError:
                    # Fichier non lisible / faux document Word
                    return False
                except Exception:
                    return False

        elif ext == ".epub":
            try:
                from ebooklib import epub
                from bs4 import BeautifulSoup
                book = epub.read_epub(str(file_path))
                for item in book.get_items():
                    if item.get_type() == 9:  # DOCUMENT
                        soup = BeautifulSoup(item.get_content(), 'html.parser')
                        if query in soup.get_text().lower():
                            return True
            except Exception:
                return False

        elif ext in [".odt", ".odf"]:
            try:
                content = odf_utils.extract_text_from_odt(file_path)
                return query in content.lower()
            except Exception:
                return False

        elif ext == ".ods":
            try:
                content = odf_utils.ods_to_html(file_path)
                return query in content.lower()
            except Exception:
                return False

        elif ext == ".odp":
            try:
                content = odf_utils.odp_to_html(file_path)
                return query in content.lower()
            except Exception:
                return False

        elif ext == ".xlsx":
            try:
                content = odf_utils.extract_text_from_xlsx(file_path)
                return query in content.lower()
            except Exception:
                return False

        elif ext == ".pptx":
            try:
                content = odf_utils.extract_text_from_pptx(file_path)
                return query in content.lower()
            except Exception:
                return False

    except Exception :
        # log optionnel : commenter ou activer si besoin de traces silencieuses
        # logger.debug(f"[IGNORE] Lecture impossible pour {file_path}: {e}")
        # ou commenter complètement cette ligne si silence total
        pass 

    return False

import sqlite3
import os
import config

def matches_metadata(doc_path, keywords):
    # 1. Normalisation du chemin
    doc_path = os.path.normpath(str(doc_path))
    # 2. Normalisation des keywords
    keywords = [kw.strip().lower() for kw in keywords]

    try:
        with sqlite3.connect(config.DB_PATH) as conn:
            cur = conn.cursor()
            cur.execute(
                "SELECT tags, comment FROM document_metadata WHERE document_path = ?",
                (doc_path,)
            )
            row = cur.fetchone()
            if not row:
                return False

            tags, comment = row
            content = f"{tags or ''} {comment or ''}".lower()
            # 3. Recherche case-insensitive
            return any(kw in content for kw in keywords)

    except Exception as e:
        print(f"[ERREUR] Recherche metadata : {e}")
        return False


def get_qta_icon_for_extension(ext):
    ext = ext.lower()
    if ext in ['.pdf']:
        return qta.icon("fa5.file-pdf")
    elif ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tif', '.tiff']:
        return qta.icon("fa5.file-image")
    elif ext in ['.txt', '.md']:
        return qta.icon("fa5.file-alt")
    elif ext in ['.html', '.htm']:
        return qta.icon("fa5.file-code")
    else:
        return qta.icon("fa5.file")

def get_icon_for_extension(ext, theme="dark", color="#0078D7"):
    import qtawesome as qta
    icon_map = {
        '.pdf': 'fa5s.file-pdf',
        '.txt': 'fa5s.file-alt',
        '.md': 'fa5s.file-alt',
        '.html': 'fa5s.file-code',
        '.jpg': 'fa5s.file-image',
        '.jpeg': 'fa5s.file-image',
        '.png': 'fa5s.file-image',
        '.gif': 'fa5s.file-image',
        '.bmp': 'fa5s.file-image',
        '.tif': 'fa5s.file-image',
        '.tiff': 'fa5s.file-image',
        '.epub': 'fa5s.book'
    }

    icon_name = icon_map.get(ext.lower(), 'fa5s.file')
    return qta.icon(icon_name, color=color)

def get_folder_icon(color="#0078D7"):
    import qtawesome as qta
    return qta.icon("fa5s.folder", color=color)


def extract_all_text_from_docx(path, translate):
    try:
        from docx import Document
        doc = Document(path)
        full_text = [para.text for para in doc.paragraphs]
        return "\n".join(full_text).strip()
    except Exception as e:
        print(f"[ERROR] {translate('docx_read_error')}: {e}")
        return None


def convert_docx_to_html(path):
    with open(path, "rb") as docx_file:
        result = mammoth.convert_to_html(docx_file)
        html = result.value  
        messages = result.messages  
    return html

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("LibreGED")
        # Récupère la taille disponible de l’écran
        screen = QGuiApplication.primaryScreen()
        geom = screen.availableGeometry()
        # Définir une taille par défaut à 90 % de la largeur/hauteur, limitée à 1000×900 au maximum
        default_width  = min(int(geom.width()  * 0.9), 1000)
        default_height = min(int(geom.height() * 0.9),  900)
        # Redimensionne la fenêtre à cette taille par défaut
        self.resize(default_width, default_height)
        # Et abaisse (ou supprime) la contrainte minimale
        # Par exemple pour autoriser jusqu’à 600×400 px :
        self.setMinimumSize(600, 400)

        # --- Initialisation du compteur de résultats de recherche ---
        self.search_result_count = 0
        self.search_count_label = QLabel("")
        self.search_count_label.hide()
        self.statusBar().addPermanentWidget(self.search_count_label)

        self.current_theme = config.load_user_theme()
        self.current_language = config.load_user_language()
        self.load_translations()

        self.t = lambda key: self.translations.get(self.current_language, {}).get(key, key)

        self.file_info_label = QLabel("")
        self.file_info_label.setStyleSheet("padding-left: 10px; font-style: italic;")
        self.statusBar().addWidget(self.file_info_label)  

        #affichage du nombre de fichiers trouvés lors de la recheche
        self.search_count_label = QLabel("")  
        self.search_count_label.setStyleSheet("padding-right: 10px; font-weight: bold; color: #3366cc;")
        self.statusBar().addPermanentWidget(self.search_count_label)
        
        self.update_window_title()

        # Définir le style des boutons
        self.button_style = get_theme_button_style(self.current_theme)

        # -- Panneau gauche --
        left_panel = QWidget()
        left_layout = QVBoxLayout(left_panel)
        left_panel.setStyleSheet("background-color: transparent; border-radius: 12px;")

        
        # --- Logo ---
        self.logo_label = QLabel()
        # À ce stade, self.current_theme DOIT être défini
        logo_path = config.get_logo_path(self.current_theme)
        if logo_path.exists():
            pixmap = QPixmap(str(logo_path))
            if not pixmap.isNull():
                scaled = pixmap.scaledToWidth(160, Qt.SmoothTransformation)
                self.logo_label.setPixmap(scaled)
            else:
                print("[ERREUR] Pixmap vide malgré le chemin")
                self.logo_label.setText("Logo attention")
        else:
            print("[ERREUR] Le fichier logo est introuvable")
            self.logo_label.setText("logo KO")

        self.logo_label.setStyleSheet("background: transparent;")
        self.logo_label.setAlignment(Qt.AlignCenter)
        left_layout.addWidget(self.logo_label)

        left_layout.addSpacing(10)

        # --- Nombre de fichiers ---
        self.file_count_label = QLabel()
        self.file_count_label.setAlignment(Qt.AlignCenter)
        self.file_count_label.setStyleSheet("font-weight: bold; color: #3366cc;")
        left_layout.addWidget(self.file_count_label)

        left_layout.addSpacing(10)

        # --- Zone de recherche ---
        search_layout = QHBoxLayout()
        self.search_input = QLineEdit()
        self.search_input.setObjectName("SearchBar")
        self.search_input.setPlaceholderText(self.t("search_placeholder"))
        self.search_input.returnPressed.connect(self.perform_search)

        self.search_button = QPushButton()
        self.search_button.setIcon(qta.icon("fa5s.search", color="#007BFF"))
        self.search_button.setToolTip(self.t("search_tooltip"))
        self.search_button.clicked.connect(self.perform_search)

        self.tag_filter_button = QPushButton()
        self.tag_filter_button.setIcon(qta.icon("fa5s.tags", color="#248afd"))
        self.tag_filter_button.setToolTip(self.t("filter_by_tag"))
        self.tag_filter_button.clicked.connect(self.toggle_tag_filter)

        # Appliquer le même style que les autres 
        icon_button_style = (
            get_icon_button_style_dark()
            if self.current_theme == "dark"
            else get_icon_button_style_light()
        )
        self.tag_filter_button.setStyleSheet(icon_button_style)
        self.tag_filter_button.setFixedSize(40, 40)

        search_layout.addWidget(self.tag_filter_button)

        search_layout.addWidget(self.search_input)
        search_layout.addWidget(self.search_button)
        left_layout.addLayout(search_layout)

        # Bouton pour arrêter / réinitialiser la recherche
        self.stop_search_button = QPushButton()
        self.stop_search_button.setIcon(qta.icon("fa5s.stop-circle", color="red"))
        self.stop_search_button.setToolTip(self.t("stop_search"))

        # Flag pour savoir si une recherche est en cours
        self._is_searching = False

        # — Bouton “Arrêter” (pour stopper la recherche en cours) —
        self.stop_search_button.clicked.connect(self.stop_search)
        self.stop_search_button.setVisible(False)
        search_layout.addWidget(self.stop_search_button)

        self.tag_filter_combo = QComboBox()
        self.tag_filter_combo.setVisible(False)
        self.tag_filter_combo.setStyleSheet(get_tag_input_style(self.current_theme))
        self.tag_filter_combo.setPlaceholderText(self.t("select_tag_filter"))

        # Connexion du filtre par tag : on reçoit l'index, puis on appelle filter_by_selected_tag
        self.tag_filter_combo.currentIndexChanged.connect(
            lambda idx: self.filter_by_selected_tag(self.tag_filter_combo.itemText(idx))
        )
        

        left_layout.addWidget(self.tag_filter_combo)

        self.tag_filter_arrow = QToolButton(self.tag_filter_combo)
        self.tag_filter_arrow.setIcon(qta.icon("fa5s.chevron-down", color="white" if self.current_theme == "dark" else "black"))
        self.tag_filter_arrow.setCursor(Qt.PointingHandCursor)
        self.tag_filter_arrow.setStyleSheet("border: none;")
        self.tag_filter_arrow.setFixedSize(18, 18)
        self.tag_filter_arrow.setFocusPolicy(Qt.NoFocus)
        self.tag_filter_arrow.clicked.connect(self.tag_filter_combo.showPopup)

        # Positionner dynamiquement
        self.tag_filter_combo.resizeEvent = lambda event: (
            self.reposition_tag_filter_arrow(),
            QComboBox.resizeEvent(self.tag_filter_combo, event)
        )
    
        # --- Ligne 3 boutons : réindexer, réinitialiser, thème ---
        icons_layout = QHBoxLayout()

        # Boutons
        self.reindex_button = QPushButton()
        self.reindex_button.setIcon(qta.icon("fa5s.recycle", color="#007BFF"))
        self.reindex_button.setToolTip(self.t("tooltip_reindex"))
        self.reindex_button.clicked.connect(self.reindex_files)

        self.import_button = QPushButton()
        self.import_button.setIcon(qta.icon("fa5s.folder-plus", color="#007BFF"))
        self.import_button.setToolTip(self.t("tooltip_import"))
        self.import_button.clicked.connect(self.import_external_file_or_folder)

        self.reset_button = QPushButton()
        self.reset_button.setIcon(qta.icon("fa5s.sync", color="#007BFF"))
        self.reset_button.setToolTip(self.t("tooltip_refresh_ui"))  # ex: “Rafraîchir l’interface”
        self.reset_button.clicked.connect(self.refresh_ui)
        search_layout.addWidget(self.reset_button)

        # --- Bouton Information (À propos) ---
        self.info_button = QPushButton()
        self.info_button.setIcon(qta.icon("fa5s.info-circle", color="#007BFF"))
        self.info_button.setToolTip(self.t("tooltip_about"))
        self.info_button.clicked.connect(self.show_about_window)

        self.theme_button = QPushButton()
        self.theme_button.setIcon(qta.icon("fa5s.adjust", color="#007BFF"))
        self.theme_button.setToolTip(self.t("tooltip_theme"))
        self.theme_button.clicked.connect(self.toggle_theme)
                
        icon_button_style = (
            get_icon_button_style_dark()
            if self.current_theme == "dark"
            else get_icon_button_style_light()
        )

        for btn in [self.reindex_button, self.import_button, self.reset_button, self.info_button, self.theme_button]:
            btn.setStyleSheet(icon_button_style)
            btn.setFixedSize(40, 40)

        # Ajout dans le layout dans l'ordre voulu
        icons_layout.addWidget(self.reindex_button)
        icons_layout.addWidget(self.import_button)
        icons_layout.addWidget(self.reset_button)
        icons_layout.addWidget(self.info_button)  
        icons_layout.addWidget(self.theme_button)
                
        left_layout.addLayout(icons_layout)
        left_layout.addSpacing(10)

        # --- progress bar ---
        self.init_progress_bar()

        # --- Arborescence des fichiers ---
        self.tree = QTreeWidget()
        self.tree.setHeaderHidden(True)
        self.tree.itemClicked.connect(self.on_tree_item_clicked)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.show_tree_context_menu)
        left_layout.addWidget(self.tree)
        self.tree.setItemDelegate(ScrollingItemDelegate(self.tree))

        # -- Zone de prévisualisation complète (avec sélecteur en haut) --
        self.preview_container = QWidget()
        self.preview_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        # Layout principal de la zone de prévisualisation
        self.preview_layout = QVBoxLayout(self.preview_container)
        #self.preview_layout.setContentsMargins(0, 0, 0, 0)
        self.preview_layout.setSpacing(5)
        
        # === Contenu principal de prévisualisation (stack) ===
        self.preview_frame = QFrame()
        #self.preview_frame.setObjectName("PreviewContent")
        #self.preview_frame.setStyleSheet("")
        #self.preview_frame.setFrameShape(QFrame.StyledPanel)
        self.preview_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.previewed_file_path = None

        frame_layout = QVBoxLayout(self.preview_frame)
        frame_layout.setContentsMargins(10, 10, 10, 10)

        # Le stack reste ici
        self.preview_stack = QStackedLayout()
        frame_layout.addLayout(self.preview_stack)

        # Ajoute la frame au layout principal
        self.preview_layout.addWidget(self.preview_frame)

        # --- TEXTES ---
        self.text_preview = QTextEdit()
        self.text_preview.setStyleSheet(get_text_preview_style(self.current_theme))
        self.text_preview.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)

        self.text_preview.setReadOnly(True)
        self.text_preview.setFrameShape(QTextEdit.NoFrame)
        self.text_preview.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.text_preview.setAlignment(Qt.AlignLeft | Qt.AlignTop)
        self.text_preview.setContentsMargins(10, 10, 10, 10)

        # --- IMAGES ---
        self.image_preview_label = QLabel()
        self.image_preview_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
        self.image_scroll = QScrollArea()
        self.image_scroll.setWidgetResizable(True)
        self.image_scroll.setWidget(self.image_preview_label)
        self.image_scroll.viewport().setCursor(Qt.OpenHandCursor)
        self.image_scroll.viewport().installEventFilter(self)

        # --- HTML ---
        self.html_preview = None  

        # --- XLS ---
        self.xlsx_tab_widget = QTabWidget()
        self.xlsx_tab_widget.setVisible(False)
        self.preview_stack.addWidget(self.xlsx_tab_widget)

        # Ajout des widgets à la pile
        self.preview_stack.addWidget(self.text_preview)
        self.preview_stack.addWidget(self.image_scroll)

        # Sélection par défaut
        self.preview_stack.setCurrentWidget(self.text_preview)

        # Ajout du preview_content_widget à l'interface
        self.preview_layout.addWidget(self.preview_frame)

        # === Contrôles de zoom + PDF + navigation regroupés ===
        self.zoom_controls_widget = QWidget()
        self.zoom_controls_layout = QHBoxLayout(self.zoom_controls_widget)  
        self.zoom_controls_layout.setContentsMargins(0, 0, 0, 0)

        # -- bouton de rotation --
        self.rotate_button = QPushButton()
        self.rotate_button.setObjectName("RotateButton")
        self.rotate_button.setIcon(qta.icon("fa5s.redo", color="#007BFF"))  
        self.rotate_button.setToolTip("Rotation de l'image")
        self.rotate_button.clicked.connect(self.rotate_image)  # Connecte la fonction de rotation
        
        # -- bouton précédent --
        self.prev_page_button = QPushButton()
        self.prev_page_button.setObjectName("PrevPageButton")
        self.prev_page_button.setIcon(qta.icon("fa5s.arrow-left", color="#007BFF"))
        self.prev_page_button.setToolTip("Page précédente")
        self.prev_page_button.clicked.connect(self.previous_pdf_page)

        # -- bouton suivant --
        self.next_page_button = QPushButton()
        self.next_page_button.setObjectName("NextPageButton")
        self.next_page_button.setIcon(qta.icon("fa5s.arrow-right", color="#007BFF"))
        self.next_page_button.setToolTip("Page suivante")
        self.next_page_button.clicked.connect(self.next_pdf_page)

        # -- Zoom + --
        self.zoom_in_button = QPushButton()
        self.zoom_in_button.setObjectName("ZoomInButton")
        self.zoom_in_button.setIcon(qta.icon("fa5s.search-plus", color="#007BFF"))
        self.zoom_in_button.setToolTip("Zoom avant")
        self.zoom_in_button.clicked.connect(self.zoom_in)

        # -- Zoom - --
        self.zoom_out_button = QPushButton()
        self.zoom_out_button.setObjectName("ZoomOutButton")
        self.zoom_out_button.setIcon(qta.icon("fa5s.search-minus", color="#007BFF"))
        self.zoom_out_button.setToolTip("Zoom arrière")
        self.zoom_out_button.clicked.connect(self.zoom_out)
        for btn in [self.prev_page_button, self.next_page_button, self.rotate_button, self.zoom_in_button, self.zoom_out_button]:
            btn.setFixedSize(32, 32)

        # -- Zoom label --
        self.zoom_label = QLabel("Zoom : 100%")
        self.zoom_label.setAlignment(Qt.AlignCenter)
        self.zoom_controls_layout.addWidget(self.zoom_label)  
        
        self.zoom_controls_layout.addSpacing(20)
        
        # Organisation dans le layout
        self.zoom_controls_layout.addWidget(self.prev_page_button)
        self.zoom_controls_layout.addStretch(1)
        self.zoom_controls_layout.addWidget(self.rotate_button)
        self.zoom_controls_layout.addWidget(self.zoom_in_button)
        self.zoom_controls_layout.addWidget(self.zoom_out_button)
        self.zoom_controls_layout.addWidget(self.zoom_label)
        self.zoom_controls_layout.addStretch(1)
        self.zoom_controls_layout.addWidget(self.next_page_button)

        # sélecteur de page PDF
        self.top_page_selector = QComboBox()
        self.top_page_selector.setFixedWidth(130)
        self.top_page_selector.setVisible(False)
        self.top_page_selector.setEnabled(False)
        self.top_page_selector.currentIndexChanged.connect(self.go_to_pdf_page_from_top)
        self.top_page_selector.setEditable(True)
        self.top_page_selector.lineEdit().setAlignment(Qt.AlignCenter)
        self.top_page_selector.setEditable(False)

        # Conteneur avec layout centré en haut
        self.top_selector_widget = QWidget()
        self.top_selector_widget.setFixedHeight(50)
        top_selector_layout = QHBoxLayout(self.top_selector_widget)
        top_selector_layout.setContentsMargins(0, 0, 0, 0)
        top_selector_layout.addStretch()
        top_selector_layout.addWidget(self.top_page_selector)
        top_selector_layout.addStretch()

        self.top_selector_widget.setVisible(False)  # MASQUÉ PAR DÉFAUT
        self.preview_layout.insertWidget(0, self.top_selector_widget)


        self.preview_layout.addWidget(self.zoom_controls_widget)
        self.zoom_controls_widget.hide()

        # === Bouton Métadonnées ===
        self.toggle_metadata_button = QPushButton(f" {self.t('button_metadata')}")
        self.toggle_metadata_button.setCheckable(True)
        self.toggle_metadata_button.setChecked(False)
        self.toggle_metadata_button.setIcon(qta.icon("fa5s.angle-down", color="#007BFF"))
        self.toggle_metadata_button.clicked.connect(self.toggle_metadata_visibility)
        self.toggle_metadata_button.setStyleSheet(get_metadata_toggle_button_style(self.current_theme))
        self.preview_layout.addWidget(self.toggle_metadata_button)

        # === Formulaire Métadonnées ===
        self.metadata_form = QWidget()
        self.metadata_layout = QGridLayout(self.metadata_form)
        self.metadata_layout.setContentsMargins(4, 2, 4, 2)
        self.metadata_layout.setHorizontalSpacing(8)
        self.metadata_layout.setVerticalSpacing(6)

        self.metadata_animation = QPropertyAnimation(self.metadata_form, b"maximumHeight")
        self.metadata_animation.setDuration(400)
        self.metadata_animation.setEasingCurve(QEasingCurve.OutCubic)
        self.metadata_animation.finished.connect(self.on_metadata_animation_finished)

        self.metadata_form.setStyleSheet(get_metadata_form_style(self.current_theme))

        self.meta_author_field = QLineEdit()

        # === TAGS ===
        self.setup_metadata_tags_field()

        self.meta_comment_field = QTextEdit()
        self.meta_comment_field.setFixedHeight(40)
        self.meta_version_field = QLineEdit()
        self.meta_updated_field = QLineEdit()
        self.meta_updated_field.setReadOnly(True)

        self.meta_author_field.setMaximumHeight(20)
        self.meta_version_field.setMaximumHeight(20)
        self.meta_updated_field.setMaximumHeight(20)

        self.label_author = QLabel(self.t("label_author"))
        self.metadata_layout.addWidget(self.label_author, 0, 0)
        self.metadata_layout.addWidget(self.meta_author_field, 0, 1)
        self.metadata_layout.addWidget(self.meta_tags_container, 1, 1)  
        self.label_comment = QLabel(self.t("label_comment"))
        self.metadata_layout.addWidget(self.label_comment, 2, 0)
        self.metadata_layout.addWidget(self.meta_comment_field, 2, 1)
        self.label_version = QLabel(self.t("label_version"))
        self.metadata_layout.addWidget(self.label_version, 3, 0)
        self.metadata_layout.addWidget(self.meta_version_field, 3, 1)
        self.label_updated = QLabel(self.t("label_updated"))
        self.metadata_layout.addWidget(self.label_updated, 4, 0)
        self.metadata_layout.addWidget(self.meta_updated_field, 4, 1)

        self.save_metadata_button = QPushButton(self.t("button_save_metadata"))
        self.save_metadata_button.setObjectName("saveMetadataButton")
        self.save_metadata_button.clicked.connect(self.save_current_metadata)
        self.metadata_layout.addWidget(self.save_metadata_button, 5, 0, 1, 2)
        self.save_metadata_button.setStyleSheet(get_save_button_style())
        self.save_confirmation_label = QLabel("")
        self.save_confirmation_label.setStyleSheet(get_save_confirmation_label_style())
        self.save_confirmation_label.hide()
        self.metadata_layout.addWidget(self.save_confirmation_label, 6, 0, 1, 2)

        # Scroll container uniquement
        self.metadata_form.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        self.preview_layout.addWidget(self.metadata_form)
        self.metadata_form.hide()

        self.metadata_form.hide()

        # -- Splitter principal --
        splitter = QSplitter()
        splitter.addWidget(left_panel)
        splitter.addWidget(self.preview_container)
        splitter.setSizes([250, 950])

        container = QWidget()
        layout = QVBoxLayout(container)
        layout.addWidget(splitter)
        self.setCentralWidget(container)

        # --- Barre de boutons supplémentaires en bas ---
        bottom_buttons_layout = QHBoxLayout()

        btn_style = get_bottom_button_style(self.current_theme)

        # Bouton Supprimer
        self.delete_button = QPushButton()
        self.delete_button.setIcon(qta.icon("fa5s.trash", color="#007BFF"))
        self.delete_button.setToolTip(self.t("tooltip_delete"))
        self.delete_button.setFixedSize(40, 40)
        self.delete_button.setStyleSheet(btn_style)
        self.delete_button.clicked.connect(self.delete_selected_items)
        bottom_buttons_layout.addWidget(self.delete_button)

        # Bouton Renommer
        self.rename_button = QPushButton()
        self.rename_button.setIcon(qta.icon("fa5s.i-cursor", color="#007BFF"))
        self.rename_button.setToolTip(self.t("tooltip_rename"))
        self.rename_button.setFixedSize(40, 40)
        self.rename_button.setStyleSheet(btn_style)
        self.rename_button.clicked.connect(self.rename_selected_item)
        bottom_buttons_layout.addWidget(self.rename_button)

        # Bouton Statistiques
        self.stats_button = QPushButton()
        self.stats_button.setIcon(qta.icon("fa5s.chart-bar", color="#007BFF"))
        self.stats_button.setToolTip(self.t("tooltip_stats"))
        self.stats_button.setFixedSize(40, 40)
        self.stats_button.setStyleSheet(btn_style)
        self.stats_button.clicked.connect(self.show_ged_statistics)
        bottom_buttons_layout.addWidget(self.stats_button)

        # Bouton Sauvegarde (propre, sans flèche)
        self.backup_button = QPushButton()
        self.backup_button.setIcon(qta.icon("fa5s.archive", color="#007BFF"))
        self.backup_button.setToolTip(self.t("tooltip_backup"))
        self.backup_button.setFixedSize(40, 40)
        self.backup_button.setStyleSheet(btn_style)
        self.backup_button.clicked.connect(self.show_backup_menu)
        bottom_buttons_layout.addWidget(self.backup_button)


        # Bouton Quitter (en rouge)
        self.quit_button = QPushButton()
        self.quit_button.setIcon(qta.icon("fa5s.sign-out-alt", color="red"))
        self.quit_button.setToolTip(self.t("tooltip_quit"))
        self.quit_button.setFixedSize(40, 40)
        self.quit_button.setStyleSheet(btn_style)
        self.quit_button.clicked.connect(self.close)
        bottom_buttons_layout.addWidget(self.quit_button)

        left_layout.addLayout(bottom_buttons_layout)

        # -- Initialisation --
        self.update_file_count()
        self.load_documents()
        if self.current_theme == "dark":
            self.apply_dark_theme()
        else:
            self.apply_light_theme()
        

    def update_logo(self):
        logo_path = config.get_logo_path(self.current_theme)
        
        if logo_path.exists():
            pixmap = QPixmap(str(logo_path)).scaledToWidth(160, Qt.SmoothTransformation)
            self.logo_label.setPixmap(pixmap)
        else:
            self.logo_label.setText("KO")
    
    def init_progress_bar(self):
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)

        self.progress_label = QLabel("")  # ← nouveau label texte
        self.progress_label.setVisible(False)

        self.statusBar().addPermanentWidget(self.progress_bar)
        self.statusBar().addPermanentWidget(self.progress_label)

    
    def _update_progress_value(self, val, total):
        if not self._is_searching:
            return

        try:
            if total == 0:
                return

            percent = min(int(val / total * 100), 100)

            if self.progress_bar:
                self.progress_bar.blockSignals(True)
                self.progress_bar.setMaximum(total)
                self.progress_bar.setValue(val)
                self.progress_bar.blockSignals(False)

            if hasattr(self, "progress_label") and self.progress_label:
                self.progress_label.setText(f"{val}/{total} ({percent}%)")
                self.progress_label.setVisible(True)

            # Ne pas appeler processEvents ici !
            # QApplication.processEvents()

        except RecursionError:
            print("[FATAL] Boucle récursive détectée dans _update_progress_value()")
        except Exception as e:
            print(f"[ERREUR] _update_progress_value : {e}")

    def hide_progress_bar_fade(self):
        anim = QPropertyAnimation(self.progress_bar, b"windowOpacity")
        anim.setDuration(1000)
        anim.setStartValue(1.0)
        anim.setEndValue(0.0)
        anim.setEasingCurve(QEasingCurve.InOutQuad)

        def after_hide():
            self.progress_bar.setVisible(False)
            self.progress_bar.setWindowOpacity(1.0)
            self.progress_label.setVisible(False)  # ← cacher le label

        anim.finished.connect(after_hide)
        anim.start()
        self._progress_anim = anim

    
    def show_progress_bar_fade(self):
        self.progress_bar.setWindowOpacity(0.0)
        self.progress_bar.setVisible(True)
        anim = QPropertyAnimation(self.progress_bar, b"windowOpacity")
        anim.setDuration(500)
        anim.setStartValue(0.0)
        anim.setEndValue(1.0)
        anim.setEasingCurve(QEasingCurve.InOutQuad)
        anim.start()
        self._progress_show_anim = anim
    
    def show_search_progress(self):
        self.progress_label.setText(self.t("search_in_progress"))
        self.progress_label.setVisible(True)
        self.progress_bar.setMaximum(0)  # Mode indéterminé (barre qui "tourne")
        self.progress_bar.setVisible(True)
        QApplication.processEvents()
    
    def hide_search_progress(self):
        self.progress_bar.setVisible(False)
        self.progress_label.setVisible(False)
        self.progress_bar.setMaximum(100)  # Réinitialise en mode déterminé

    def update_window_title(self, filename: str = None):
        # 1) l'icône de la fenêtre devient le drapeau
        flag_icon = get_flag_icon(self.current_language)
        self.setWindowIcon(flag_icon)

        # 2) on assemble le texte
        title = "LibreGED v.2.5.0"
        if filename:
            title += f" – {filename}"
        self.setWindowTitle(title)


    def t(self, key):
        return self.translations.get(self.current_language, {}).get(key, key)


    def show_preview_widget(self, widget):
        """Affiche le widget voulu dans le QStackedLayout de prévisualisation."""
        index = self.preview_stack.indexOf(widget)
        if index != -1:
            self.preview_stack.setCurrentIndex(index)

    def go_to_pdf_page_from_top(self, index):
        if not hasattr(self, "pdf_doc") or not self.pdf_doc:
            return
        self.current_pdf_page = index
        self.render_current_pdf_page()

    def load_documents(self):
        self.tree.clear()

        root_items = {}  # Chemins → dossiers
        top_items = []   # Références aux éléments racine (à injecter plus tard)

        # 1. Collecte tous les chemins depuis la base
        all_docs = fetch_all_documents()
        rel_paths = [Path(doc[2]) for doc in all_docs]

        # 2. Ajoute tous les dossiers parents à partir des chemins connus
        folder_paths = set()
        for path in rel_paths:
            folder_paths.update(path.parents)
            folder_paths.add(path.parent)

        folder_paths = {p for p in folder_paths if str(p) != "."}
        sorted_folders = sorted(folder_paths, key=lambda p: len(p.parts))

        # 3. Crée les dossiers dans l’arborescence
        for folder in sorted_folders:
            parts = folder.parts
            parent = None
            path_accumulator = []

            for i, part in enumerate(parts):
                path_accumulator.append(part)
                current_path = os.sep.join(path_accumulator)

                if current_path not in root_items:
                    folder_item = QTreeWidgetItem([part])
                    folder_full_path = config.FILES_DIR / current_path

                    # Vérifie si le dossier est un lien symbolique
                    is_symlink = folder_full_path.is_symlink()
                    icon_color = "#00A2FF" if is_symlink else "#0078D7"
                    folder_item.setIcon(0, get_folder_icon(color=icon_color))

                    folder_item.setFlags(folder_item.flags() | Qt.ItemIsEnabled | Qt.ItemIsSelectable)
                    folder_item.setData(0, Qt.ItemDataRole.UserRole, current_path)
                    root_items[current_path] = folder_item

                    if parent:
                        parent.addChild(folder_item)
                    else:
                        top_items.append(folder_item)
                parent = root_items[current_path]

        # 4. Ajoute les fichiers (en ignorant les .keep et .json)
        for doc in all_docs:
            rel_path = doc[2]
            abs_path = config.FILES_DIR / rel_path

            if abs_path.name == ".keep" or rel_path.endswith(".json"):
                continue

            parts = rel_path.split(os.sep)
            parent = None
            path_accumulator = []

            for i, part in enumerate(parts):
                path_accumulator.append(part)
                current_path = os.sep.join(path_accumulator)

                if i == len(parts) - 1:
                    file_item = QTreeWidgetItem([part])
                    ext = os.path.splitext(rel_path)[1]

                    # Vérifie si le fichier est un lien symbolique
                    is_symlink = abs_path.is_symlink()
                    icon_color = "#00A2FF" if is_symlink else "#0078D7"
                    file_item.setIcon(0, get_icon_for_extension(ext, self.current_theme, color=icon_color))

                    file_item.setData(0, Qt.ItemDataRole.UserRole, rel_path)

                    if parent:
                        parent.addChild(file_item)
                    else:
                        top_items.append(file_item)
                else:
                    parent = root_items.get(current_path)

        # 5. Tri récursif
        def sort_tree_items_recursively(item):
            children = [item.child(i) for i in range(item.childCount())]
            children.sort(key=lambda x: x.text(0).lower())
            for i in reversed(range(item.childCount())):
                item.takeChild(i)
            for child in children:
                item.addChild(child)
                sort_tree_items_recursively(child)

        top_items.sort(key=lambda x: x.text(0).lower())
        for item in top_items:
            sort_tree_items_recursively(item)
            self.tree.addTopLevelItem(item)

        self.set_metadata_fields_enabled(False)


            
    def update_file_count(self):
        from database.db import fetch_all_documents  # Assure-toi que `fetch_all_documents` utilise `config.DB_PATH`
        
        docs = fetch_all_documents()

        # Filtrer pour exclure les fichiers .json
        count = len([doc for doc in docs if not doc[2].endswith('.json')])

        self.file_count_label.setText(self.t("file_count_label").format(count=count))


    def clear_preview(self):
        self.text_preview.hide()
        self.image_scroll.hide()
        self.zoom_controls_widget.hide()
        self.prev_page_button.hide()
        self.next_page_button.hide()
        self.xlsx_tab_widget.setVisible(False)
        self.top_page_selector.setVisible(False)  # ← important !

        if self.html_preview:
            self.html_preview.hide()

        display_text = item.text()
        
        rel_path = item.data(0, Qt.ItemDataRole.UserRole)
            
        if not rel_path:
            self.show_text_preview(self.t("error_missing_path"))
            return

        doc_path = config.FILES_DIR / rel_path  # Utiliser config.FILES_DIR ici
        
        if not doc_path.exists():
            self.show_text_preview(self.t("error_file_not_found"))
            return
        
        ext = doc_path.suffix.lower().strip()

        self.file_info_label = QLabel("")
        self.statusBar().addPermanentWidget(self.file_info_label)

        
        try:
            if ext in [".html", ".htm"]:
                if WEB_ENGINE_AVAILABLE and self.html_preview:
                    try:
                        self.html_preview.setVisible(True)
                        self.html_preview.load(QUrl.fromLocalFile(str(doc_path)))
                    except Exception as e:
                        self.show_text_preview(self.t("error_loading_html").format(error=str(e)))
                else:
                    try:
                        with open(doc_path, "r", encoding="utf-8", errors="ignore") as f:
                            content = f.read()
                        self.text_preview.setPlainText(content)
                        self.text_preview.setVisible(True)
                    except Exception as e:
                        self.show_text_preview(self.t("error_reading_html").format(error=str(e)))

            elif ext in [".txt", ".md", ".py"]:
                with open(doc_path, "r", encoding="utf-8", errors="ignore") as f:
                    content = f.read()
                self.show_text_preview(content)

            elif ext == ".pdf":
                self.pdf_doc = fitz.open(str(doc_path))
                self.current_pdf_page = 0
                self.current_zoom = 1.0
                self.render_current_pdf_page(adapt_to_width=True)

                page_count = self.pdf_doc.page_count
                print(f"[DEBUG] PDF détecté avec {page_count} pages")

                if page_count > 1:
                    self.top_page_selector.blockSignals(True)
                    self.top_page_selector.clear()
                    for i in range(page_count):
                        icon = qta.icon("fa5.file-alt", color="#007BFF")  # ou "fa.file", "fa5s.file", etc.
                        self.top_page_selector.addItem(icon, f"{self.t('page')} {i + 1}")
                        #self.top_page_selector.addItem(f"Page {i + 1}")
                    self.top_page_selector.setCurrentIndex(0)
                    self.top_page_selector.setVisible(True)
                    self.top_page_selector.setEnabled(True)
                    self.top_page_selector.blockSignals(False)
                    self.top_selector_widget.setVisible(True)  
                else:
                    self.top_page_selector.clear()
                    self.top_page_selector.setVisible(False)
                    self.top_page_selector.setEnabled(False)
                    self.top_selector_widget.setVisible(False)  

            elif ext in [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tif", ".tiff"]:
                try:
                    image = Image.open(str(doc_path))
                    try:
                        for orientation in ExifTags.TAGS.keys():
                            if ExifTags.TAGS[orientation] == 'Orientation':
                                break
                        exif = dict(image._getexif().items())
                        orientation_value = exif.get(orientation, None)
                        if orientation_value == 3:
                            image = image.rotate(180, expand=True)
                        elif orientation_value == 6:
                            image = image.rotate(270, expand=True)
                        elif orientation_value == 8:
                            image = image.rotate(90, expand=True)
                    except Exception as exif_err:
                        print(f"[INFO] Aucune orientation EXIF ou erreur : {exif_err}")

                    image = image.convert("RGBA")
                    data = image.tobytes("raw", "RGBA")
                    qim = QImage(data, image.width, image.height, image.width * 4, QImage.Format_RGBA8888)
                    pixmap = QPixmap.fromImage(qim)

                    if pixmap.isNull():
                        self.show_text_preview(self.t("error_invalid_image"))
                    else:
                        self.pdf_doc = None
                        self.current_zoom = 1.0
                        self.show_image_preview(pixmap)

                except Exception as e:
                    QMessageBox.critical(self, self.t("error_loading_image"), str(e))

            elif ext == ".epub":
                self.show_epub_preview(doc_path)

            elif ext == ".docx":
                html = self.show_docx_preview(doc_path)
                if html:
                    self.show_html_preview(html)

            elif ext == ".odt":
                html = odf_utils.odt_to_html(str(doc_path))
                self.show_html_preview(html)
            
            elif ext in [".xlsx", ".xlsm"]:
                self.show_xlsx_preview(doc_path)

            elif ext == ".pptx":
                content = odf_utils.extract_text_from_pptx(doc_path)
                self.show_text_preview(content if content else self.t("textfile_read_error").format(error=""))

            else:
                self.show_text_preview(self.t("error_unsupported"))

        except Exception as e:
            QMessageBox.critical(self, self.t("error_reading_file"), str(e))
    
    ###################################### VERSION 2.3 #################################
    def open_tree_context_menu(self, position):
        selected_item = self.tree.itemAt(position)
        if not selected_item:
            return

        file_rel_path = selected_item.data(0, Qt.UserRole)
        file_abs_path = config.FILES_DIR / file_rel_path

        menu = QMenu(self)

        if file_abs_path.is_symlink():
            open_original_action = menu.addAction(self.t("open_original_folder"))
            open_original_action.triggered.connect(lambda: self.open_real_folder(file_abs_path))

        menu.exec(self.tree.viewport().mapToGlobal(position))

    def open_real_folder(self, symlink_path: Path):
        try:
            real_path = symlink_path.resolve(strict=True)
            folder = real_path.parent
            if folder.exists():
                import subprocess
                subprocess.Popen(["xdg-open", str(folder)])
            else:
                QMessageBox.warning(self, "Erreur", "Le dossier cible n'existe plus.")
        except Exception as e:
            QMessageBox.critical(self, "Erreur", f"Impossible d’ouvrir le dossier :\n{e}")



    #################################### FIN VERSION 2.3 ###############################


    def show_epub_preview(self, file_path):
        try:
            from tempfile import NamedTemporaryFile
            import base64

            book = epub.read_epub(str(file_path))

            # === DÉBUT DU DOCUMENT HTML ===
            theme_bg = "#ffffff" if self.current_theme == "light" else "#2e2e2e"
            theme_color = "#000000" if self.current_theme == "light" else "#f0f0f0"

            html_content = f"""
            <html>
            <head>
                <meta charset="utf-8">
                <style>
                    body {{
                        font-family: sans-serif;
                        margin: 0;
                        padding: 20px;
                        max-width: none;
                        background-color: {theme_bg};
                        color: {theme_color};
                    }}
                    img {{
                        max-width: 100%;
                        height: auto;
                        display: block;
                        margin: 20px auto;
                    }}
                    hr {{
                        margin: 20px 0;
                    }}
                </style>
            </head>
            <body>
            """

            # === COUVERTURE ===
            cover_item = self.get_epub_cover_image(book)
            if cover_item:
                cover_data = cover_item.get_content()
                encoded = base64.b64encode(cover_data).decode("utf-8")
                html_content += f"<img src='data:image/jpeg;base64,{encoded}' /><hr>"

            # === CONTENU HTML ===
            for item in book.get_items():
                if isinstance(item, epub.EpubHtml):
                    try:
                        html = item.get_content().decode("utf-8", errors="ignore")
                        html_content += html + "<hr>"
                    except Exception as e:
                        print(f"[WARN] Erreur sur l’item {item.file_name} : {e}")

            html_content += "</body></html>"

            # === AFFICHAGE VIA QWebEngineView ===
            if WEB_ENGINE_AVAILABLE:
                # Nettoyage de l'ancien fichier temporaire
                if hasattr(self, "current_epub_tmpfile"):
                    try:
                        os.unlink(self.current_epub_tmpfile)
                    except Exception as e:
                        print(f"[INFO] Impossible de supprimer l’ancien fichier temporaire : {e}")

                # Écriture dans un fichier temporaire
                tmp = NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8")
                tmp.write(html_content)
                tmp.close()
                self.current_epub_tmpfile = tmp.name

                # Création du nouveau QWebEngineView
                new_preview = QWebEngineView()
                new_preview.load(QUrl.fromLocalFile(tmp.name))

                # Remplacement propre dans la pile
                if self.html_preview:
                    index = self.preview_stack.indexOf(self.html_preview)
                    self.preview_stack.removeWidget(self.html_preview)
                    self.html_preview.deleteLater()
                else:
                    index = self.preview_stack.count()

                self.html_preview = new_preview
                self.preview_stack.insertWidget(index, self.html_preview)
                self.preview_stack.setCurrentWidget(self.html_preview)

            else:
                self.show_text_preview(self.t("error_epub_preview_unavailable"))

        except Exception as e:
            self.show_text_preview(self.t("error_epub").format(error=str(e)))


    def get_epub_cover_image(self, book):
        try:
            from ebooklib import epub
            
            # Si book est un chemin relatif, il faut le résoudre
            if isinstance(book, str):  # Si book est une chaîne (chemin de fichier)
                book_path = config.FILES_DIR / book  # Résolution du chemin relatif
                book = epub.read_epub(str(book_path))  # Ouverture du fichier EPUB
            
            for item in book.get_items():
                # On vérifie si le mimetype correspond à une image
                if item.media_type.startswith("image/") and 'cover' in item.get_name().lower():
                    return item
            return None
        except Exception as e:
            print(f"[ERROR] Failed to extract cover image from EPUB: {e}")
            return None


    def show_docx_preview(self, file_path):
        try:
            import mammoth
            from tempfile import NamedTemporaryFile
            import os

            # Résoudre le chemin complet pour le fichier DOCX
            full_path = config.FILES_DIR / file_path  # Utiliser config.FILES_DIR ici

            print(f"[DEBUG] Lecture Word (Mammoth) : {full_path}")

            with open(str(full_path), "rb") as docx_file:
                result = mammoth.convert_to_html(docx_file)
                html = result.value

            return f"<html><body style='font-family: sans-serif;'>{html}</body></html>"

        except Exception as e:
            print(f"[ERROR] DOCX to HTML failed: {e}")
            return None


    def show_xlsx_preview(self, file_path):
        try:
            # Résoudre le chemin complet pour le fichier XLSX ou XLSM
            full_path = config.FILES_DIR / file_path  # Utiliser config.FILES_DIR ici

            # Lire le fichier Excel (que ce soit .xlsx ou .xlsm)
            xls = pd.ExcelFile(str(full_path))  # Ouvre le fichier Excel

            # Clear the current tab widget
            self.xlsx_tab_widget.clear()

            for sheet_name in xls.sheet_names:  # Parcours toutes les feuilles
                df = pd.read_excel(xls, sheet_name=sheet_name)  # Lire la feuille dans un DataFrame

                # Créer un tableau QTableWidget pour afficher les données
                table = QTableWidget()
                table.setEditTriggers(QAbstractItemView.NoEditTriggers)
                table.setRowCount(df.shape[0])
                table.setColumnCount(df.shape[1])

                # Définir les en-têtes de colonnes AVANT de remplir les cellules
                table.setHorizontalHeaderLabels([
                    "" if str(col).startswith("Unnamed") else str(col)
                    for col in df.columns
                ])

                # Remplir les cellules
                for i, row in df.iterrows():
                    for j, value in enumerate(row):
                        display_value = "" if pd.isna(value) else str(value)
                        table.setItem(i, j, QTableWidgetItem(display_value))

                # Ajuster les dimensions des colonnes et des lignes
                table.resizeColumnsToContents()
                table.resizeRowsToContents()

                # Ajouter le tableau à l'onglet
                self.xlsx_tab_widget.addTab(table, sheet_name)

            # Masquer les autres prévisualisations et afficher le tableau Excel
            self.text_preview.hide()
            self.image_scroll.hide()
            self.zoom_controls_widget.hide()
            self.xlsx_tab_widget.setVisible(True)
            self.preview_stack.setCurrentWidget(self.xlsx_tab_widget)

        except Exception as e:
            print(f"[ERREUR] XLSX/XLSM multi-feuilles : {e}")
            self.show_text_preview(self.t("textfile_read_error").format(error=str(e)))

    def reset_search(self):
        # tout de suite, plus rien ne doit mettre à jour l’UI
        self._is_searching   = False
        self._search_aborted = False

        # Remise à zéro de l’UI
        self.search_input.clear()
        self.tree.clear()
        self.search_count_label.clear()
        self.search_count_label.hide()

        self.hide_progress_bar_fade()
        self.progress_label.clear()
        self.progress_label.hide()
        self.progress_bar.reset()
        self.progress_bar.hide()

        self.stop_search_button.setVisible(False)
        self.stop_search_button.setEnabled(True)

        self.load_documents()
        self.clear_preview()

        self.pdf_doc = None
        self.original_pixmap = None
        self.current_zoom = 1.0
        self.current_pdf_page = 0

        self.set_metadata_fields_enabled(False)
        self.toggle_metadata_button.setChecked(False)
        self.metadata_form.hide()

        # Désactive le bouton reset jusqu’à la prochaine recherche
        self.reset_button.setEnabled(False)



    def clear_preview(self):
        self.pdf_doc = None
        self.original_pixmap = None
        self.current_zoom = 1.0

        # Vider les prévisualisations de texte et les masquer
        if self.text_preview:
            self.text_preview.clear()
            self.text_preview.setVisible(False)

        # Vider la prévisualisation d'image et la masquer
        if self.image_preview_label:
            self.image_preview_label.clear()
            self.image_scroll.setVisible(False)

        # Nettoyer le QWebEngineView s’il existe, sans le supprimer
        if hasattr(self, 'html_preview') and self.html_preview is not None:
            try:
                self.html_preview.setHtml("")  # Efface le contenu HTML
                self.html_preview.hide()       # Masque le widget
            except RuntimeError as e:
                print(f"[AVERTISSEMENT] html_preview inaccessible : {e}")

        # Masquer les contrôles de zoom et les boutons de navigation
        self.zoom_controls_widget.hide()
        self.prev_page_button.hide()
        self.next_page_button.hide()
        self.top_page_selector.clear()
        self.top_page_selector.setVisible(False)
        self.top_selector_widget.setVisible(False)

        # Réinitialiser le bouton de métadonnées
        self.toggle_metadata_button.setEnabled(False)

        # Réinitialiser le widget XLSX
        if self.xlsx_tab_widget:
            self.xlsx_tab_widget.clear()
            self.xlsx_tab_widget.setVisible(False)

        # Réinitialiser le pied de page (nom du fichier)
        if hasattr(self, "file_info_label") and self.file_info_label:
            self.file_info_label.setText("")

        # Réinitialise les tags
        self.tag_filter_combo.setVisible(False)
        self.tag_filter_combo.setCurrentIndex(0)



    def delete_selected_items(self):
        selected_items = self.tree.selectedItems()
        print(f"[DEBUG] Nombre d'éléments sélectionnés : {len(selected_items)}")
        if not selected_items:
            QMessageBox.information(self, self.t("delete_confirm_title"), self.t("delete_nothing_selected"))
            return

        # Confirmation avant suppression
        confirm = QMessageBox.question(
            self,
            self.t("delete_confirm_title"),
            self.t("delete_confirm_text"),
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )

        if confirm != QMessageBox.Yes:
            return

        with sqlite3.connect(config.DB_PATH) as conn:  
            cur = conn.cursor()

            for item in selected_items:
                rel_path = item.data(0, Qt.ItemDataRole.UserRole)
                print(f"[DEBUG] Donnée UserRole : {rel_path}")
                if not rel_path:
                    continue

                rel_path_str = str(rel_path).replace("\\", "/")
                full_path = config.FILES_DIR / rel_path  # Utiliser config.FILES_DIR pour obtenir le chemin complet

                print(f"[INFO] Suppression : {rel_path_str}")
                print(f"        Chemin absolu : {full_path}")
                print(f"        Type : {'Dossier' if full_path.is_dir() else 'Fichier'}")

                try:
                    if full_path.is_dir():
                        if full_path.is_symlink():  # Si c'est un lien symbolique
                            os.remove(full_path)  # Utiliser os.remove() pour les liens symboliques
                            print(f"        Lien symbolique supprimé")
                        else:
                            shutil.rmtree(full_path)  # Sinon utiliser rmtree pour les dossiers réels
                            print(f"        Dossier supprimé")

                        cur.execute("DELETE FROM documents WHERE path = ? OR path LIKE ?", (rel_path_str, rel_path_str + "/%"))
                        cur.execute("DELETE FROM document_metadata WHERE document_path = ? OR document_path LIKE ?", (rel_path_str, rel_path_str + "/%"))
                        print("        Enregistrements supprimés en base (documents + métadonnées)")

                    elif full_path.is_file():
                        full_path.unlink()  # Utiliser unlink pour supprimer un fichier
                        print(f"       Fichier supprimé")

                        cur.execute("DELETE FROM documents WHERE path = ?", (rel_path_str,))
                        cur.execute("DELETE FROM document_metadata WHERE document_path = ?", (rel_path_str,))
                        print("        Enregistrements supprimés en base (documents + métadonnées)")

                    else:
                        print("        Chemin introuvable")

                except Exception as e:
                    print(f"[ERREUR] Suppression échouée pour {rel_path_str} : {e}")
                    QMessageBox.warning(
                        self,
                        self.t("delete_error_title"),
                        self.t("delete_error_text").format(path=rel_path, error=str(e))
                    )

            conn.commit()

        print("[INFO] Suppression terminée")
        self.load_documents()
        self.update_file_count()
        self.reindex_files()
        self.clear_preview()

    def rename_selected_item(self):
        selected_items = self.tree.selectedItems()
        if not selected_items or len(selected_items) != 1:
            QMessageBox.information(self, self.t("rename_title"), self.t("rename_select_one"))
            return

        item = selected_items[0]
        rel_path = item.data(0, Qt.ItemDataRole.UserRole)
        if not rel_path:
            return

        old_path = config.FILES_DIR / rel_path
        if not old_path.exists():
            QMessageBox.warning(self, self.t("rename_error_title"), self.t("file_not_found"))
            return

        old_name = old_path.name
        parent_path = old_path.parent

        new_name, ok = QInputDialog.getText(
            self,
            self.t("rename_title"),
            self.t("rename_prompt").format(old_name=old_name)
        )

        if not ok or not new_name.strip():
            return

        new_name = new_name.strip()

        # Ajouter extension si oubliée (pour les fichiers uniquement)
        if old_path.is_file() and not os.path.splitext(new_name)[1]:
            new_name += old_path.suffix

        new_path = parent_path / new_name
        if new_path.exists():
            QMessageBox.warning(
                self,
                self.t("rename_error_title"),
                self.t("rename_already_exists").format(new_name=new_name)
            )
            return

        try:
            old_path.rename(new_path)
            rel_new_path = str(new_path.relative_to(config.FILES_DIR))

            # Mise à jour de la base SQLite
            with sqlite3.connect(config.DB_PATH) as conn:
                cur = conn.cursor()
                cur.execute("UPDATE documents SET name = ?, path = ? WHERE path = ?", (
                    new_name,
                    rel_new_path,
                    rel_path
                ))
                cur.execute("UPDATE document_metadata SET document_path = ? WHERE document_path = ?", (
                    rel_new_path,
                    rel_path
                ))
                conn.commit()

            print(f"[OK] Renommé : {rel_path} → {rel_new_path}")

            # Si le fichier renommé est affiché, mettre à jour la prévisualisation
            if self.file_info_label.text().strip() == str(rel_path):
                self.preview_document(rel_new_path)

            # Réindexation complète du dossier "files"
            print("[INFO] Réindexation après renommage...")
            self.reindex_files()  # Appelle ta fonction de réindexation
            self.load_documents()
            self.update_file_count()

        except Exception as e:
            QMessageBox.critical(
                self,
                self.t("rename_error_title"),
                self.t("rename_error_message").format(error=str(e))
            )



    def show_ged_statistics(self):
        stats = Counter()
        total_size = 0
        folder_count = 0
        stats["odt"] = 0
        stats["docx"] = 0
        stats["xlsx"] = 0
        stats["pptx"] = 0
        stats["ods"] = 0
        stats["odp"] = 0

        with sqlite3.connect(config.DB_PATH) as conn:  
            cur = conn.cursor()
            cur.execute("SELECT path, extension FROM documents WHERE extension != '.json'")
            for path, ext in cur.fetchall():
                ext = ext.lower()
                if ext == ".pdf":
                    stats["pdf"] += 1
                elif ext in [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif", ".tiff"]:
                    stats["images"] += 1
                elif ext in [".txt", ".md", ".py"]:
                    stats["textes"] += 1
                elif ext in [".html", ".htm"]:
                    stats["html"] += 1
                elif ext == ".docx":
                    stats["docx"] += 1
                elif ext == ".odt":
                    stats["odt"] += 1
                elif ext == ".ods":
                    stats["ods"] += 1
                elif ext == ".odp":
                    stats["odp"] += 1
                elif ext == ".xlsx":
                    stats["xlsx"] += 1
                elif ext == ".pptx":
                    stats["pptx"] += 1

                else:
                    stats["autres"] += 1

                try:
                    full_path = config.FILES_DIR / path  # Utilisation de config.FILES_DIR pour le chemin complet
                    if full_path.exists() and full_path.is_file():
                        total_size += full_path.stat().st_size
                except:
                    pass

        for root, dirs, _ in os.walk(config.FILES_DIR):  # Utilisation de config.FILES_DIR ici aussi
            folder_count += len(dirs)

        total_files = sum(stats.values())
        size_readable = humanize.naturalsize(total_size, binary=True)

        # === Boîte personnalisée ===
        dlg = QDialog(self)
        dlg.setWindowTitle(self.t("stats_title"))
        layout = QVBoxLayout(dlg)

        icon_map = {
            self.t("stats_total_files"): qta.icon("fa5s.copy", color="#007BFF"),
            self.t("stats_pdf"): qta.icon("fa5s.file-pdf", color="#007BFF"),
            self.t("stats_odt"): qta.icon("fa5s.file-word", color="#007BFF"),
            self.t("stats_ods"): qta.icon("fa5s.file-excel", color="#007BFF"),
            self.t("stats_odp"): qta.icon("fa5s.file-powerpoint", color="#007BFF"),
            self.t("stats_images"): qta.icon("fa5s.image", color="#007BFF"),
            self.t("stats_texts"): qta.icon("fa5s.file-alt", color="#007BFF"),
            self.t("stats_docx"): qta.icon("fa5s.file-word", color="#007BFF"),
            self.t("stats_xlsx"): qta.icon("fa5s.file-excel", color="#007BFF"),
            self.t("stats_pptx"): qta.icon("fa5s.file-powerpoint", color="#007BFF"),
            self.t("stats_html"): qta.icon("fa5s.code", color="#007BFF"),
            self.t("stats_other"): qta.icon("fa5s.question-circle", color="#007BFF"),
            self.t("stats_folders"): qta.icon("fa5s.folder", color="#007BFF"),
            self.t("stats_size"): qta.icon("fa5s.hdd", color="#007BFF"),
        }

        rows = [
            (self.t("stats_total_files"), total_files),
            (self.t("stats_pdf"), stats["pdf"]),
            (self.t("stats_images"), stats["images"]),
            (self.t("stats_texts"), stats["textes"]),
            (self.t("stats_odt"), stats["odt"]),
            (self.t("stats_docx"), stats["docx"]),
            (self.t("stats_xlsx"), stats["xlsx"]),
            (self.t("stats_pptx"), stats["pptx"]),
            (self.t("stats_html"), stats["html"]),
            (self.t("stats_other"), stats["autres"]),
            (self.t("stats_folders"), folder_count),
            (self.t("stats_size"), size_readable),
        ]

        stats_grid = QGridLayout()
        for i, (label, value) in enumerate(rows):
            icon_label = QLabel()
            icon_label.setPixmap(icon_map[label].pixmap(24, 24))
            stats_grid.addWidget(icon_label, i, 0)

            text_label = QLabel(f"{label} :")
            stats_grid.addWidget(text_label, i, 1)

            value_label = QLabel(str(value))
            value_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
            stats_grid.addWidget(value_label, i, 2)

        layout.addLayout(stats_grid)

        # === Boutons avec style rond ===
        btn_style = get_round_button_style()

        export_btn = QPushButton()
        export_btn.setIcon(qta.icon("fa5s.file-export", color="#007BFF"))
        export_btn.setToolTip(self.t("tooltip_export_stats"))
        export_btn.setFixedSize(40, 40)
        export_btn.setStyleSheet(btn_style)
        export_btn.clicked.connect(lambda: self.export_ged_statistics(stats, folder_count, total_size))

        chart_btn = QPushButton()
        chart_btn.setIcon(qta.icon("fa5s.chart-pie", color="#007BFF"))
        chart_btn.setToolTip(self.t("tooltip_show_pie"))
        chart_btn.setFixedSize(40, 40)
        chart_btn.setStyleSheet(btn_style)
        chart_btn.clicked.connect(lambda: self.show_pie_chart(stats))

        close_btn = QPushButton()
        close_btn.setIcon(qta.icon("fa5s.times-circle", color="red"))
        close_btn.setToolTip(self.t("tooltip_close"))
        close_btn.setFixedSize(40, 40)
        close_btn.setStyleSheet(btn_style)
        close_btn.clicked.connect(dlg.close)

        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        btn_layout.addWidget(export_btn)
        btn_layout.addWidget(chart_btn)
        btn_layout.addWidget(close_btn)
        btn_layout.addStretch()

        layout.addLayout(btn_layout)
        dlg.exec()


    def export_ged_statistics(self, stats, folder_count, total_size):
        from reportlab.lib.pagesizes import A4
        from reportlab.pdfgen import canvas
        import csv
        import humanize
        from PySide6.QtWidgets import QFileDialog

        save_path, _ = QFileDialog.getSaveFileName(
            self,
            self.t("export_dialog_title"),
            "",
            "PDF (*.pdf);;CSV (*.csv)"
        )
        if not save_path:
            return

        total_files = sum(stats.values())
        size_readable = humanize.naturalsize(total_size, binary=True)

        data = [
            ("Fichiers totaux", total_files),
            ("PDF", stats["pdf"]),
            ("Images", stats["images"]),
            ("Textes", stats["textes"]),
            ("HTML", stats["html"]),
            ("Autres", stats["autres"]),
            ("Dossiers", folder_count),
            ("Taille totale", size_readable)
        ]

        if save_path.endswith(".csv"):
            with open(save_path, "w", newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow([self.t("export_column_category"), self.t("export_column_value")])
                for row in data:
                    writer.writerow(row)

        elif save_path.endswith(".pdf"):
            c = canvas.Canvas(save_path, pagesize=A4)
            width, height = A4
            c.setFont("Helvetica", 12)
            c.drawString(50, height - 50, self.t("export_pdf_title"))
            y = height - 100
            for key, val in data:
                c.drawString(60, y, f"{key} : {val}")
                y -= 20
            c.save()

        QMessageBox.information(
            self,
            self.t("export_success_title"),
            self.t("export_success_message").format(path=save_path)
        )

    def show_pie_chart(self, stats):
        from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton

        # Préparer les données
        labels = []
        sizes = []
        for key, label_key in [
            ("pdf", "stats_pdf"),
            ("images", "stats_images"),
            ("textes", "stats_texts"),
            ("html", "stats_html"),
            ("autres", "stats_other")
        ]:
            count = stats[key]
            if count > 0:
                labels.append(self.t(label_key))
                sizes.append(count)

        if not sizes:
            QMessageBox.information(
                self,
                self.t("chart_no_data_title"),
                self.t("chart_no_data_message")
            )
            return

        # Créer une figure matplotlib
        fig, ax = plt.subplots()
        ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=140)
        ax.axis('equal')
        
        # Emballer la figure dans un widget Qt
        canvas = FigureCanvas(fig)

        # Créer une fenêtre Qt personnalisée
        dialog = QDialog(self)
        dialog.setWindowTitle(self.t("chart_window_title"))
        dialog.setMinimumSize(500, 400)
        layout = QVBoxLayout(dialog)
        layout.addWidget(canvas)

        close_btn = QPushButton(self.t("chart_close_button"))
        close_btn.clicked.connect(dialog.close)
        layout.addWidget(close_btn)

        dialog.exec()

    def show_backup_menu(self):
        menu = QMenu(self)

        action_backup = QAction(self.t("menu_backup_now"), self)
        action_restore = QAction(self.t("menu_restore_backup"), self)

        action_backup.triggered.connect(self.backup_ged)
        action_restore.triggered.connect(self.restore_ged)

        menu.addAction(action_backup)
        menu.addAction(action_restore)

        menu.exec(self.backup_button.mapToGlobal(self.backup_button.rect().bottomLeft()))

    
    def backup_ged(self):
        from PySide6.QtWidgets import QFileDialog, QProgressDialog
        from datetime import datetime
        default_name = f"ged_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
        
        # Demander à l'utilisateur où sauvegarder le fichier
        save_path, _ = QFileDialog.getSaveFileName(
            self,
            self.t("backup_dialog_title"),
            default_name,
            "Fichier ZIP (*.zip)"
        )

        if not save_path:
            return

        self.progress_dialog = QProgressDialog(
            self.t("backup_progress_text"),
            self.t("backup_cancel_button"),
            0, 100, self
        )
        self.progress_dialog.setWindowTitle(self.t("backup_window_title"))

        self.progress_dialog.setWindowModality(Qt.WindowModal)
        self.progress_dialog.setMinimumDuration(0)
        self.progress_dialog.setValue(0)

        # Récupérer les traductions et la langue courante
        translations = self.translations
        current_language = self.current_language

        self.thread = QThread()
        self.backup_worker = BackupWorker(
            config.FILES_DIR, config.DB_PATH, save_path,
            translations, current_language
        )
        self.backup_worker.moveToThread(self.thread)

        # Connexion des signaux
        self.thread.started.connect(self.backup_worker.run)
        self.backup_worker.progress.connect(self.progress_dialog.setValue)
        self.backup_worker.finished.connect(self.on_backup_finished)
        self.backup_worker.error.connect(self.on_backup_error)

        # Nettoyage
        self.backup_worker.finished.connect(self.thread.quit)
        self.backup_worker.error.connect(self.thread.quit)
        self.backup_worker.finished.connect(self.backup_worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        # Important : attendre la fin réelle du thread pour éviter les fuites
        self.thread.finished.connect(lambda: self.thread.wait())

        # Démarrer
        self.thread.start()

    def on_backup_finished(self, message):
        self.progress_dialog.setValue(100)
        QMessageBox.information(self, self.t("backup_success_title"), message)

    def on_backup_error(self, error_message):
        self.progress_dialog.cancel()
        QMessageBox.critical(
            self,
            self.t("backup_error_title"),
            self.t("backup_error_message").format(error=error_message)
        )

    def restore_ged(self):
        from PySide6.QtWidgets import QFileDialog, QProgressDialog
        import zipfile
        import tempfile

        zip_path, _ = QFileDialog.getOpenFileName(
            self,
            self.t("restore_dialog_title"),
            "",
            "Fichier ZIP (*.zip)"
        )

        if not zip_path:
            return

        reply = QMessageBox.question(
            self,
            self.t("restore_confirm_title"),
            self.t("restore_confirm_text"),
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )
        if reply != QMessageBox.Yes:
            return

        # === Jauge de progression ===
        self.progress_dialog = QProgressDialog(
            self.t("restore_progress_text"),
            self.t("restore_cancel_button"),
            0, 100, self
        )
        self.progress_dialog.setWindowTitle(self.t("restore_window_title"))
        self.progress_dialog.setWindowModality(Qt.WindowModal)
        self.progress_dialog.setMinimumDuration(0)
        self.progress_dialog.setValue(0)

        # === Lancement du thread de restauration ===
        self.thread = QThread()
        self.restore_worker = RestoreWorker(zip_path, config.FILES_DIR, config.DB_PATH, self.translations, self.current_language)
        self.restore_worker.moveToThread(self.thread)

        self.thread.started.connect(self.restore_worker.run)
        self.restore_worker.progress.connect(self.progress_dialog.setValue)
        self.restore_worker.finished.connect(self.on_restore_finished)
        self.restore_worker.error.connect(self.on_restore_error)

        self.restore_worker.finished.connect(self.thread.quit)
        self.restore_worker.error.connect(self.thread.quit)
        self.restore_worker.finished.connect(self.restore_worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        self.thread.finished.connect(lambda: self.thread.wait())
        self.thread.start()

    def on_restore_finished(self, message):
        self.progress_dialog.setValue(100)
        self.load_documents()
        self.update_file_count()
        self.clear_preview()
        self.reindex_files()
        QMessageBox.information(self, self.t("restore_done_title"), message)

    def on_restore_error(self, error_message):
        self.progress_dialog.cancel()
        QMessageBox.critical(
            self,
            self.t("restore_error_title"),
            self.t("restore_error_message").format(error=error_message)
        )

    def load_translations(self):
        """Charge les traductions depuis un fichier JSON."""
        try:
            # Utiliser le chemin centralisé de config.py pour les traductions
            with open(config.LANGUAGES_PATH, "r", encoding="utf-8") as file:
                self.translations = json.load(file)
            print("[DEBUG] Traductions chargées avec succès.")
        except Exception as e:
            print(f"[ERROR] Failed to load translations: {e}")
            self.translations = {}



    def change_language(self, lang_code):
        """Change la langue de l'application."""
        self.current_language = lang_code
        config.save_user_language(lang_code)
        self.load_translations()  # Recharge les traductions après avoir sauvegardé la langue
        self.update_language()  # Met à jour l'interface utilisateur
        self.update_window_title()



    def update_language(self):
        trans = self.translations.get(self.current_language, {})

        # Fenêtre À propos
        if hasattr(self, 'about_label'):
            cwd = os.getcwd()
            about_html = trans.get("about_full_text", "").format(cwd=cwd)
            self.about_label.setText(about_html)

        if hasattr(self, 'close_button'):
            self.close_button.setText(trans.get("close", "Fermer"))

        # TOOLTIP & PLACEHOLDER ZONE DE RECHERCHE
        if hasattr(self, 'search_input'):
            self.search_input.setPlaceholderText(self.t("search_placeholder"))
        if hasattr(self, 'search_button'):
            self.search_button.setToolTip(self.t("search_tooltip"))

        # TOOLTIP BOUTONS PRINCIPAUX
        if hasattr(self, 'reindex_button'):
            self.reindex_button.setToolTip(self.t("tooltip_reindex"))
        if hasattr(self, 'import_button'):
            self.import_button.setToolTip(self.t("tooltip_import"))
        if hasattr(self, 'reset_button'):
            self.reset_button.setToolTip(self.t("tooltip_reset"))
        if hasattr(self, 'info_button'):
            self.info_button.setToolTip(self.t("tooltip_about"))
        if hasattr(self, 'theme_button'):
            self.theme_button.setToolTip(self.t("tooltip_theme"))
        if hasattr(self, 'toggle_metadata_button'):
            self.toggle_metadata_button.setText(f" {self.t('button_metadata')}")
        if hasattr(self, 'label_author'):
            self.label_author.setText(self.t("label_author"))
        if hasattr(self, 'label_comment'):
            self.label_comment.setText(self.t("label_comment"))
        if hasattr(self, 'label_version'):
            self.label_version.setText(self.t("label_version"))
        if hasattr(self, 'label_updated'):
            self.label_updated.setText(self.t("label_updated"))
        if hasattr(self, 'save_metadata_button'):
            self.save_metadata_button.setText(self.t("button_save_metadata"))
        if hasattr(self, 'delete_button'):
            self.delete_button.setToolTip(self.t("tooltip_delete"))
        if hasattr(self, 'rename_button'):
            self.rename_button.setToolTip(self.t("tooltip_rename"))
        if hasattr(self, 'stats_button'):
            self.stats_button.setToolTip(self.t("tooltip_stats"))
        if hasattr(self, 'backup_button'):
            self.backup_button.setToolTip(self.t("tooltip_backup"))
        if hasattr(self, 'quit_button'):
            self.quit_button.setToolTip(self.t("tooltip_quit"))
        if hasattr(self, 'update_file_count'):
            self.update_file_count()
        if hasattr(self, 'export_btn'):
            self.export_btn.setToolTip(self.t("tooltip_export_stats"))
        if hasattr(self, 'label_tags'):
            self.label_tags.setText(self.t("label_tags"))
        if hasattr(self, 'meta_tag_input'):
            self.meta_tag_input.setPlaceholderText(self.t("tag_input_placeholder"))
        # Mise à jour de la barre de zoom avec traduction
        if hasattr(self, "zoom_label") and hasattr(self, "pdf_doc") and self.pdf_doc:
            label = self.t("zoom_with_page").format(
                percent=int(self.current_zoom * 100),
                current=self.current_pdf_page + 1,
                total=self.pdf_doc.page_count
            )
            self.zoom_label.setText(label)
        # Bouton de recherche dans les tags
        if hasattr(self, 'tag_filter_combo'):
            self.tag_filter_combo.setPlaceholderText(self.t("select_tag_filter"))
            self.tag_filter_button.setToolTip(self.t("filter_by_tag"))

    
    def show_about_window(self):
        # Créer un QWidget personnalisé (au lieu de QMessageBox)
        about_window = QDialog(self)
        about_window.setWindowTitle(self.t("about_title")) # Traduction du titre

        # Créer le layout principal
        main_layout = QVBoxLayout(about_window)

        # Créer une zone de défilement pour le texte
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)  # Permet au contenu de se redimensionner
        main_layout.addWidget(scroll_area)

        # Créer un QWebEngineView pour afficher le contenu HTML
        self.html_preview = QWebEngineView()
        scroll_area.setWidget(self.html_preview)  # Ajouter le QWebEngineView à la zone de défilement

        # Texte principal de la boîte "À propos"
        cwd = os.getcwd()
        about_html = self.translations.get(self.current_language, {}).get("about_full_text", "").format(cwd=cwd)

        # Charger le contenu HTML dans le QWebEngineView
        self.html_preview.setHtml(about_html)  # Charger le texte dans le WebEngineView

        # Boutons de langue
        self.add_language_buttons(main_layout)

        # Bouton de fermeture
        self.close_button = QPushButton(self.t("close"))
        self.close_button.clicked.connect(about_window.close)
        main_layout.addWidget(self.close_button, alignment=Qt.AlignmentFlag.AlignCenter)

        # Gestion du chemin pour la documentation "info.html"
        try:
            # Si l'application est compilée, chercher dans _MEIPASS
            if hasattr(sys, '_MEIPASS'):  # Si l'application est compilée
                info_path = Path(sys._MEIPASS) / 'assets' / 'info.html'
            else:
                info_path = config.ASSETS_DIR / 'info.html'

            # Vérifier si le fichier existe
            if info_path.exists():
                with open(info_path, 'r', encoding='utf-8') as file:
                    info_html = file.read()
                    self.html_preview.setHtml(info_html)  # Afficher le contenu de info.html dans la fenêtre "À propos"
            else:
                QMessageBox.warning(self, "Erreur", "Impossible de trouver la documentation.")
                return

        except Exception as e:
            print(f"[ERROR] Error loading info.html: {e}")
            self.html_preview.setHtml(self.t("error_loading_info"))  # Message d'erreur

        # === Définir la taille minimale de la fenêtre ===
        about_window.setMinimumSize(600, 400)  # Par exemple, 600x400 pixels pour la fenêtre

        # Optionnel : ajuster la taille automatique selon le contenu
        about_window.resize(600, 400)  # Si tu veux aussi définir une taille de départ

        # Affichage
        about_window.exec()



    def add_language_buttons(self, main_layout):
        # Récupérer le style des boutons de langue depuis le fichier de styles
        language_button_style = get_language_button_style()

        def make_flag_button(code, tooltip):
            btn = QPushButton()
            #flag_path = Path(resource_path(f"assets/flags/{code}.png"))
            flag_path = config.ASSETS_DIR / "flags" / f"{code}.png"
            btn.setIcon(QIcon(QPixmap(str(flag_path))))
            btn.setStyleSheet(language_button_style)
            btn.setToolTip(tooltip)
            btn.clicked.connect(lambda: self.change_language(code))
            self.apply_hover_effect(btn)
            return btn

        # Création des boutons avec leurs codes de langue et labels
        buttons = {
            'fr': "Français",
            'en': "English",
            'de': "Deutsch",
            'it': "Italiano",
            'es': "Español",
            'nl': "Nederlands",
            'pt': "Português",
            'pl': "Polski",
            'sw': "Svenska",         # Sweden → "Swedish"
            'dk': "Dansk",           # Denmark → "Danish"
            'fi': "Suomi",           # Finland → "Finnish"
            'gr': "Ελληνικά",        # Greek in native script
            'cz': "Čeština"          # Czech
        }

        language_layout = QHBoxLayout()
        for code, label in buttons.items():
            btn = make_flag_button(code, label)
            setattr(self, f"{code}_button", btn)
            language_layout.addWidget(btn)

        language_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
        main_layout.addLayout(language_layout)


    
    def eventFilter(self, obj, event):
        if isinstance(obj, QPushButton):
            if event.type() == QEvent.Enter:
                obj.setIconSize(QSize(42, 42))
            elif event.type() == QEvent.Leave:
                obj.setIconSize(QSize(32, 32))
        return super().eventFilter(obj, event)
        
    def apply_hover_effect(self, button: QPushButton):
        button.setIconSize(QSize(32, 32))
        button.installEventFilter(self)


    def render_current_pdf_page(self, dpi=150, adapt_to_width=False):
        if not self.pdf_doc or self.pdf_doc.page_count == 0:
            self.show_text_preview(self.t("pdf_empty_or_invalid"))
            return

        try:
            page = self.pdf_doc.load_page(self.current_pdf_page)
            pix = page.get_pixmap(dpi=dpi)
            mode = QImage.Format_RGB888 if pix.alpha == 0 else QImage.Format_RGBA8888
            img = QImage(pix.samples, pix.width, pix.height, pix.stride, mode)
            pixmap = QPixmap.fromImage(img)

            if pixmap.isNull():
                self.show_text_preview(self.t("error_invalid_image"))
                return

            self.original_pixmap = pixmap
            self.preview_stack.setCurrentWidget(self.image_scroll)

            if adapt_to_width:
                viewport_width = self.image_scroll.viewport().width()
                ratio = viewport_width / pixmap.width()
                self.current_zoom = ratio  # stocker le zoom de départ
                self.update_zoom_label()

            self.apply_zoom()

            self.text_preview.hide()
            self.image_scroll.show()
            self.zoom_controls_widget.show()

            if self.pdf_doc.page_count > 1:
                self.prev_page_button.show()
                self.next_page_button.show()
            else:
                self.prev_page_button.hide()
                self.next_page_button.hide()

        except Exception as e:
            self.show_text_preview(self.t("pdf_render_error").format(error=e))

        if self.pdf_doc:
            label = self.t("zoom_with_page").format(
                percent=int(self.current_zoom * 100),
                current=self.current_pdf_page + 1,
                total=self.pdf_doc.page_count
            )
        else:
            label = f"{self.t('zoom')} {int(self.current_zoom * 100)}%"
        self.zoom_label.setText(label)

        if hasattr(self, "top_page_selector") and self.top_page_selector.isEnabled():
            self.top_page_selector.blockSignals(True)
            self.top_page_selector.setCurrentIndex(self.current_pdf_page)
            self.top_page_selector.blockSignals(False)




    def apply_zoom(self):
        if not self.original_pixmap:
            return

        # Taille cible en fonction du facteur de zoom
        scaled_pixmap = self.original_pixmap.scaled(
            self.original_pixmap.size() * self.current_zoom,
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation
        )
        self.image_preview_label.setPixmap(scaled_pixmap)
        self.image_preview_label.adjustSize()


    def show_image_preview(self, pixmap):
        self.preview_stack.setCurrentWidget(self.image_scroll)
        self.image_scroll.show()
        self.original_pixmap = pixmap

        viewport_width = self.image_scroll.viewport().width()
        if pixmap.width() == 0:
            self.show_text_preview(self.t("error_invalid_image_width"))
            return

        scale_ratio = viewport_width / pixmap.width()
        fitted_pixmap = pixmap.scaledToWidth(viewport_width, Qt.SmoothTransformation)

        self.image_preview_label.setPixmap(fitted_pixmap)
        self.image_preview_label.adjustSize()

        self.current_zoom = scale_ratio
        self.update_zoom_label()

        self.zoom_controls_widget.show()
        if self.pdf_doc and self.pdf_doc.page_count > 1:
            self.prev_page_button.show()
            self.next_page_button.show()
        else:
            self.prev_page_button.hide()
            self.next_page_button.hide()

        self.preview_stack.setCurrentWidget(self.image_scroll)

    def preview_textual_file(self, path: Path):
        ext = path.suffix.lower()
        try:
            if ext in [".txt", ".md", ".py"]:
                with open(path, "r", encoding="utf-8", errors="ignore") as f:
                    text = f.read()
                self.display_text_content(text)

            elif ext == ".epub":
                self.show_epub_preview(path)

            elif ext in [".docx"]:
                print(f"[DEBUG] Lecture Word (Mammoth) : {path}")
                try:
                    html = convert_docx_to_html(path)
                    full_html = f"<html><body style='font-family:sans-serif;padding:20px'>{html}</body></html>"

                    from tempfile import NamedTemporaryFile
                    tmp = NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8")
                    tmp.write(full_html)
                    tmp.close()

                    if WEB_ENGINE_AVAILABLE and self.html_preview:
                        # même logique que pour EPUB : supprimer ancien fichier et recréer html_preview
                        self.html_preview.load(QUrl.fromLocalFile(tmp.name))
                        self.show_preview_widget(self.html_preview)
                    else:
                        self.show_text_preview(self.t("disabled_html_preview"))

                except Exception as e:
                    self.show_text_preview(self.t("error_docx_mammoth").format(error=str(e)))

            else:
                self.display_text_content(self.t("unsupported_text_type"))

        except Exception as e:
            self.display_text_content(self.t("textfile_read_error").format(error=str(e)))

    def display_text_content(self, text: str):
        self.clear_preview()
        self.text_preview.setPlainText(text)
        self.text_preview.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)  
        #self.text_preview.setAlignment(Qt.AlignTop)
        self.text_preview.setVisible(True)
        self.preview_stack.setCurrentWidget(self.text_preview)

    def show_text_preview(self, text):
        self.clear_preview()
        self.text_preview.setPlainText(text)
        self.text_preview.setVisible(True)
        self.preview_stack.setCurrentWidget(self.text_preview)
    
    def rotate_image(self):
        if hasattr(self, 'current_image'):
            try:
                # Tourner l'image de 90 degrés
                rotated_image = self.current_image.rotate(90, expand=True)
                self.current_image = rotated_image  # Mettre à jour l'image avec la version tournée

                # Convertir l'image Pillow en QPixmap
                qim = ImageQt(rotated_image)  # Convertir l'image Pillow en QImage
                pixmap = QPixmap.fromImage(qim)  # Convertir QImage en QPixmap

                # Réafficher l'image après rotation
                self.show_image_preview(pixmap)  # Utiliser un QPixmap pour l'affichage
            except Exception as e:
                print(f"[ERREUR] Erreur lors de la rotation de l'image : {e}")
        else:
            print("[ERREUR] Aucune image à faire pivoter.")

    def zoom_in(self):
        self.current_zoom = min(self.current_zoom + 0.1, 5.0)
        self.update_zoom_label()
        self.update_pdf_zoom()

    def zoom_out(self):
        self.current_zoom = max(0.2, self.current_zoom - 0.1)
        self.update_zoom_label()
        self.update_pdf_zoom()

    def update_zoom_label(self):
        if self.pdf_doc:
            self.zoom_label.setText(
                self.t("zoom_with_page").format(
                    percent=int(self.current_zoom * 100),
                    current=self.current_pdf_page + 1,
                    total=self.pdf_doc.page_count
                )
            )
        else:
            self.zoom_label.setText(
                f"{self.t('zoom')}{int(self.current_zoom * 100)}%"
            )


    def update_pdf_zoom(self):
        if self.pdf_doc:
            dpi = int(150 * self.current_zoom)
            dpi = max(72, min(dpi, 600))
            self.render_current_pdf_page(dpi)
        else:
            self.apply_zoom()

    def previous_pdf_page(self):
        if self.pdf_doc and self.current_pdf_page > 0:
            self.current_pdf_page -= 1
            self.render_current_pdf_page()

    def next_pdf_page(self):
        if self.pdf_doc and self.current_pdf_page < self.pdf_doc.page_count - 1:
            self.current_pdf_page += 1
            self.render_current_pdf_page()

    def eventFilter(self, source, event):
        if source is self.image_scroll.viewport():
            if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton:
                self._drag_start_pos = event.pos()
                source.setCursor(Qt.ClosedHandCursor)
                return True
            elif event.type() == QEvent.MouseMove and self._drag_start_pos:
                delta = self._drag_start_pos - event.pos()
                self.image_scroll.horizontalScrollBar().setValue(
                    self.image_scroll.horizontalScrollBar().value() + delta.x())
                self.image_scroll.verticalScrollBar().setValue(
                    self.image_scroll.verticalScrollBar().value() + delta.y())
                self._drag_start_pos = event.pos()
                return True
            elif event.type() == QEvent.MouseButtonRelease:
                self._drag_start_pos = None
                source.setCursor(Qt.OpenHandCursor)
                return True
        return super().eventFilter(source, event)

    def load_image_file(self, image_path):
        pixmap = QPixmap(str(image_path))
        if pixmap.isNull():
            self.show_text_preview(self.t("error_image_load_failed"))
            return

        self.pdf_doc = None  # on s'assure qu’on n’est plus en mode PDF
        self.current_zoom = 1.0
        self.show_image_preview(pixmap)

    def display_epub(self, file_path):
        try:
            book = epub.read_epub(str(file_path))
            content = ""
            for item in book.get_items():
                if isinstance(item, epub.EpubHtml):  
                    soup = BeautifulSoup(item.get_content(), 'html.parser')
                    content += soup.get_text() + "\n\n"

            if content.strip():
                self.text_preview.setPlainText(content)
            else:
                self.text_preview.setPlainText(self.t("epub_empty_or_unreadable"))

            self.text_preview.setVisible(True)

        except Exception as e:
            print(f"[ERREUR EPUB] {e}")
            self.text_preview.setPlainText(self.t("epub_open_error"))
            self.text_preview.setVisible(True)


    def reindex_files(self):
        class ReindexWorker(QObject):
            progress = Signal(int, int)
            finished = Signal(int)
            error = Signal(str)

            def run(self):
                try:
                    from database.reindex import reindex_files_with_progress
                    print("[DEBUG] Réindexation démarrée...")
                    count = reindex_files_with_progress(self.progress.emit)
                    print(f"[DEBUG] Réindexation terminée : {count} fichiers indexés.")
                    self.finished.emit(count)
                except Exception as e:
                    print("[ERREUR] Réindexation échouée :", e)
                    self.error.emit(str(e))

        self.progress_bar.setValue(0)
        self.progress_bar.setVisible(True)
        self.reindex_button.setEnabled(False)

        self.worker_thread = QThread()
        self.worker = ReindexWorker()
        self.worker.moveToThread(self.worker_thread)

        self.worker_thread.started.connect(self.worker.run)
        self.worker.progress.connect(self._update_progress_value)
        self.worker.finished.connect(self.on_reindex_done)
        self.worker.error.connect(self.on_reindex_error)

        # Quitter proprement
        self.worker.finished.connect(self.worker_thread.quit)
        self.worker.error.connect(self.worker_thread.quit)

        # Libérer les objets
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker_thread.finished.connect(self.worker_thread.deleteLater)

        # Attendre la fin réelle du thread
        self.worker_thread.finished.connect(lambda: self.worker_thread.wait())

        self.worker_thread.start()

    def import_external_file_or_folder(self):
        from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
        import shutil
        from pathlib import Path
        import os

        # 1) Choix du type d'import
        type_choice, ok = QInputDialog.getItem(
            self,
            self.t("import_type_title"),
            self.t("import_type_question"),
            [
                self.t("import_file"),
                self.t("import_folder"),
                self.t("create_new_folder")
            ],
            editable=False
        )
        if not ok:
            return

        # 2) Création d'un nouveau dossier si souhaité
        if type_choice == self.t("create_new_folder"):
            folder_name, ok = QInputDialog.getText(
                self,
                self.t("new_folder_title"),
                self.t("new_folder_question")
            )
            if ok and folder_name.strip():
                selected_item = self.tree.currentItem()
                if selected_item:
                    rel_path = selected_item.data(0, Qt.ItemDataRole.UserRole)
                    selected_path = config.FILES_DIR / rel_path
                    base_dir = selected_path if selected_path.is_dir() else selected_path.parent
                else:
                    base_dir = config.FILES_DIR

                new_path = base_dir / folder_name.strip()
                try:
                    new_path.mkdir(parents=True, exist_ok=False)
                    (new_path / ".keep").touch()
                    self.folder_to_select_after_reindex = str(new_path.relative_to(config.FILES_DIR))
                    self.reindex_files()
                    QMessageBox.information(
                        self,
                        self.t("folder_created_title"),
                        self.t("folder_created_message").format(name=folder_name)
                    )
                except FileExistsError:
                    QMessageBox.warning(
                        self,
                        self.t("folder_exists_title"),
                        self.t("folder_exists_message").format(name=folder_name)
                    )
                except Exception as e:
                    QMessageBox.critical(
                        self,
                        self.t("folder_error_title"),
                        self.t("folder_error_message").format(error=str(e))
                    )
            return

        # 3) Choix du fichier ou dossier source
        if type_choice == self.t("import_file"):
            source_path, _ = QFileDialog.getOpenFileName(
                self, self.t("import_file_dialog"), "", "Tous les fichiers (*)"
            )
        else:
            source_path = QFileDialog.getExistingDirectory(
                self, self.t("import_folder_dialog")
            )
        if not source_path:
            return

        file_path = Path(source_path)
        folder_name = file_path.name

        # 4) Choix du mode (copy ou link)
        options = {
            "copy": self.t("import_mode_copy"),    # ex: "Copier"
            "link": self.t("import_mode_link")     # ex: "Créer un lien symbolique"
        }
        reverse_options = {v: k for k, v in options.items()}
        selected_text, ok = QInputDialog.getItem(
            self,
            self.t("import_mode_title"),
            self.t("import_mode_question"),
            list(options.values()),
            editable=False
        )
        if not ok:
            return
        mode = reverse_options[selected_text]

        # 5) NOUVEAU : choix du sous-dossier DESTINATION dans files/
        dest_dir = QFileDialog.getExistingDirectory(
            self,
            self.t("select_target_folder"),
            str(config.FILES_DIR),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
        )
        if not dest_dir:
            return
        dest_dir = Path(dest_dir)
        target = dest_dir / folder_name

        # 6) Copie / symlink + réindex
        try:
            if file_path.is_dir():
                if mode == "copy":
                    shutil.copytree(str(file_path), str(target))
                else:
                    try:
                        os.symlink(str(file_path), str(target))
                    except (OSError, NotImplementedError):
                        shutil.copytree(str(file_path), str(target))
            else:
                if mode == "copy":
                    shutil.copy2(str(file_path), str(target))
                else:
                    try:
                        os.symlink(str(file_path), str(target))
                    except (OSError, NotImplementedError):
                        shutil.copy2(str(file_path), str(target))

            # Met à jour le dossier à sélectionner après réindex
            self.folder_to_select_after_reindex = str(dest_dir.relative_to(config.FILES_DIR))
            self.reindex_files()

            # Message de succès
            msg_key = "import_success_folder" if file_path.is_dir() else "import_success_file"
            QMessageBox.information(
                self,
                self.t("import_success_title"),
                self.t(msg_key).format(name=folder_name)
            )

        except Exception as e:
            # En cas d'erreur, annule la barre de progression et affiche l'erreur
            self.hide_progress_bar_fade()
            QMessageBox.critical(
                self,
                self.t("import_error_title"),
                self.t("import_error_message").format(error=str(e))
            )


    def on_reindex_done(self, count):
        print(f"[INFO] Réindexation : {count} fichier(s) détecté(s)")

        # Important : déconnecte les signaux ici aussi
        try: self.worker.progress.disconnect()
        except: pass
        try: self.worker.finished.disconnect()
        except: pass

        self.hide_progress_bar_fade()
        self.reindex_button.setEnabled(True)
        self.load_documents()
        self.clear_preview()

        # Sélectionner le dossier créé s’il a été stocké
        if hasattr(self, "folder_to_select_after_reindex"):
            self.select_and_expand_item(self.folder_to_select_after_reindex)
            del self.folder_to_select_after_reindex

        self.update_file_count()

        # Rafraîchir la prévisualisation si un fichier est toujours sélectionné
        current_item = self.tree.currentItem()
        if current_item:
            rel_path = current_item.data(0, Qt.ItemDataRole.UserRole)
            if rel_path:
                self.load_preview_from_path(rel_path)

        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setWindowTitle(self.t("reindex_title"))
        msg.setText(self.t("reindex_done_message").format(count=count))
        msg.setStyleSheet(get_message_box_style(self.current_theme))
        msg.exec()

    def on_reindex_error(self, error):
        try:
            self.worker.progress.disconnect()
            self.worker.error.disconnect()
        except:
            pass

        self.hide_progress_bar_fade()
        self.reindex_button.setEnabled(True)

        QMessageBox.critical(
            self,
            self.t("reindex_error_title"),
            self.t("reindex_error_message").format(error=error)
        )

    def select_and_expand_item(self, rel_path):
        parts = rel_path.split(os.sep)
        parent = None
        path_accumulator = []

        for i, part in enumerate(parts):
            path_accumulator.append(part)
            current_path = os.sep.join(path_accumulator)
            existing = self.find_tree_item(part, parent)

            if not existing:
                return  # l'élément n'a pas été trouvé

            parent = existing

            if i == len(parts) - 1:
                existing.setSelected(True)
                self.tree.scrollToItem(existing)
            else:
                existing.setExpanded(True)


    def on_tree_item_clicked(self, item, column):
        rel_path = item.data(0, Qt.ItemDataRole.UserRole)
        if not rel_path:
            self.set_metadata_fields_enabled(False)
            self.metadata_form.hide()
            self.toggle_metadata_button.setEnabled(False)
            return

        full_path = config.FILES_DIR / rel_path  

        if full_path.is_dir():
            # bascule ouverture/fermeture du dossier
            item.setExpanded(not item.isExpanded())
            # on ne poursuit pas le chargement de métadonnées pour un dossier
            return

        # --- comportement inchangé pour l’affichage des fichiers ---
        self.load_preview_from_path(rel_path)
        self.load_metadata(rel_path)
        self.toggle_metadata_button.setEnabled(True)



    def load_preview_from_path(self, rel_path):
        self.clear_preview()  # Réinitialiser la zone de prévisualisation
        doc_path = config.FILES_DIR / rel_path  
        self.file_info_label.setText(f"📄 {rel_path}")

        if not doc_path.exists():
            self.show_text_preview(self.t("error_file_not_found"))
            return

        ext = os.path.splitext(doc_path)[1].lower()

        try:
            if ext in [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tif", ".tiff"]:
                # C'est une image
                image = Image.open(str(doc_path))
                self.current_image = image  # Stocker l'image actuelle pour la rotation

                # Vérifier EXIF pour orientation et corriger
                try:
                    for orientation in ExifTags.TAGS.keys():
                        if ExifTags.TAGS[orientation] == 'Orientation':
                            break
                    exif = dict(image._getexif().items())
                    orientation_value = exif.get(orientation, None)
                    if orientation_value == 3:
                        image = image.rotate(180, expand=True)
                    elif orientation_value == 6:
                        image = image.rotate(270, expand=True)
                    elif orientation_value == 8:
                        image = image.rotate(90, expand=True)
                except Exception as exif_err:
                    print(f"[INFO] Aucune orientation EXIF ou erreur : {exif_err}")

                # Convertir l'image Pillow en QPixmap
                qim = ImageQt(image)
                self.original_pixmap = QPixmap.fromImage(qim)  # Stocker l'image sous forme de QPixmap

                self.show_image_preview(self.original_pixmap)  # Afficher l'image en QPixmap

                # Afficher le bouton de rotation uniquement pour les images
                self.rotate_button.setVisible(True)  

            else:
                # Si ce n'est pas une image, on cache le bouton de rotation
                self.rotate_button.setVisible(False)

        except Exception as e:
            print(f"[ERREUR] Erreur de prévisualisation de l'image : {e}")
            self.show_text_preview(self.t("error_invalid_image"))

        try:
            # === HTML ===
            if ext in [".html", ".htm"]:
                if WEB_ENGINE_AVAILABLE:
                    self.html_preview = QWebEngineView()
                    self.preview_stack.addWidget(self.html_preview)
                    self.preview_stack.setCurrentWidget(self.html_preview)
                    self.html_preview.load(QUrl.fromLocalFile(str(doc_path)))
                else:
                    with open(doc_path, "r", encoding="utf-8", errors="ignore") as f:
                        content = f.read()
                    self.show_text_preview(content)

            # === TEXTES ===
            elif ext in [".txt", ".md", ".py", ".epub", ".doc"]:
                self.preview_textual_file(doc_path)

            # === PDF ===
            elif ext == ".pdf":
                self.pdf_doc = fitz.open(str(doc_path))
                self.current_pdf_page = 0
                self.current_zoom = 1.0
                self.render_current_pdf_page(adapt_to_width=True)

                page_count = self.pdf_doc.page_count
                print(f"[DEBUG] PDF détecté avec {page_count} pages")

                if page_count > 1:
                    self.top_page_selector.blockSignals(True)
                    self.top_page_selector.clear()
                    for i in range(page_count):
                        icon = qta.icon("fa5.file-alt", color="#007BFF")  # ou "fa.file", "fa5s.file", etc.
                        self.top_page_selector.addItem(icon, f"{self.t('page')} {i + 1}")
                        #self.top_page_selector.addItem(f"Page {i + 1}")
                    self.top_page_selector.setCurrentIndex(0)
                    self.top_page_selector.setVisible(True)
                    self.top_page_selector.setEnabled(True)
                    self.top_page_selector.blockSignals(False)
                    self.top_selector_widget.setVisible(True)  # ← ICI !!!
                else:
                    self.top_page_selector.clear()
                    self.top_page_selector.setVisible(False)
                    self.top_page_selector.setEnabled(False)
                    self.top_selector_widget.setVisible(False)  # ← ICI AUSSI !!!

            # === IMAGES ===
            elif ext in [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tif", ".tiff"]:
                try:
                    image = Image.open(str(doc_path))
                    try:
                        for orientation in ExifTags.TAGS.keys():
                            if ExifTags.TAGS[orientation] == 'Orientation':
                                break
                        exif = dict(image._getexif().items())
                        orientation_value = exif.get(orientation, None)
                        if orientation_value == 3:
                            image = image.rotate(180, expand=True)
                        elif orientation_value == 6:
                            image = image.rotate(270, expand=True)
                        elif orientation_value == 8:
                            image = image.rotate(90, expand=True)
                    except Exception as exif_err:
                        print(f"[INFO] Aucune orientation EXIF ou erreur : {exif_err}")

                    image = image.convert("RGBA")
                    data = image.tobytes("raw", "RGBA")
                    qim = QImage(data, image.width, image.height, QImage.Format_RGBA8888)
                    pixmap = QPixmap.fromImage(qim)

                    if pixmap.isNull():
                        self.show_text_preview(self.t("error_invalid_image"))
                    else:
                        self.pdf_doc = None
                        self.current_zoom = 1.0
                        self.show_image_preview(pixmap)
                except Exception as e:
                    QMessageBox.critical(self, self.t("error_loading_image_title"), str(e))

            # === DOCX ===
            elif ext == ".docx":
                html = self.show_docx_preview(doc_path)
                if html and WEB_ENGINE_AVAILABLE:
                    self.html_preview = QWebEngineView()
                    self.preview_stack.addWidget(self.html_preview)
                    self.preview_stack.setCurrentWidget(self.html_preview)
                    self.html_preview.setHtml(html)
                else:
                    self.show_text_preview(self.t("docx_empty_or_webengine_unavailable"))
            
            # === XLSX ===
            elif ext in [".xlsx", ".xlsm"]:
                self.show_xlsx_preview(doc_path)

            # === PPTX ===
            elif ext == ".pptx":
                content = odf_utils.extract_text_from_pptx(doc_path)
                if content:
                    self.show_text_preview(content)
                else:
                    self.show_text_preview(self.t("textfile_read_error").format(error="PPTX vide ou invalide"))

            # === ODT ===
            elif ext == ".odt":
                html = odf_utils.odf_to_html(doc_path)
                if html and WEB_ENGINE_AVAILABLE:
                    self.html_preview = QWebEngineView()
                    self.preview_stack.addWidget(self.html_preview)
                    self.preview_stack.setCurrentWidget(self.html_preview)
                    self.html_preview.setHtml(html)
                else:
                    self.show_text_preview(self.t("odt_empty_or_webengine_unavailable"))

            # === ODS / ODP ===
            elif ext == ".ods":
                self.show_ods_preview(rel_path)

            elif ext == ".odp":
                html = odf_utils.odp_to_html(doc_path)
                self.show_html_preview(html)
            
            # === XML ===
            elif ext == ".xml":
                self.show_xml_preview(rel_path)
            
            # === SVG ===
            elif ext == ".svg":
                self.show_svg_preview(rel_path)

            else:
                self.show_text_preview(self.t("error_unsupported"))

        except Exception as e:
            QMessageBox.critical(self, self.t("error_reading_file"), str(e))

    def show_html_preview(self, html):
        from PySide6.QtWebEngineWidgets import QWebEngineView

        if not WEB_ENGINE_AVAILABLE:
            self.show_text_preview(self.t("disabled_html_preview"))
            return

        if hasattr(self, 'html_preview') and self.html_preview:
            index = self.preview_stack.indexOf(self.html_preview)
            self.preview_stack.removeWidget(self.html_preview)
            self.html_preview.deleteLater()

        self.html_preview = QWebEngineView()
        self.html_preview.setHtml(html)
        self.preview_stack.addWidget(self.html_preview)
        self.preview_stack.setCurrentWidget(self.html_preview)

    def show_ods_preview(self, rel_path):
        import pandas as pd
        from PySide6.QtWidgets import QTableWidget, QTableWidgetItem, QAbstractItemView

        file_path = config.FILES_DIR / rel_path
        self.xlsx_tab_widget.clear()

        try:
            xls = pd.ExcelFile(str(file_path), engine="odf")
            for sheet_name in xls.sheet_names:
                df = pd.read_excel(xls, sheet_name=sheet_name, engine="odf")

                table = QTableWidget()
                table.setEditTriggers(QAbstractItemView.NoEditTriggers)
                table.setRowCount(df.shape[0])
                table.setColumnCount(df.shape[1])
                # Définir les en-têtes de colonnes AVANT de remplir les cellules
                table.setHorizontalHeaderLabels([
                    "" if str(col).startswith("Unnamed") else str(col)
                    for col in df.columns
                ])

                # Remplir les cellules
                for i, row in df.iterrows():
                    for j, value in enumerate(row):
                        display_value = "" if pd.isna(value) else str(value)
                        table.setItem(i, j, QTableWidgetItem(display_value))

                table.resizeColumnsToContents()
                table.resizeRowsToContents()
                self.xlsx_tab_widget.addTab(table, sheet_name)

            self.xlsx_tab_widget.setVisible(True)
            self.preview_stack.setCurrentWidget(self.xlsx_tab_widget)

        except Exception as e:
            self.show_text_preview(self.t("textfile_read_error").format(error=str(e)))

    def show_xml_preview(self, rel_path):
        from PySide6.QtWebEngineWidgets import QWebEngineView

        file_path = config.FILES_DIR / rel_path

        if not WEB_ENGINE_AVAILABLE:
            self.show_text_preview(self.t("disabled_html_preview"))
            return

        if hasattr(self, 'xml_preview') and self.xml_preview:
            self.preview_stack.removeWidget(self.xml_preview)
            self.xml_preview.deleteLater()

        self.xml_preview = QWebEngineView()
        self.xml_preview.load(QUrl.fromLocalFile(str(file_path)))
        self.preview_stack.addWidget(self.xml_preview)
        self.preview_stack.setCurrentWidget(self.xml_preview)

    def show_svg_preview(self, rel_path):
        from PySide6.QtWebEngineWidgets import QWebEngineView

        file_path = config.FILES_DIR / rel_path

        if not WEB_ENGINE_AVAILABLE:
            self.show_text_preview(self.t("disabled_html_preview"))
            return

        if hasattr(self, 'svg_web_view') and self.svg_web_view:
            self.preview_stack.removeWidget(self.svg_web_view)
            self.svg_web_view.deleteLater()

        self.svg_web_view = QWebEngineView()
        self.svg_web_view.load(QUrl.fromLocalFile(str(file_path)))
        self.preview_stack.addWidget(self.svg_web_view)
        self.preview_stack.setCurrentWidget(self.svg_web_view)

    def filter_documents(self, text):
        text = text.strip().lower()
        for i in range(self.tree.topLevelItemCount()):
            top_item = self.tree.topLevelItem(i)
            self._filter_tree_item(top_item, text)

    def _filter_tree_item(self, item, query):
        match = query in item.text(0).lower()
        has_visible_child = False

        for i in range(item.childCount()):
            child = item.child(i)
            child_visible = self._filter_tree_item(child, query)
            has_visible_child = has_visible_child or child_visible

        visible = match or has_visible_child
        item.setHidden(not visible)
        return visible


    def perform_search(self):
        raw = self.search_input.text().strip().lower()
        if not raw:
            return

        keywords = [kw for kw in raw.replace(",", " ").split() if kw]
        if not keywords:
            return

        # --- Prépare l'UI ---
        self._is_searching = True
        self._search_aborted = False
        self.search_result_count = 0
        self.search_count_label.setText(self.t("search_results_count_label").format(count=0))
        self.search_count_label.show()
        self.tree.clear()
        self.show_progress_bar_fade()
        self.progress_label.setText(self.t("search_in_progress"))
        self.progress_label.show()
        self.progress_bar.setMaximum(0)
        self.progress_bar.show()
        self.stop_search_button.setEnabled(True)
        self.stop_search_button.setVisible(True)
        self.reset_button.setEnabled(False)

        # --- Thread & Worker sans deleteLater() ---
        thread = QThread(self)
        worker = SearchWorker(keywords, self.t)
        worker.moveToThread(thread)

        # Connexions
        thread.started.connect(worker.run)
        worker.result_found.connect(self.display_search_result)
        worker.progress.connect(self._update_progress_value)
        worker.error.connect(self.on_search_error)
        worker.finished.connect(self.on_search_finished)
        worker.finished.connect(thread.quit)    # arrête le thread au finish

        # Sauvegarde pour pouvoir arrêter/réinitialiser plus tard
        self.search_thread = thread
        self.search_worker = worker
        thread.start()




    def display_search_result(self, doc):
        # 1) Si on a déjà reset, on ne fait plus rien
        if not self._is_searching:
            return

        rel_path = doc[2]
        parts = rel_path.split(os.sep)
        parent = None
        path_accumulator = []

        for i, part in enumerate(parts):
            path_accumulator.append(part)
            current_path = os.sep.join(path_accumulator)

            if i == len(parts) - 1:
                file_item = QTreeWidgetItem([part])
                file_item.setData(0, Qt.ItemDataRole.UserRole, rel_path)
                if parent:
                    parent.addChild(file_item)
                else:
                    self.tree.addTopLevelItem(file_item)

                p = file_item.parent()
                while p:
                    p.setExpanded(True)
                    p = p.parent()

                file_item.setSelected(True)
                self.tree.scrollToItem(file_item)

            else:
                existing = self.find_tree_item(part, parent)
                if existing:
                    folder_item = existing
                else:
                    folder_item = QTreeWidgetItem([part])
                    if parent:
                        parent.addChild(folder_item)
                    else:
                        self.tree.addTopLevelItem(folder_item)
                parent = folder_item
        
        # 3) Mettre à jour le compteur
        self.search_result_count += 1
        self.search_count_label.setText(
            self.t("search_results_count_label").format(count=self.search_result_count)
        )


    def on_search_finished(self):
        # la recherche est finie (normalement ou par stop_search)
        self.hide_progress_bar_fade()
        self.stop_search_button.setVisible(False)

        # On autorise enfin la réinitialisation
        self.reset_button.setEnabled(True)

        if self._search_aborted:
            # on cache simplement le compteur
            self.search_count_label.hide()
        else:
            # fin normale : on affiche message si zéro résultat
            if self.search_result_count == 0:
                QMessageBox.information(
                    self,
                    self.t("search_no_result_title"),
                    self.t("search_no_result_message")
                )
                self.search_count_label.hide()
            else:
                # on remet à jour le label s’il y a eu des résultats
                self.search_count_label.setText(
                    self.t("search_results_count_label").format(count=self.search_result_count)
                )

        # Et on coupe proprement le thread s’il n’est pas encore terminé
        thr = getattr(self, 'search_thread', None)
        if thr and thr.isRunning():
            thr.quit()
            thr.wait()
        self.search_thread = None
        self.search_worker = None

                

    def reset_ui(self):
        # ne touche pas au thread ni au worker : ils sont déjà terminés
        self._is_searching = False
        self._search_aborted = False

        # UI
        self.search_input.clear()
        self.tree.clear()
        self.search_count_label.clear()
        self.search_count_label.hide()
        self.hide_progress_bar_fade()  # masque bar & label de prog
        self.progress_label.clear()
        self.progress_label.hide()
        self.progress_bar.reset()
        self.progress_bar.hide()
        self.stop_search_button.setVisible(False)

        # remet l'arborescence par défaut
        self.load_documents()
        self.clear_preview()
        self.update_file_count()

        # désactive le bouton “Réinitialiser” jusqu’à la prochaine recherche
        self.reset_button.setEnabled(False)

    def refresh_ui(self):
        """
        Vide la prévisualisation et remet
        l’arborescence + l’UI au « state » de démarrage.
        """
        # 1) Vider la preview
        self.clear_preview()
        
        # 2) Recharger l’arborescence complète
        self.tree.clear()
        self.load_documents()
        
        # 3) Cacher/vider tous les éléments de recherche
        self.search_input.clear()
        self.search_count_label.clear()
        self.search_count_label.hide()
        self.progress_bar.reset()
        self.progress_bar.hide()
        self.progress_label.clear()
        self.progress_label.hide()
        self.stop_search_button.setVisible(False)
        
        # 4) Remettre l’état des métadonnées à « vide »
        self.set_metadata_fields_enabled(False)
        self.toggle_metadata_button.setChecked(False)
        self.metadata_form.hide()
        
        # 5) Réinitialiser les var. internes si besoin
        self.pdf_doc = None
        self.original_pixmap = None
        self.current_zoom = 1.0
        self.current_pdf_page = 0




    def on_search_error(self, error):
        self.hide_progress_bar_fade()
        QMessageBox.critical(self, self.t("search_error_title"), str(error))

    def stop_search(self):
        # désactive toute suite de résultats
        self._search_aborted = True
        self._is_searching   = False
        self.stop_search_button.setEnabled(False)
        if getattr(self, 'search_worker', None):
            self.search_worker.stop()




    def find_tree_item(self, name, parent=None):
        if parent is None:
            # Cherche parmi les top-level
            count = self.tree.topLevelItemCount()
            for i in range(count):
                item = self.tree.topLevelItem(i)
                if item.text(0) == name:
                    return item
        else:
            # Cherche parmi les enfants
            count = parent.childCount()
            for i in range(count):
                item = parent.child(i)
                if item.text(0) == name:
                    return item
        return None

    def toggle_theme(self):
        if self.current_theme == "dark":
            self.current_theme = "light"
            self.apply_light_theme()
        else:
            self.current_theme = "dark"
            self.apply_dark_theme()
        
        self.meta_tag_input_arrow.setIcon(
            qta.icon("fa5s.chevron-down", color="black" if self.current_theme == "light" else "white")
        )
        self.meta_tag_input.setStyleSheet(get_tag_input_style(self.current_theme))

        config.save_user_theme(self.current_theme) 
        self.update_logo()


    def apply_light_theme(self):
        QApplication.setStyle(QStyleFactory.create("Fusion"))
        QApplication.setPalette(self.get_light_palette())
        QApplication.instance().setStyleSheet(QApplication.instance().styleSheet() + get_zoom_button_style())
        self.setStyleSheet(LIGHT_THEME_STYLESHEET + get_zoom_button_style())
        self.text_preview.setStyleSheet(get_text_preview_style("light"))  

        apply_common_styles(self)

    def apply_dark_theme(self):
        QApplication.setPalette(self.get_dark_palette())
        self.setStyleSheet(DARK_THEME_STYLESHEET + get_zoom_button_style())
        QApplication.instance().setStyleSheet(QApplication.instance().styleSheet() + get_zoom_button_style())
        self.text_preview.setStyleSheet(get_text_preview_style("dark"))  

        apply_common_styles(self)
    
    def get_light_palette(self):
        palette = QPalette()
        palette.setColor(QPalette.Window, QColor("#fdfdfd"))
        palette.setColor(QPalette.WindowText, QColor("#202020"))
        palette.setColor(QPalette.Base, QColor("#ffffff"))
        palette.setColor(QPalette.Text, QColor("#202020"))
        palette.setColor(QPalette.ToolTipBase, QColor("#ffffff"))
        palette.setColor(QPalette.ToolTipText, QColor("#000000"))
        palette.setColor(QPalette.Button, QColor("#f4f4f4"))
        palette.setColor(QPalette.ButtonText, QColor("#202020"))
        return palette

    def get_dark_palette(self):
        palette = QPalette()
        palette.setColor(QPalette.Window, QColor("#2e2e2e"))
        palette.setColor(QPalette.WindowText, QColor("#ffffff"))
        palette.setColor(QPalette.Base, QColor("#3c3c3c"))
        palette.setColor(QPalette.Text, QColor("#ffffff"))
        palette.setColor(QPalette.ToolTipBase, QColor("#333333"))
        palette.setColor(QPalette.ToolTipText, QColor("#ffffff"))
        palette.setColor(QPalette.Button, QColor("#444444"))
        palette.setColor(QPalette.ButtonText, QColor("#ffffff"))
        return palette

    def apply_button_styles(self):
        style = get_theme_button_style(self.current_theme)

        for btn in [
            self.reindex_button,
            self.import_button,
            self.reset_button,
            self.theme_button,
            self.search_button,
            self.zoom_in_button,
            self.zoom_out_button,
            self.prev_page_button,
            self.next_page_button
        ]:
            btn.setStyleSheet(style)
            btn.setFixedSize(40, 40)


    def load_metadata(self, rel_path):
        full_path = config.FILES_DIR / rel_path  

        if full_path.is_dir():  # Correction ici
            self.set_metadata_fields_enabled(False)
            self.toggle_metadata_button.setEnabled(False)
            self.metadata_form.hide()
            return

        try:
            with sqlite3.connect(config.DB_PATH) as conn:
                cur = conn.cursor()
                cur.execute("""
                    SELECT author, tags, comment, version, updated_at
                    FROM document_metadata
                    WHERE document_path = ?
                """, (rel_path,))
                row = cur.fetchone()

            if row:
                author, tags_text, comment, version, updated_at = row

                self.meta_author_field.setText(author or "")
                self.meta_comment_field.setPlainText(comment or "")
                self.meta_version_field.setText(version or "")
                self.meta_updated_field.setText(updated_at or "")

                # === CHARGEMENT DES TAGS ===
                self.current_tags = []
                self.clear_tags_from_layout()


                for tag in [t.strip() for t in (tags_text or "").split(",") if t.strip()]:
                    self.add_tag(tag)

            else:
                # Aucun metadata connu → réinitialiser
                self.meta_author_field.clear()
                self.meta_comment_field.clear()
                self.meta_version_field.clear()
                self.meta_updated_field.clear()

                self.current_tags = []
                self.clear_tags_from_layout()


            self.current_metadata_path = rel_path
            self.set_metadata_fields_enabled(True)
            self.toggle_metadata_button.setEnabled(True)

        except Exception as e:
            print(f"[ERREUR] Chargement métadonnées depuis la base pour {rel_path} : {e}")
            self.set_metadata_fields_enabled(False)
            self.toggle_metadata_button.setEnabled(False)
            self.metadata_form.hide()



    def save_current_metadata(self):
        if not hasattr(self, "current_metadata_path") or not self.current_metadata_path:
            print("[ERREUR] Aucune métadonnée en cours à sauvegarder.")
            return  # On arrête la fonction si le chemin des métadonnées est invalide ou non défini

        metadata = {
            "author": self.meta_author_field.text(),
            "tags": ", ".join(self.current_tags),
            "comment": self.meta_comment_field.toPlainText(),
            "version": self.meta_version_field.text(),
            "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }

        # Sauvegarde en base
        save_metadata_to_db(self.current_metadata_path, metadata)

        print(f"[DEBUG] Sauvegarde en base des métadonnées : {self.current_metadata_path}")

        self.meta_updated_field.setText(metadata["updated_at"])
        self.save_confirmation_label.setText(self.t("metadata_saved"))
        self.save_confirmation_label.show()
        QTimer.singleShot(2000, self.save_confirmation_label.hide)



    def set_metadata_fields_enabled(self, enabled: bool):
        self.meta_author_field.setEnabled(enabled)
        self.meta_comment_field.setEnabled(enabled)
        self.meta_version_field.setEnabled(enabled)
        self.meta_updated_field.setEnabled(enabled)  # <--- indispensable
        self.save_metadata_button.setEnabled(enabled)

    def toggle_metadata_visibility(self):
        is_open = self.toggle_metadata_button.isChecked()
        icon_name = "fa5s.angle-up" if is_open else "fa5s.angle-down"
        self.toggle_metadata_button.setIcon(qta.icon(icon_name, color="#007BFF"))

        self.metadata_animation.stop()

        if is_open:
            self.metadata_form.setMaximumHeight(0)  # Reset animation
            self.metadata_form.show()
            self.metadata_animation.setStartValue(0)
            self.metadata_animation.setEndValue(self.metadata_form.sizeHint().height())
        else:
            self.metadata_animation.setStartValue(self.metadata_form.height())
            self.metadata_animation.setEndValue(0)

        self.metadata_animation.start()

    def on_metadata_animation_finished(self):
        if not self.toggle_metadata_button.isChecked():
            self.metadata_form.hide()

    def add_tag_from_input(self):
        tag = self.meta_tag_input.currentText().strip()
        self.add_tag(tag)

    def clear_tags_from_layout(self):
        """Supprime tous les widgets de tags du layout, sauf le champ d'entrée."""
        for i in reversed(range(self.meta_tags_layout.count())):
            item = self.meta_tags_layout.itemAt(i)
            widget = item.widget()
            if widget and widget != self.meta_tag_input:
                widget.setParent(None)
                widget.deleteLater()


    def on_tag_typing(self, text):
        if "," in text:
            parts = text.split(",")
            for part in parts[:-1]:
                part = part.strip()
                if part and part not in self.current_tags:
                    self.add_tag(part)
            self.meta_tag_input.setText(parts[-1].strip())

    def add_tag(self, tag):
        if not tag or tag.lower() in [t.lower() for t in self.current_tags]:
            return

        widget = self.create_tag_widget(tag)
        widget.setGraphicsEffect(QGraphicsOpacityEffect(widget))
        widget.graphicsEffect().setOpacity(0)

        self.meta_tags_layout.addWidget(widget)
        self.current_tags.append(tag)

        # Animation
        fade_anim = QPropertyAnimation(widget.graphicsEffect(), b"opacity")
        fade_anim.setDuration(300)
        fade_anim.setStartValue(0.0)
        fade_anim.setEndValue(1.0)

        grow_anim = QPropertyAnimation(widget, b"maximumHeight")
        grow_anim.setDuration(300)
        grow_anim.setStartValue(0)
        grow_anim.setEndValue(32)

        anim_group = QParallelAnimationGroup()
        anim_group.addAnimation(fade_anim)
        anim_group.addAnimation(grow_anim)
        anim_group.start()
        widget._anim_group = anim_group  # évite le GC

        self.meta_tag_input.setCurrentText("")
        
        self.refresh_tag_suggestions()

    def remove_tag(self, widget):
        tag = widget.property("tag_text")
        if tag in self.current_tags:
            self.current_tags.remove(tag)
        widget.setParent(None)
        widget.deleteLater()

    def setup_metadata_tags_field(self):
        self.current_tags = []

        self.meta_tags_display = QWidget()
        self.meta_tags_layout = FlowLayout()
        self.meta_tags_display.setLayout(self.meta_tags_layout)

        self.meta_tag_input = QComboBox()
        self.meta_tag_input.setFixedHeight(28)
        self.meta_tag_input.setStyleSheet(get_tag_input_style(self.current_theme))

        self.meta_tag_input.setEditable(True)
        line_edit = self.meta_tag_input.lineEdit()
        line_edit.returnPressed.connect(self.add_tag_from_input)
        self.meta_tag_input.setInsertPolicy(QComboBox.NoInsert)

        completer = self.meta_tag_input.completer()
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.refresh_tag_suggestions()

        self.meta_tag_add_button = QPushButton("+")
        self.meta_tag_add_button.setFixedSize(24, 24)
        self.meta_tag_add_button.setObjectName("tagAddButton")
        self.meta_tag_add_button.clicked.connect(self.add_tag_from_input)

        input_row = QHBoxLayout()
        input_row.setContentsMargins(0, 0, 0, 0)
        input_row.setSpacing(4)
        input_row.addWidget(self.meta_tag_input)
        input_row.addWidget(self.meta_tag_add_button)
        input_row.addStretch()

        self.meta_tags_container = QWidget()
        container_layout = QVBoxLayout(self.meta_tags_container)
        container_layout.setContentsMargins(0, 0, 0, 0)
        container_layout.setSpacing(4)
        container_layout.addWidget(self.meta_tags_display)
        container_layout.addLayout(input_row)

        self.label_tags = QLabel(self.t("label_tags"))
        self.metadata_layout.addWidget(self.label_tags, 1, 0)
        self.metadata_layout.addWidget(self.meta_tags_container, 1, 1)

        # Ajout de l'icône de flèche QtAwesome
        self.meta_tag_input_arrow = QToolButton(self.meta_tag_input)
        self.meta_tag_input_arrow.setIcon(
            qta.icon("fa5s.chevron-down", color="black" if self.current_theme == "light" else "white")
        )
        self.meta_tag_input_arrow.setCursor(Qt.PointingHandCursor)
        self.meta_tag_input_arrow.setStyleSheet("border: none;")
        self.meta_tag_input_arrow.setFixedSize(18, 18)
        self.meta_tag_input_arrow.setFocusPolicy(Qt.NoFocus)
        self.meta_tag_input_arrow.clicked.connect(self.meta_tag_input.showPopup)


        # Positionner le bouton à droite dans le QComboBox
        frame_width = self.meta_tag_input.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.meta_tag_input_arrow.move(
            self.meta_tag_input.rect().right() - self.meta_tag_input_arrow.width() - frame_width,
            (self.meta_tag_input.rect().height() - self.meta_tag_input_arrow.height()) // 2
        )
        self.meta_tag_input_arrow.raise_()

        self.meta_tag_input.resizeEvent = lambda event: (self.reposition_tag_input_arrow(), QComboBox.resizeEvent(self.meta_tag_input, event))

    
    def update_tag_input_icon(self):
        color = "black" if self.current_theme == "light" else "white"
        arrow_icon = qta.icon("fa5s.chevron-down", color=color)
        self.meta_tag_input_arrow.setIcon(arrow_icon)
    
    def reposition_tag_input_arrow(self):
        if hasattr(self, "meta_tag_input_arrow"):
            frame_width = self.meta_tag_input.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
            self.meta_tag_input_arrow.move(
                self.meta_tag_input.rect().right() - self.meta_tag_input_arrow.width() - frame_width,
                (self.meta_tag_input.rect().height() - self.meta_tag_input_arrow.height()) // 2
            )


    def create_tag_widget(self, tag):
        tag_widget = QWidget()
        tag_widget.setProperty("tag_text", tag)

        color = TAG_COLORS[hash(tag) % len(TAG_COLORS)]
        tag_widget.setStyleSheet(f"""
            QWidget {{
                background-color: {color};
                border-radius: 12px;
                padding: 4px 6px;
            }}
        """)

        layout = QHBoxLayout(tag_widget)
        layout.setContentsMargins(8, 2, 6, 2)
        layout.setSpacing(4)

        label = QLabel(tag)
        label.setStyleSheet("color: white; font-size: 11px;")
        label.setToolTip(tag)
        label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

        remove_btn = QPushButton("×")
        remove_btn.setObjectName("tagRemoveButton")
        remove_btn.setCursor(Qt.PointingHandCursor)
        remove_btn.setFixedSize(16, 16)
        remove_btn.setStyleSheet("""
            QPushButton#tagRemoveButton {
                background-color: transparent;
                color: white;
                font-size: 12px;
                border: none;
            }
            QPushButton#tagRemoveButton:hover {
                color: #ff6666;
            }
        """)
        remove_btn.clicked.connect(lambda _, w=tag_widget: self.remove_tag(w))

        layout.addWidget(label)
        layout.addWidget(remove_btn)

        initial_width = 100
        expanded_width = 160

        tag_widget.setMinimumWidth(initial_width)
        tag_widget.setMaximumWidth(initial_width)
        tag_widget.setFixedHeight(tag_widget.sizeHint().height())
        tag_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        def enterEvent(event):
            tag_widget.raise_()
            container_width = tag_widget.parent().width()
            max_safe_width = container_width - tag_widget.pos().x() - 20
            target_width = min(expanded_width, max_safe_width)

            anim = QPropertyAnimation(tag_widget, b"minimumWidth")
            anim.setDuration(150)
            anim.setStartValue(tag_widget.width())
            anim.setEndValue(target_width)
            anim.setEasingCurve(QEasingCurve.OutCubic)
            anim.start()
            tag_widget.setMaximumWidth(target_width)
            tag_widget._anim = anim

        def leaveEvent(event):
            tag_widget.setMaximumWidth(initial_width)
            anim = QPropertyAnimation(tag_widget, b"minimumWidth")
            anim.setDuration(150)
            anim.setStartValue(tag_widget.width())
            anim.setEndValue(initial_width)
            anim.setEasingCurve(QEasingCurve.OutCubic)
            anim.start()
            tag_widget._anim = anim

        tag_widget.enterEvent = enterEvent
        tag_widget.leaveEvent = leaveEvent

        return tag_widget

    def load_metadata_fields(self, metadata):
        # === Étape 1 : purge propre ===
        self.clear_tags_from_layout()
        self.current_tags = []

        # === Étape 2 : chargement des champs simples ===
        self.meta_author_field.setText(metadata.get("author", ""))
        self.meta_comment_field.setPlainText(metadata.get("comment", ""))
        self.meta_version_field.setText(metadata.get("version", ""))
        self.meta_updated_field.setText(metadata.get("updated_at", ""))

        # === Étape 3 : chargement des tags (avec vérif) ===
        raw_tags = metadata.get("tags", "")
        if isinstance(raw_tags, str):
            tags = [t.strip() for t in raw_tags.split(",") if t.strip()]
        elif isinstance(raw_tags, list):
            tags = [t.strip() for t in raw_tags if t.strip()]
        else:
            tags = []

        print(f"[DEBUG] Tags à charger : {tags}")

        for tag in tags:
            tag_widget = self.create_tag_widget(tag)
            self.meta_tags_layout.addWidget(tag_widget)
            self.current_tags.append(tag)


    def refresh_tag_suggestions(self):
        tags = get_all_tags()

        self.meta_tag_input.clear()
        placeholder = self.t("tag_input_placeholder")
        self.meta_tag_input.addItem(placeholder)
        self.meta_tag_input.model().item(0).setEnabled(False)

        self.meta_tag_input.addItems(sorted(set(tags), key=str.lower))


    def load_tag_filter(self):
        # 1) Vide et ajoute un placeholder “Sélectionner un tag”
        self.tag_filter_combo.clear()
        placeholder = self.t("select_tag_filter")
        self.tag_filter_combo.addItem(placeholder)
        self.tag_filter_combo.model().item(0).setEnabled(False)

        # 2) Ajoute tous les tags existants
        tags = get_all_tags()  # renvoie la liste de tous les tags en base
        for tag in sorted(set(tags), key=str.lower):
            self.tag_filter_combo.addItem(tag)


 
    def show_tree_context_menu(self, position):
        item = self.tree.itemAt(position)
        if not item:
            return

        rel_path = item.data(0, Qt.ItemDataRole.UserRole)
        if not rel_path:
            return

        full_path = config.FILES_DIR / rel_path
        if not full_path.exists():
            return

        menu = QMenu(self)

        # 1) SYMLINK (fichier ou dossier)
        if full_path.is_symlink():
            real = full_path.resolve()

            # — Ouvrir la cible (si dossier) ou le dossier parent (si fichier)
            act_open = QAction(self.t("context_open_folder"), self)
            act_open.triggered.connect(lambda _, p=(real if real.is_dir() else real.parent):
                QDesktopServices.openUrl(QUrl.fromLocalFile(str(p))))
            menu.addAction(act_open)

            # — Ajouter des fichiers dans la cible si c'est un dossier
            if real.is_dir():
                act_add = QAction(self.t("context_add_files"), self)
                act_add.triggered.connect(lambda _, p=real: self._add_files_to_target_folder(p))
                menu.addAction(act_add)

            # — Supprimer la cible
            act_del = QAction(self.t("context_delete_target"), self)
            act_del.triggered.connect(lambda _, link=full_path: self._delete_symlink_target(link))
            menu.addAction(act_del)

        # 2) DOSSIER « NATIF »
        elif full_path.is_dir():
            act_open = QAction(self.t("context_open_folder"), self)
            act_open.triggered.connect(lambda: self.open_folder_for_item(rel_path))
            menu.addAction(act_open)

            act_add = QAction(self.t("context_add_files"), self)
            act_add.triggered.connect(lambda: self.add_files_to_folder(rel_path))
            menu.addAction(act_add)

        # 3) FICHIER « NATIF »
        elif full_path.is_file():
            act_open = QAction(self.t("context_open_folder"), self)
            act_open.triggered.connect(lambda: self.open_folder_for_item(rel_path))
            menu.addAction(act_open)

        # --- Option commune à tous (symlink ou non) : déplacer ---
        act_move = QAction(self.t("context_move_item"), self)
        act_move.triggered.connect(lambda: self.move_item_to_another_folder(rel_path))
        menu.addAction(act_move)

        menu.exec(self.tree.viewport().mapToGlobal(position))


    def open_folder_for_item(self, rel_path):
        import subprocess
        folder_path = (config.FILES_DIR / rel_path).parent  # Utiliser config.FILES_DIR ici

        try:
            if sys.platform.startswith("win"):
                os.startfile(str(folder_path))
            elif sys.platform.startswith("darwin"):
                subprocess.run(["open", str(folder_path)])
            else:  # Linux
                subprocess.run(["xdg-open", str(folder_path)])
        except Exception as e:
            QMessageBox.warning(
                self,
                self.t("error_title"),
                self.t("error_open_folder").format(error=str(e))
            )

    def add_files_to_folder(self, rel_path):

        folder_path = config.FILES_DIR / rel_path
        if not folder_path.exists() or not folder_path.is_dir():
            QMessageBox.warning(self, self.t("error_title"), self.t("folder_not_found"))
            return

        file_paths, _ = QFileDialog.getOpenFileNames(
            self,
            self.t("select_files_to_add"),
            "",
            self.t("all_files_filter")
        )
        if not file_paths:
            return

        folder_name = Path(rel_path).name
        options = {
            "copy": self.t("import_mode_copy"),    # ex: "Copier"
            "link": self.t("import_mode_link")     # ex: "Créer un lien symbolique"
        }

        reverse_options = {v: k for k, v in options.items()}

        selected_text, ok = QInputDialog.getItem(
            self,
            self.t("import_mode_title"),
            self.t("import_mode_question"),
            list(options.values()),
            editable=False
        )

        if not ok:
            return

        mode = reverse_options[selected_text]

        try:
            added_names = []
            for src in file_paths:
                src_path = Path(src)
                dest_path = folder_path / src_path.name

                if dest_path.exists():
                    reply = QMessageBox.question(
                        self,
                        self.t("file_exists_title"),
                        self.t("file_exists_message").format(name=src_path.name),
                        QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                        QMessageBox.StandardButton.No
                    )
                    if reply == QMessageBox.StandardButton.No:
                        continue

                    if mode == "copy":
                        shutil.copy2(str(src_path), str(dest_path))
                    else:
                        # Essaie toujours de créer un symlink, en précisant le type cible
                        try:
                            os.symlink(
                                str(src_path),
                                str(dest_path),
                                target_is_directory=src_path.is_dir()
                            )
                        except (OSError, NotImplementedError) as e:
                            # si Windows refuse (pas admin / pas de mode dev), on recourt à la copie
                            if src_path.is_dir():
                                shutil.copytree(str(src_path), str(dest_path))
                            else:
                                shutil.copy2(str(src_path), str(dest_path))

                added_names.append(src_path.name)

            self.reindex_files()

            if added_names:
                QMessageBox.information(
                    self,
                    self.t("import_success_title"),
                    self.t("import_success_file_to_folder").format(
                        names=", ".join(added_names),
                        folder=Path(rel_path).name
                    )
                )

        except Exception as e:
            QMessageBox.critical(
                self,
                self.t("import_error_title"),
                self.t("import_error_message").format(error=str(e))
            )

    def move_item_to_another_folder(self, rel_path):
        from PySide6.QtWidgets import QFileDialog, QMessageBox
        import shutil

        source_path = config.FILES_DIR / rel_path
        if not source_path.exists():
            QMessageBox.warning(self, self.t("error_title"), self.t("file_not_found"))
            return

        target_dir = QFileDialog.getExistingDirectory(
            self,
            self.t("select_target_folder"),
            str(config.FILES_DIR)
        )

        if not target_dir:
            return  # utilisateur a annulé

        target_dir = Path(target_dir)

        # Assure qu'on déplace bien dans le dossier `files/`
        if not str(target_dir).startswith(str(config.FILES_DIR)):
            QMessageBox.warning(self, self.t("error_title"), self.t("error_not_inside_ged"))
            return

        dest_path = target_dir / source_path.name

        if dest_path.exists():
            reply = QMessageBox.question(
                self,
                self.t("file_exists_title"),
                self.t("file_exists_message").format(name=dest_path.name),
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                QMessageBox.StandardButton.No
            )
            if reply == QMessageBox.StandardButton.No:
                return

        try:
            shutil.move(str(source_path), str(dest_path))
            self.reindex_files()
            msg = QMessageBox(self)
            msg.setWindowTitle(self.t("move_success_title"))
            msg.setText(self.t("move_success_message").format(name=source_path.name))
            msg.setIcon(QMessageBox.Information)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.open()  # ← non bloquant

        except Exception as e:
            QMessageBox.critical(
                self,
                self.t("move_error_title"),
                self.t("move_error_message").format(error=str(e))
            )

    def _delete_symlink_target(self, link_path: Path):
        """
        Supprime le fichier/dossier pointé par link_path (symlink),
        puis relance la réindexation pour nettoyer la base et l'affichage.
        """
        real = link_path.resolve()
        # confirmation
        reply = QMessageBox.question(
            self,
            self.t("delete_confirm_title"),
            self.t("delete_confirm_text").format(path=str(real)),
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply != QMessageBox.StandardButton.Yes:
            return

        try:
            # suppression du réel
            if real.is_dir():
                shutil.rmtree(real)
            else:
                real.unlink()
            # on relance la réindexation pour supprimer aussi le symlink orphelin
            self.reindex_files()
        except Exception as e:
            QMessageBox.critical(
                self,
                self.t("delete_error_title"),
                self.t("delete_error_text").format(path=str(real), error=str(e))
            )

    def _add_files_to_target_folder(self, real_folder: Path):
        """
        Ouvre un dialogue pour ajouter des fichiers DANS real_folder (hors /files),
        en créant des symlinks vers l'original ou en copiant en fallback.
        Puis réindexe pour mettre à jour l'arborescence.
        """
        from pathlib import Path
        import shutil, os
        # Sélection multi-fichiers
        file_paths, _ = QFileDialog.getOpenFileNames(
            self,
            self.t("select_files_to_add"),
            "",
            self.t("all_files_filter")
        )
        if not file_paths:
            return

        added = []
        for src in file_paths:
            src = Path(src)
            dest = real_folder / src.name
            if dest.exists():
                # on peut proposer un overwrite ou skip suivant votre besoin
                continue
            try:
                os.symlink(str(src), str(dest))
            except (OSError, NotImplementedError):
                shutil.copy2(str(src), str(dest))
            added.append(src.name)

        if added:
            QMessageBox.information(
                self,
                self.t("import_success_title"),
                self.t("import_success_file_to_folder").format(
                    names=", ".join(added),
                    folder=str(real_folder)
                )
            )
        # on réindexe toute la base, y compris ce nouveau contenu
        self.reindex_files()

    def toggle_tag_filter(self):
        if self.tag_filter_combo.isVisible():
            self.tag_filter_combo.setVisible(False)
        else:
            self.refresh_tag_filter_list()
            self.tag_filter_combo.setVisible(True)

    def refresh_tag_filter_list(self):
        from database.db import get_all_tags
        tags = get_all_tags()
        self.tag_filter_combo.blockSignals(True)
        self.tag_filter_combo.clear()
        self.tag_filter_combo.addItem(self.t("select_tag_filter"))
        self.tag_filter_combo.model().item(0).setEnabled(False)
        self.tag_filter_combo.model().item(0).setForeground(QColor("#999999"))

        for tag in sorted(tags, key=str.lower):
            self.tag_filter_combo.addItem(tag)
        self.tag_filter_combo.blockSignals(False)

    def filter_by_selected_tag(self, selected_tag):
        selected = selected_tag.strip()
        if not selected or selected == self.t("select_tag_filter"):
            return

        from database.db import fetch_all_documents, get_metadata_for_path
        self.tree.clear()
        self.search_count_label.setText(self.t("search_results_count_label").format(count=0))
        self.search_count_label.show()

        count = 0
        for doc in fetch_all_documents():
            meta = get_metadata_for_path(doc[2]) or {}
            if selected.lower() in meta.get("tags", "").lower():
                rel_path = doc[2]
                parts = rel_path.split(os.sep)
                parent = None
                for i, part in enumerate(parts):
                    path_acc = os.sep.join(parts[:i+1])
                    if i == len(parts) - 1:
                        item = QTreeWidgetItem([part])
                        item.setData(0, Qt.UserRole, rel_path)
                        if parent:
                            parent.addChild(item)
                        else:
                            self.tree.addTopLevelItem(item)
                        count += 1
                    else:
                        existing = self.find_tree_item(part, parent)
                        if existing:
                            folder_item = existing
                        else:
                            folder_item = QTreeWidgetItem([part])
                            if parent:
                                parent.addChild(folder_item)
                            else:
                                self.tree.addTopLevelItem(folder_item)
                        parent = folder_item

        self.search_count_label.setText(self.t("search_results_count_label").format(count=count))




    
    def reposition_tag_filter_arrow(self):
        if hasattr(self, "tag_filter_arrow"):
            frame_width = self.tag_filter_combo.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
            self.tag_filter_arrow.move(
                self.tag_filter_combo.rect().right() - self.tag_filter_arrow.width() - frame_width,
                (self.tag_filter_combo.rect().height() - self.tag_filter_arrow.height()) // 2
            )




from PySide6.QtCore import QPropertyAnimation
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton, QGraphicsOpacityEffect
from PySide6.QtCore import Qt
from PySide6.QtGui import QFontMetrics
from styles import get_tag_widget_style

class TagWidget(QFrame):
    def __init__(self, text, remove_callback):
        super().__init__()
        self.text = text
        self.remove_callback = remove_callback
        self.setObjectName("TagWidget")

        self.setStyleSheet(get_tag_widget_style())

        layout = QHBoxLayout(self)
        layout.setContentsMargins(4, 0, 4, 0)
        layout.setSpacing(4)

        self.label = QLabel(text)
        self.label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
        self.remove_button = QPushButton("×")
        self.remove_button.setObjectName("tagRemoveButton")
        self.remove_button.setCursor(Qt.PointingHandCursor)
        self.remove_button.setFixedSize(18, 18)
        self.remove_button.clicked.connect(self.animate_removal)

        layout.addWidget(self.label)
        layout.addWidget(self.remove_button)

        # Calcul dynamique de la largeur *après* création du label
        # font_metrics = QFontMetrics(self.label.font())
        # text_width = font_metrics.horizontalAdvance(text)
        # total_width = text_width + self.remove_button.width() + 24  # marge + padding

        # Évite les tags démesurés mais laisse respirer
        # self.setMaximumWidth(min(200, total_width))
        # self.setMinimumWidth(total_width)

    def animate_removal(self):
        self.effect = QGraphicsOpacityEffect(self)
        self.setGraphicsEffect(self.effect)

        self.animation = QPropertyAnimation(self.effect, b"opacity")
        self.animation.setDuration(500)
        self.animation.setStartValue(1.0)
        self.animation.setEndValue(0.0)
        self.animation.finished.connect(self.delete_tag)
        self.animation.start()

    def delete_tag(self):
        self.remove_callback(self)
        self.setParent(None)
        self.deleteLater()

from PySide6.QtWidgets import QStyledItemDelegate, QStyle
from PySide6.QtCore import QTimer, QPointF, QRectF, Qt, Signal
from PySide6.QtGui import QPainter, QFontMetrics, QLinearGradient, QColor, QBrush, QCursor

from PySide6.QtCore import Signal

class ScrollingItemDelegate(QStyledItemDelegate):
    def __init__(self, tree_widget):
        super().__init__(tree_widget)
        self.tree_widget = tree_widget
        self.scroll_offset = 0
        self.scroll_timer = QTimer()
        self.scroll_timer.timeout.connect(self.update_scroll)
        self.hovered_index = None
        self.scroll_speed = 2

        # Ajouter un attribut pour vérifier si le widget a été supprimé
        self.tree_widget_deleted = False

        # Connexion à l'événement de destruction de QTreeWidget
        self.tree_widget.destroyed.connect(self.on_widget_deleted)

        tree_widget.viewport().installEventFilter(self)
        tree_widget.setMouseTracking(True)

    def eventFilter(self, obj, event):
        # Si l'objet QTreeWidget a été supprimé, on ne traite plus l'événement
        if self.tree_widget_deleted:
            return super().eventFilter(obj, event)

        if obj is self.tree_widget.viewport():
            pos = self.tree_widget.viewport().mapFromGlobal(QCursor.pos())
            index = self.tree_widget.indexAt(pos)

            if index != self.hovered_index:
                self.hovered_index = index
                self.scroll_offset = 0
                if index.isValid():
                    self.scroll_timer.start(40)
                else:
                    self.scroll_timer.stop()
                self.tree_widget.viewport().update()

        return super().eventFilter(obj, event)

    def on_widget_deleted(self):
        """Cette méthode est appelée lorsque tree_widget est supprimé ou nettoyé."""
        self.tree_widget_deleted = True

    def update_scroll(self):
        if not self.hovered_index or not self.hovered_index.isValid():
            self.scroll_timer.stop()
            return

        text = self.hovered_index.data()
        if not text:
            return

        fm = QFontMetrics(self.tree_widget.font())
        text_width = fm.horizontalAdvance(text)
        rect = self.tree_widget.visualRect(self.hovered_index)
        icon_space = 24 + 8
        available_width = rect.width() - icon_space

        if text_width <= available_width:
            self.scroll_offset = 0
            return

        self.scroll_offset += self.scroll_speed
        if self.scroll_offset > text_width:
            self.scroll_offset = 0

        self.tree_widget.viewport().update(rect)

    def paint(self, painter, option, index):
        painter.save()
        text = index.data()
        if not text:
            painter.restore()
            return

        rect = option.rect
        icon = index.model().data(index, Qt.DecorationRole)
        fm = QFontMetrics(option.font)

        icon_size = 24
        icon_padding = 6
        icon_rect = QRectF(rect.left() + 4, rect.top() + (rect.height() - icon_size) / 2, icon_size, icon_size)
        text_x = icon_rect.right() + icon_padding
        text_width = fm.horizontalAdvance(text)
        available_width = rect.width() - (text_x - rect.left()) - 8

        # Sélection personnalisée (léger fond bleu clair)
        if option.state & QStyle.State_Selected:
            selection_rect = QRectF(rect)
            painter.setPen(Qt.NoPen)
            painter.drawRect(selection_rect)

        # Icône
        if isinstance(icon, QIcon):
            icon.paint(painter, int(icon_rect.left()), int(icon_rect.top()), icon_size, icon_size)

        # Texte défilant ou statique
        painter.setPen(option.palette.text().color())
        if index == self.hovered_index and text_width > available_width:
            painter.setClipRect(QRectF(text_x, rect.top(), available_width, rect.height()))
            painter.drawText(QPointF(text_x - self.scroll_offset, rect.top() + (rect.height() + fm.ascent() - fm.descent()) / 2), text)

            # Dégradé gauche
            grad_left = QLinearGradient(text_x, 0, text_x + 20, 0)
            grad_left.setColorAt(0, QColor(option.palette.window().color()))
            color = QColor(option.palette.window().color())  # Copie la couleur existante
            color.setAlpha(0)  # Rend la couleur transparente
            grad_left.setColorAt(1, color)
            painter.fillRect(QRectF(text_x, rect.top(), 20, rect.height()), QBrush(grad_left))

            # Dégradé droit
            grad_right = QLinearGradient(text_x + available_width - 20, 0, text_x + available_width, 0)
            transparent = QColor(option.palette.window().color())
            transparent.setAlpha(0)
            grad_right.setColorAt(0, transparent)
            grad_right.setColorAt(1, QColor(option.palette.window().color()))
            painter.fillRect(QRectF(text_x + available_width - 20, rect.top(), 20, rect.height()), QBrush(grad_right))
        else:
            painter.drawText(QPointF(text_x, rect.top() + (rect.height() + fm.ascent() - fm.descent()) / 2), text)

        painter.restore()

from PySide6.QtCore import QObject, Signal
import sys
from pathlib import Path

class BackupWorker(QObject):
    progress = Signal(int)
    finished = Signal(str)
    error = Signal(str)

    def __init__(self, files_dir, db_path, save_path, translations, current_language):
        super().__init__()

        self.translations = translations  # Ajout de l'attribut translations
        self.current_language = current_language  # Ajout de l'attribut current_language

        # Utiliser les chemins définis dans config.py
        self.db_path = db_path if not hasattr(sys, '_MEIPASS') else config.DB_PATH  # Utilise config.DB_PATH
        self.files_dir = files_dir if not hasattr(sys, '_MEIPASS') else config.FILES_DIR  # Utilise config.FILES_DIR
        self.save_path = save_path

    def run(self):
        import zipfile, os
        try:
            total_items = sum(len(files) for _, _, files in os.walk(self.files_dir)) + 1  # +1 pour la base
            processed = 0

            with zipfile.ZipFile(self.save_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for root, _, files in os.walk(self.files_dir):
                    for file in files:
                        full_path = os.path.join(root, file)
                        rel_path = os.path.relpath(full_path, os.path.dirname(self.files_dir))

                        if os.path.islink(full_path):
                            target = os.readlink(full_path)
                            info = zipfile.ZipInfo(rel_path)
                            info.create_system = 3  # Unix
                            info.external_attr = 0o120777 << 16  # Type fichier : symlink
                            zipf.writestr(info, target.encode('utf-8'))
                        else:
                            zipf.write(full_path, arcname=rel_path)

                        processed += 1
                        self.progress.emit(int(processed * 100 / total_items))

                # Sauvegarde de la base de données
                zipf.write(self.db_path, arcname="ged.db")
                processed += 1
                self.progress.emit(int(processed * 100 / total_items))

            msg = self.translations.get(self.current_language, {}).get(
                "backup_done_message", "Sauvegarde terminée."
            )
            self.finished.emit(msg)

        except Exception as e:
            self.error.emit(str(e))

class RestoreWorker(QObject):
    progress = Signal(int)
    finished = Signal(str)
    error = Signal(str)

    def __init__(self, zip_path, files_dir, db_path, translations, current_language):
        super().__init__()
        self.zip_path = zip_path
        self.files_dir = files_dir
        self.db_path = db_path
        self.translations = translations
        self.current_language = current_language

    def run(self):
        import zipfile, shutil, os
        from pathlib import Path
        try:
            with zipfile.ZipFile(self.zip_path, 'r') as zipf:
                namelist = zipf.namelist()
                total = len(namelist)
                count = 0

                # Purge les anciens fichiers
                if os.path.exists(self.files_dir):
                    shutil.rmtree(self.files_dir)
                os.makedirs(self.files_dir, exist_ok=True)

                for name in namelist:
                    member = zipf.getinfo(name)
                    target_path = Path(self.files_dir).parent / name

                    # Création des sous-dossiers
                    target_path.parent.mkdir(parents=True, exist_ok=True)

                    if name == "ged.db":
                        # Base SQLite → copie directe dans db_path
                        with open(self.db_path, 'wb') as out_db:
                            out_db.write(zipf.read(name))
                    elif (member.external_attr >> 16) & 0o170000 == 0o120000:
                        # C'est un lien symbolique
                        target = zipf.read(name).decode('utf-8')
                        os.symlink(target, target_path)
                    else:
                        # Fichier standard
                        with open(target_path, 'wb') as f:
                            f.write(zipf.read(name))

                    count += 1
                    self.progress.emit(int(count * 100 / total))

            msg = self.translations.get(self.current_language, {}).get(
                "restore_done_message", "Restauration terminée."
            )
            self.finished.emit(msg)

        except Exception as e:
            self.error.emit(str(e))


class SearchWorker(QObject):
    result_found = Signal(tuple)
    progress     = Signal(int, int)  # val, total
    finished     = Signal()
    error        = Signal(str)

    def __init__(self, keywords, translate):
        super().__init__()
        # Normalisation en minuscules dès le départ
        self.keywords  = [kw.lower() for kw in keywords]
        self.translate = translate
        self._is_running = True

    def stop(self):
        self._is_running = False

    def run(self):
        try:
            all_docs = fetch_all_documents()
            total    = len(all_docs)

            for i, doc in enumerate(all_docs):
                if not self._is_running:
                    break

                rel_path = doc[2]
                filename = Path(rel_path).name.lower()
                abs_path = config.FILES_DIR / rel_path

                # On teste nom et métadonnées quoi qu’il arrive
                found = True
                for kw in self.keywords:
                    in_name     = kw in filename
                    in_metadata = matches_metadata(rel_path, [kw])

                    # Recherche dans le contenu **seulement** si le fichier existe
                    in_content = False
                    if abs_path.exists():
                        in_content = matches_content(abs_path, kw, self.translate)

                    if not (in_name or in_metadata or in_content):
                        found = False
                        break

                if found:
                    self.result_found.emit(doc)

                self.progress.emit(i+1, total)

            self.finished.emit()

        except Exception as e:
            self.error.emit(str(e))
            self.finished.emit()



from PySide6.QtWidgets import QLayout, QSizePolicy, QWidgetItem
from PySide6.QtCore import QSize, Qt, QPoint, QRect

class FlowLayout(QLayout):
    def __init__(self, parent=None, margin=0, spacing=8):
        super().__init__(parent)
        self.setContentsMargins(margin, margin, margin, margin)
        self.setSpacing(spacing)
        self.item_list = []

    def addItem(self, item):
        self.item_list.append(item)

    def count(self):
        return len(self.item_list)

    def itemAt(self, index):
        if 0 <= index < len(self.item_list):
            return self.item_list[index]
        return None

    def takeAt(self, index):
        if 0 <= index < len(self.item_list):
            return self.item_list.pop(index)
        return None

    def removeWidget(self, widget):
        """Supprime un widget de manière sécurisée du layout."""
        for i, item in enumerate(self.item_list):
            if item.widget() == widget:
                self.item_list.pop(i)
                break

    def expandingDirections(self):
        return Qt.Orientations(Qt.Orientation(0))

    def hasHeightForWidth(self):
        return True

    def heightForWidth(self, width):
        return self._do_layout(QPoint(0, 0), width, test_only=True)

    def setGeometry(self, rect):
        super().setGeometry(rect)
        self._do_layout(rect.topLeft(), rect.width())

    def sizeHint(self):
        return self.minimumSize()

    def minimumSize(self):
        size = QSize()
        for item in self.item_list:
            size = size.expandedTo(item.minimumSize())
        size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
        return size

    def _do_layout(self, position, width, test_only=False):
        x, y = position.x(), position.y()
        line_height = 0
        for item in self.item_list:
            widget = item.widget()
            if widget is None or not widget.isVisible():
                continue
            space_x = self.spacing()
            next_x = x + item.sizeHint().width() + space_x
            if next_x - space_x > position.x() + width and line_height > 0:
                x = position.x()
                y += line_height + self.spacing()
                next_x = x + item.sizeHint().width() + space_x
                line_height = 0
            if not test_only:
                item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
            x = next_x
            line_height = max(line_height, item.sizeHint().height())
        return y + line_height - position.y()

