import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import sys
import json
import threading
import queue
import tempfile
from pathlib import Path

CONFIG_FILE = Path(sys.executable if getattr(sys, 'frozen', False) else __file__).parent / "config.json"

DEFAULT_CONFIG = {
    "source_path": "",
    "dest_logo_path": "",
    "dest_flatten_path": "",
    "external_logo_path": "",
    "last_action": "both",
    "window_geometry": "1000x720+100+50"
}

CHECKED = "☑"
UNCHECKED = "☐"


def load_config():
    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            cfg = json.load(f)
            for k in DEFAULT_CONFIG:
                cfg.setdefault(k, DEFAULT_CONFIG[k])
            return cfg
    except (FileNotFoundError, json.JSONDecodeError):
        return dict(DEFAULT_CONFIG)


def save_config(cfg):
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(cfg, f, indent=2, ensure_ascii=False)
    except Exception:
        pass


class ProgressCallback:
    def __init__(self, cancel_event, progress_queue, action_label=""):
        self.cancel_event = cancel_event
        self.progress_queue = progress_queue
        self.action_label = action_label

    def is_cancelled(self):
        return self.cancel_event.is_set()

    def on_progress(self, current, total):
        self.progress_queue.put(("progress", current, total))

    def on_file_done(self, rel_path, success, error_msg):
        if success:
            self.progress_queue.put(("log", f"  \u2713 {self.action_label}{rel_path}"))
        else:
            self.progress_queue.put(("log", f"  \u2717 {self.action_label}{rel_path} : {error_msg}"))


class WorkerThread(threading.Thread):
    def __init__(self, action, source, dest_logo, dest_flatten, logo_path,
                 selected_files, progress_queue, cancel_event):
        super().__init__(daemon=True)
        self.action = action
        self.source = source
        self.dest_logo = dest_logo
        self.dest_flatten = dest_flatten
        self.logo_path = logo_path
        self.selected_files = selected_files
        self.progress_queue = progress_queue
        self.cancel_event = cancel_event

    def run(self):
        try:
            if self.action == "both":
                from script_pdf import process_logo
                from Impression import process_flatten

                callback_logo = ProgressCallback(self.cancel_event, self.progress_queue, "Logo > ")

                self.progress_queue.put(("phase", "Insertion du logo..."))
                p_logo, t_logo, e_logo = process_logo(
                    self.source, self.dest_logo, self.logo_path,
                    self.selected_files, callback_logo
                )

                if self.cancel_event.is_set():
                    self.progress_queue.put(("cancelled",))
                    return

                if p_logo > 0:
                    callback_flat = ProgressCallback(self.cancel_event, self.progress_queue, "Aplat > ")

                    self.progress_queue.put(("phase", "Aplatissement pour impression..."))
                    p_flat, t_flat, e_flat = process_flatten(
                        self.dest_logo, self.dest_flatten,
                        self.selected_files, callback_flat
                    )

                    if self.cancel_event.is_set():
                        self.progress_queue.put(("cancelled",))
                        return

                    all_errors = e_logo + e_flat
                    if all_errors:
                        self.progress_queue.put(("done", f"Terminé avec {len(all_errors)} erreur(s). "
                                                         f"Logo: {p_logo}/{t_logo}, Aplat: {p_flat}/{t_flat}"))
                    else:
                        self.progress_queue.put(("done", f"Succès ! Logo: {p_logo}/{t_logo}, "
                                                         f"Aplat: {p_flat}/{t_flat}"))
                else:
                    self.progress_queue.put(("done", "Aucun fichier PDF trouvé pour le logo."))

            else:
                from Impression import process_flatten
                callback_flat = ProgressCallback(self.cancel_event, self.progress_queue, "Aplat > ")

                self.progress_queue.put(("phase", "Aplatissement pour impression..."))
                p_flat, t_flat, e_flat = process_flatten(
                    self.source, self.dest_flatten,
                    self.selected_files, callback_flat
                )

                if self.cancel_event.is_set():
                    self.progress_queue.put(("cancelled",))
                    return

                if e_flat:
                    self.progress_queue.put(("done", f"Terminé avec {len(e_flat)} erreur(s). "
                                                     f"Aplat: {p_flat}/{t_flat}"))
                    for f, m in e_flat:
                        self.progress_queue.put(("log", f"  ERR {f} : {m}"))
                else:
                    self.progress_queue.put(("done", f"Succès ! Aplat: {p_flat}/{t_flat}"))

        except Exception as e:
            self.progress_queue.put(("error", str(e)))
            import traceback
            self.progress_queue.put(("log", traceback.format_exc()))


class App:
    def __init__(self):
        self.config = load_config()
        self.root = tk.Tk()
        self.root.title("AnnexesGU - Traitement des annexes")
        self.root.geometry(self.config.get("window_geometry", "1000x720+100+50"))
        self.root.minsize(800, 600)

        self.cancel_event = threading.Event()
        self.worker = None
        self.progress_queue = queue.Queue()
        self.selected_files_set = None
        self.file_items = {}
        self.dir_items = {}
        self.all_selected = tk.BooleanVar(value=True)

        self._build_ui()
        self._apply_config()
        self.root.after(200, self._poll_queue)

    def _build_ui(self):
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        row = 0

        ttk.Label(main_frame, text="Action :", font=("Segoe UI", 10, "bold")).grid(
            row=row, column=0, sticky=tk.W, pady=(0, 2))
        row += 1

        self.action_var = tk.StringVar(value=self.config.get("last_action", "both"))
        both_rb = ttk.Radiobutton(main_frame, text="Remplacement du LOGO + Préparation pour IMPRESSION",
                                   variable=self.action_var, value="both",
                                   command=self._on_action_change)
        both_rb.grid(row=row, column=0, columnspan=2, sticky=tk.W)
        row += 1
        flatten_rb = ttk.Radiobutton(main_frame, text="Préparation pour IMPRESSION uniquement",
                                      variable=self.action_var, value="flatten",
                                      command=self._on_action_change)
        flatten_rb.grid(row=row, column=0, columnspan=2, sticky=tk.W)
        row += 1

        sep0 = ttk.Separator(main_frame, orient=tk.HORIZONTAL)
        sep0.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=5)
        row += 1

        ttk.Label(main_frame, text="Dossier source :", font=("Segoe UI", 10, "bold")).grid(
            row=row, column=0, sticky=tk.W, pady=(0, 2))
        row += 1

        src_frame = ttk.Frame(main_frame)
        src_frame.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        main_frame.columnconfigure(0, weight=1)
        self.source_var = tk.StringVar()
        self.source_entry = ttk.Entry(src_frame, textvariable=self.source_var)
        self.source_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(src_frame, text="Parcourir...", command=self._browse_source).pack(side=tk.RIGHT)
        row += 1

        ttk.Label(main_frame, text="Fichiers à traiter :", font=("Segoe UI", 10, "bold")).grid(
            row=row, column=0, sticky=tk.W, pady=(5, 2))
        row += 1

        tree_frame = ttk.Frame(main_frame)
        tree_frame.grid(row=row, column=0, columnspan=3, sticky=tk.NSEW, pady=(0, 5))
        main_frame.rowconfigure(row, weight=1)

        self.tree = ttk.Treeview(tree_frame, columns=("check",), show="tree headings", height=10)
        self.tree.heading("check", text="")
        self.tree.column("check", width=30, anchor=tk.CENTER, stretch=False)
        self.tree.heading("#0", text="Fichier")
        self.tree.column("#0", width=400)

        vsb = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)
        vsb.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.tree.bind("<ButtonRelease-1>", self._on_tree_click)
        row += 1

        sel_frame = ttk.Frame(main_frame)
        sel_frame.grid(row=row, column=0, columnspan=3, sticky=tk.W, pady=(2, 5))
        ttk.Button(sel_frame, text="Tout sélectionner", command=self._select_all).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(sel_frame, text="Tout désélectionner", command=self._deselect_all).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Label(sel_frame, text="  |  ", font=("Segoe UI", 9)).pack(side=tk.LEFT)
        ttk.Button(sel_frame, text="Développer tout", command=self._expand_all).pack(side=tk.LEFT, padx=(5, 5))
        ttk.Button(sel_frame, text="Réduire tout", command=self._collapse_all).pack(side=tk.LEFT, padx=(0, 5))
        self.selection_label = ttk.Label(sel_frame, text="", font=("Segoe UI", 9))
        self.selection_label.pack(side=tk.LEFT, padx=(15, 0))
        row += 1

        sep1 = ttk.Separator(main_frame, orient=tk.HORIZONTAL)
        sep1.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=5)
        row += 1

        logo_frame = ttk.LabelFrame(main_frame, text="Logo", padding=5)
        logo_frame.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        self.use_embedded_logo = tk.BooleanVar(value=True)
        ttk.Checkbutton(logo_frame, text="Logo intégré par défaut",
                        variable=self.use_embedded_logo,
                        command=self._on_logo_mode_change).pack(side=tk.LEFT, padx=(0, 10))
        self.logo_path_var = tk.StringVar()
        self.logo_entry = ttk.Entry(logo_frame, textvariable=self.logo_path_var, state="disabled")
        self.logo_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        self.logo_browse_btn = ttk.Button(logo_frame, text="Changer...",
                                          command=self._browse_logo, state="disabled")
        self.logo_browse_btn.pack(side=tk.RIGHT)
        row += 1

        self.dest_logo_frame = ttk.LabelFrame(main_frame, text="Destination dossier nouveau logo (ANNEXES CORRIGEES)", padding=5)
        self.dest_logo_frame.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        self.dest_logo_var = tk.StringVar()
        dlf = ttk.Frame(self.dest_logo_frame)
        dlf.pack(fill=tk.X, expand=True)
        self.dest_logo_entry = ttk.Entry(dlf, textvariable=self.dest_logo_var)
        self.dest_logo_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(dlf, text="Parcourir...", command=lambda: self._browse_dest("logo")).pack(side=tk.RIGHT)
        row += 1

        self.dest_flat_frame = ttk.LabelFrame(main_frame, text="Destination dossier pour impression (ANNEXES POUR IMPRESSION)", padding=5)
        self.dest_flat_frame.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        self.dest_flatten_var = tk.StringVar()
        dff = ttk.Frame(self.dest_flat_frame)
        dff.pack(fill=tk.X, expand=True)
        self.dest_flatten_entry = ttk.Entry(dff, textvariable=self.dest_flatten_var)
        self.dest_flatten_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(dff, text="Parcourir...", command=lambda: self._browse_dest("flatten")).pack(side=tk.RIGHT)
        row += 1

        sep2 = ttk.Separator(main_frame, orient=tk.HORIZONTAL)
        sep2.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=5)
        row += 1

        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        self.run_btn = ttk.Button(btn_frame, text="Lancer le traitement", command=self._run)
        self.run_btn.pack(side=tk.LEFT, padx=(0, 5))
        self.cancel_btn = ttk.Button(btn_frame, text="Annuler", command=self._cancel, state="disabled")
        self.cancel_btn.pack(side=tk.LEFT)
        row += 1

        self.phase_var = tk.StringVar()
        ttk.Label(main_frame, textvariable=self.phase_var).grid(
            row=row, column=0, columnspan=3, sticky=tk.W, pady=(0, 2))
        row += 1

        self.progress_bar = ttk.Progressbar(main_frame, mode="determinate")
        self.progress_bar.grid(row=row, column=0, columnspan=3, sticky=tk.EW, pady=(0, 5))
        row += 1

        log_frame = ttk.LabelFrame(main_frame, text="Journal", padding=5)
        log_frame.grid(row=row, column=0, columnspan=3, sticky=tk.NSEW)
        main_frame.rowconfigure(row, weight=1)

        self.log_text = tk.Text(log_frame, height=15, width=80, wrap=tk.WORD,
                                 font=("Consolas", 9), state=tk.DISABLED)
        log_sb = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=log_sb.set)
        log_sb.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    def _on_action_change(self):
        self.config["last_action"] = self.action_var.get()
        if self.action_var.get() == "flatten":
            self.dest_logo_frame.grid_remove()
        else:
            self.dest_logo_frame.grid()

    def _on_logo_mode_change(self):
        if self.use_embedded_logo.get():
            self.logo_entry.configure(state="disabled")
            self.logo_browse_btn.configure(state="disabled")
        else:
            self.logo_entry.configure(state="normal")
            self.logo_browse_btn.configure(state="normal")

    def _browse_source(self):
        path = filedialog.askdirectory(title="Choisir le dossier source",
                                        initialdir=self._get_initial_dir(self.source_var.get()))
        if path:
            self.source_var.set(path)
            self.config["source_path"] = path
            save_config(self.config)
            self._populate_tree(path)

    def _browse_dest(self, key):
        current = getattr(self, f"dest_{key}_var").get()
        initial = current if current and os.path.isdir(current) else None
        path = filedialog.askdirectory(title="Choisir le dossier de destination",
                                        initialdir=initial)
        if path:
            getattr(self, f"dest_{key}_var").set(path)
            self.config[f"dest_{key}_path"] = path
            save_config(self.config)

    def _browse_logo(self):
        path = filedialog.askopenfilename(
            title="Choisir le fichier logo",
            filetypes=[("Images PNG", "*.png"), ("Tous les fichiers", "*.*")],
            initialdir=self._get_initial_dir(self.logo_path_var.get()))
        if path:
            self.logo_path_var.set(path)
            self.config["external_logo_path"] = path
            save_config(self.config)

    def _get_initial_dir(self, path):
        if path and os.path.exists(path):
            return path if os.path.isdir(path) else os.path.dirname(path)
        return None

    def _populate_tree(self, source_path):
        for item in self.tree.get_children():
            self.tree.delete(item)
        self.file_items.clear()
        self.selected_files_set = set()
        self.dir_items = {}

        if not source_path or not os.path.isdir(source_path):
            self._update_selection_label()
            return

        root_node = self.tree.insert("", "end", text=source_path, open=True, tags=("directory",), values=(CHECKED,))
        self.file_items[source_path + "/"] = root_node
        self.dir_items[root_node] = []

        for root, dirs, files in os.walk(source_path):
            parent_path = os.path.relpath(root, source_path)

            if parent_path != ".":
                parts = parent_path.split(os.sep)
                for i in range(len(parts)):
                    node_path = os.path.join(source_path, *parts[:i+1])
                    node_key = node_path + "/"
                    if node_key not in self.file_items:
                        if i == 0:
                            parent = root_node
                        else:
                            parent_key = os.path.join(source_path, *parts[:i]) + "/"
                            parent = self.file_items.get(parent_key, root_node)
                        node = self.tree.insert(parent, "end", text=parts[i], open=True, tags=("directory",), values=(CHECKED,))
                        self.file_items[node_key] = node
                        self.dir_items[node] = []

            for f in sorted(files):
                if f.lower().endswith('.pdf'):
                    rel_path = os.path.relpath(os.path.join(root, f), source_path)
                    if parent_path == ".":
                        parent = root_node
                    else:
                        parent_key = os.path.join(source_path, parent_path) + "/"
                        parent = self.file_items.get(parent_key, root_node)
                    file_node = self.tree.insert(parent, "end", text=f, tags=("file",), values=(CHECKED,))
                    self.file_items[rel_path] = file_node
                    self.dir_items.setdefault(parent, []).append(file_node)
                    self.selected_files_set.add(rel_path)

        self._update_selection_label()

        for dir_item in list(self.dir_items.keys()):
            if not self._get_all_file_items(dir_item):
                self.tree.set(dir_item, "check", "")

    def _on_tree_click(self, event):
        region = self.tree.identify_region(event.x, event.y)
        if region not in ("cell", "tree"):
            return
        item = self.tree.identify_row(event.y)
        if not item:
            return
        tags = self.tree.item(item, "tags")
        if "directory" in tags:
            if self.tree.set(item, "check") == "":
                return "break"
            self._toggle_directory(item)
            return "break"
        if "file" not in tags:
            return
        rel_path = self._get_rel_path(item)
        if not rel_path:
            return

        if rel_path in self.selected_files_set:
            self.selected_files_set.discard(rel_path)
            self.tree.set(item, "check", UNCHECKED)
        else:
            self.selected_files_set.add(rel_path)
            self.tree.set(item, "check", CHECKED)
        self._update_dir_checkboxes()
        self._update_selection_label()
        return "break"

    def _toggle_directory(self, dir_item):
        children = self._get_all_file_items(dir_item)
        if not children:
            return
        checked_count = sum(1 for c in children if self.tree.set(c, "check") == CHECKED)
        select = checked_count == 0

        for child in children:
            rel = self._get_rel_path(child)
            if rel:
                if select:
                    self.selected_files_set.add(rel)
                    self.tree.set(child, "check", CHECKED)
                else:
                    self.selected_files_set.discard(rel)
                    self.tree.set(child, "check", UNCHECKED)
        self._update_dir_checkboxes()
        self._update_selection_label()

    def _update_dir_checkboxes(self):
        for dir_item in list(self.dir_items.keys()):
            child_files = self._get_all_file_items(dir_item)
            if not child_files:
                continue
            checked = sum(1 for c in child_files if self.tree.set(c, "check") == CHECKED)
            all_checked = checked == len(child_files)
            self.tree.set(dir_item, "check", CHECKED if all_checked else UNCHECKED)

    def _update_selection_label(self):
        selected = self.selected_files_set or set()
        unique_dirs = set()
        for rel in selected:
            d = os.path.dirname(rel)
            unique_dirs.add(d if d else "(racine)")
        self.selection_label.config(
            text=f"{len(unique_dirs)} dossier(s), {len(selected)} fichier(s) sélectionné(s)")

    def _get_rel_path(self, item):
        for k, v in self.file_items.items():
            if v == item and not k.endswith("/"):
                return k
        return None

    def _get_child_files(self, parent_item):
        files = []
        for child in self.tree.get_children(parent_item):
            tags = self.tree.item(child, "tags")
            if "file" in tags:
                rel = self._get_rel_path(child)
                if rel:
                    files.append(rel)
            else:
                files.extend(self._get_child_files(child))
        return files

    def _get_all_file_items(self, parent_item):
        items = []
        for child in self.tree.get_children(parent_item):
            tags = self.tree.item(child, "tags")
            if "file" in tags:
                items.append(child)
            else:
                items.extend(self._get_all_file_items(child))
        return items

    def _select_all(self):
        self.selected_files_set = set()
        for k, v in self.file_items.items():
            if not k.endswith("/"):
                self.selected_files_set.add(k)
                self.tree.set(v, "check", CHECKED)
        self._update_dir_checkboxes()
        self._update_selection_label()

    def _deselect_all(self):
        self.selected_files_set = set()
        for k, v in self.file_items.items():
            if not k.endswith("/"):
                self.tree.set(v, "check", UNCHECKED)
        self._update_dir_checkboxes()
        self._update_selection_label()

    def _expand_all(self):
        for item in self.tree.get_children():
            self._expand_recursive(item)

    def _expand_recursive(self, item):
        self.tree.item(item, open=True)
        for child in self.tree.get_children(item):
            self._expand_recursive(child)

    def _collapse_all(self):
        for item in self.tree.get_children():
            self._collapse_recursive(item)

    def _collapse_recursive(self, item):
        for child in self.tree.get_children(item):
            self._collapse_recursive(child)
        self.tree.item(item, open=False)

    def _all_selected_ui(self, selected):
        pass

    def _apply_config(self):
        self.source_var.set(self.config.get("source_path", ""))
        self.dest_logo_var.set(self.config.get("dest_logo_path", ""))
        self.dest_flatten_var.set(self.config.get("dest_flatten_path", ""))
        ext_logo = self.config.get("external_logo_path", "")
        if ext_logo:
            self.logo_path_var.set(ext_logo)
            self.use_embedded_logo.set(False)
            self.logo_entry.configure(state="normal")
            self.logo_browse_btn.configure(state="normal")
        self._on_action_change()

        src = self.config.get("source_path", "")
        if src and os.path.isdir(src):
            self._populate_tree(src)

    def _run(self):
        source = self.source_var.get().strip()
        if not source or not os.path.isdir(source):
            messagebox.showerror("Erreur", "Veuillez sélectionner un dossier source valide.")
            return

        action = self.action_var.get()

        dest_logo = None
        dest_flatten = None

        if action == "both":
            dest_logo = self.dest_logo_var.get().strip()
            if not dest_logo:
                messagebox.showerror("Erreur", "Veuillez sélectionner le dossier de destination pour le logo.")
                return
            dest_flatten = self.dest_flatten_var.get().strip()
            if not dest_flatten:
                messagebox.showerror("Erreur", "Veuillez sélectionner le dossier de destination pour l'aplatissement.")
                return
            if dest_logo == source or dest_flatten == source:
                messagebox.showerror("Erreur", "Les dossiers de destination ne peuvent pas être identiques à la source.")
                return
        else:
            dest_flatten = self.dest_flatten_var.get().strip()
            if not dest_flatten:
                messagebox.showerror("Erreur", "Veuillez sélectionner le dossier de destination pour l'aplatissement.")
                return
            if dest_flatten == source:
                messagebox.showerror("Erreur", "Le dossier de destination ne peut pas être identique à la source.")
                return

        logo_path = None
        if action == "both":
            if self.use_embedded_logo.get():
                try:
                    import logo_data
                    import base64
                    tmp_dir = tempfile.gettempdir()
                    logo_path = os.path.join(tmp_dir, "annexesgu_logo.png")
                    with open(logo_path, "wb") as f:
                        f.write(base64.b64decode(logo_data.LOGO_BASE64))
                except Exception as e:
                    messagebox.showerror("Erreur", f"Impossible d'extraire le logo intégré : {e}")
                    return
            else:
                logo_path = self.logo_path_var.get().strip()
                if not logo_path or not os.path.isfile(logo_path):
                    messagebox.showerror("Erreur", "Veuillez sélectionner un fichier logo valide.")
                    return

        if not self.selected_files_set:
            messagebox.showerror("Erreur", "Aucun fichier PDF sélectionné. Veuillez choisir au moins un fichier.")
            return

        self.cancel_event.clear()
        self._clear_log()
        self.progress_bar["value"] = 0
        self.phase_var.set("Préparation...")
        self.run_btn.configure(state="disabled")
        self.cancel_btn.configure(state="normal")

        selected = list(self.selected_files_set) if self.selected_files_set else None

        self.worker = WorkerThread(
            action, source, dest_logo, dest_flatten,
            logo_path, selected,
            self.progress_queue, self.cancel_event
        )
        self.worker.start()

    def _cancel(self):
        self.cancel_event.set()
        self._log("Annulation demandée...")

    def _poll_queue(self):
        try:
            while True:
                msg = self.progress_queue.get_nowait()
                msg_type = msg[0]

                if msg_type == "progress":
                    _, current, total = msg
                    if total > 0:
                        self.progress_bar["maximum"] = total
                        self.progress_bar["value"] = current
                elif msg_type == "log":
                    self._log(msg[1])
                elif msg_type == "phase":
                    self.phase_var.set(msg[1])
                elif msg_type == "done":
                    self.phase_var.set(msg[1])
                    self._finish()
                elif msg_type == "cancelled":
                    self.phase_var.set("Traitement annulé.")
                    self._log("Traitement annulé par l'utilisateur.")
                    self._finish()
                elif msg_type == "error":
                    self.phase_var.set("Erreur lors du traitement.")
                    self._log(f"ERREUR : {msg[1]}")
                    self._finish()
        except queue.Empty:
            pass
        self.root.after(200, self._poll_queue)

    def _finish(self):
        self.run_btn.configure(state="normal")
        self.cancel_btn.configure(state="disabled")
        self.progress_bar["value"] = 0

    def _log(self, message):
        self.log_text.configure(state=tk.NORMAL)
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.log_text.configure(state=tk.DISABLED)

    def _clear_log(self):
        self.log_text.configure(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.configure(state=tk.DISABLED)

    def run(self):
        self.root.mainloop()


if __name__ == '__main__':
    app = App()
    app.run()
