import sys
import threading
import traceback
from pathlib import Path
from typing import List

import tkinter as tk
from tkinter import ttk, filedialog, messagebox

try:
    from tkinterdnd2 import DND_FILES, TkinterDnD
except Exception:
    TkinterDnD = None
    DND_FILES = None

from pdf2docx import Converter  # pdf2docx


SUPPORTED_EXTS = {".pdf"}


def unique_path(path: Path) -> Path:
    if not path.exists():
        return path
    stem, suffix, parent = path.stem, path.suffix, path.parent
    i = 1
    while True:
        c = parent / f"{stem}_{i}{suffix}"
        if not c.exists():
            return c
        i += 1


def parse_drop_files(data: str) -> List[Path]:
    """
    tkinterdnd2 returns a string that may contain { } around paths with spaces.
    """
    items, buf, in_brace = [], "", False
    for ch in data:
        if ch == "{":
            in_brace = True
            buf = ""
        elif ch == "}":
            in_brace = False
            if buf.strip():
                items.append(buf.strip())
            buf = ""
        elif ch == " " and not in_brace:
            if buf.strip():
                items.append(buf.strip())
            buf = ""
        else:
            buf += ch
    if buf.strip():
        items.append(buf.strip())
    return [Path(x) for x in items]


class App:
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("PDF → Word (DOCX) — pdf2docx")

        self.files: List[Path] = []
        self.is_converting = False

        self.output_mode = tk.StringVar(value="per_file_convert")  # per_file_convert / single_folder
        self.single_dir = tk.StringVar(value="")

        # Options pdf2docx
        self.page_range_mode = tk.StringVar(value="all")  # all / range
        self.page_start = tk.StringVar(value="0")
        self.page_end = tk.StringVar(value="")  # vide = jusqu'à la fin

        self._build_ui()
        self._setup_dnd()

        self._log("Prêt. Déposez des PDF, ou utilisez 'Ajouter…'.")
        self._log(f"Python : {sys.executable}")

    def _build_ui(self):
        frm = ttk.Frame(self.root, padding=12)
        frm.pack(fill="both", expand=True)

        drop = ttk.LabelFrame(frm, text="Zone de dépôt", padding=10)
        drop.pack(fill="x")

        self.drop_label = ttk.Label(drop, text="Glissez-déposez ici vos PDF", anchor="center")
        self.drop_label.pack(fill="x")

        mid = ttk.Frame(frm)
        mid.pack(fill="both", expand=True, pady=(10, 10))

        left = ttk.Frame(mid)
        left.pack(side="left", fill="both", expand=True)

        ttk.Label(left, text="PDF à convertir :").pack(anchor="w")
        self.listbox = tk.Listbox(left, height=10)
        self.listbox.pack(fill="both", expand=True, pady=(4, 0))

        btns = ttk.Frame(mid)
        btns.pack(side="right", fill="y", padx=(10, 0))
        ttk.Button(btns, text="Ajouter…", command=self.add_files).pack(fill="x")
        ttk.Button(btns, text="Retirer sélection", command=self.remove_selected).pack(fill="x", pady=6)
        ttk.Button(btns, text="Vider la liste", command=self.clear_list).pack(fill="x")

        out = ttk.LabelFrame(frm, text="Sortie DOCX", padding=10)
        out.pack(fill="x")

        ttk.Radiobutton(
            out,
            text="Créer un sous-dossier /convert à côté de chaque PDF",
            variable=self.output_mode, value="per_file_convert",
            command=self._refresh_output_state
        ).pack(anchor="w")

        row2 = ttk.Frame(out)
        row2.pack(fill="x", pady=(6, 0))
        ttk.Radiobutton(
            row2,
            text="Créer tous les DOCX dans un dossier unique :",
            variable=self.output_mode, value="single_folder",
            command=self._refresh_output_state
        ).pack(side="left")

        self.dir_entry = ttk.Entry(row2, textvariable=self.single_dir)
        self.dir_entry.pack(side="left", fill="x", expand=True, padx=6)
        ttk.Button(row2, text="Choisir…", command=self.choose_output_dir).pack(side="left")

        opt = ttk.LabelFrame(frm, text="Options pdf2docx (facultatif)", padding=10)
        opt.pack(fill="x", pady=(10, 0))

        ttk.Radiobutton(opt, text="Convertir toutes les pages", variable=self.page_range_mode, value="all").pack(anchor="w")
        rowr = ttk.Frame(opt)
        rowr.pack(fill="x", pady=(6, 0))
        ttk.Radiobutton(rowr, text="Convertir une plage : start =", variable=self.page_range_mode, value="range").pack(side="left")
        ttk.Entry(rowr, width=6, textvariable=self.page_start).pack(side="left", padx=(6, 6))
        ttk.Label(rowr, text="end =").pack(side="left")
        ttk.Entry(rowr, width=6, textvariable=self.page_end).pack(side="left", padx=(6, 0))
        ttk.Label(opt, text="(start/end en index de pages : 0 = première page ; end vide = jusqu'à la fin)").pack(anchor="w", pady=(6, 0))

        actions = ttk.Frame(frm)
        actions.pack(fill="x", pady=(10, 0))

        self.btn_convert = ttk.Button(actions, text="Convertir", command=self.start_convert)
        self.btn_convert.pack(side="left")

        self.progress = ttk.Label(actions, text="")
        self.progress.pack(side="left", padx=10)

        logf = ttk.LabelFrame(frm, text="Journal", padding=10)
        logf.pack(fill="both", expand=True, pady=(10, 0))

        self.log = tk.Text(logf, height=12, wrap="word")
        self.log.pack(fill="both", expand=True)

        self._refresh_output_state()

    def _setup_dnd(self):
        if TkinterDnD is None:
            self._log("Drag & Drop indisponible : installez tkinterdnd2 (pip install tkinterdnd2).")
            return
        self.drop_label.drop_target_register(DND_FILES)
        self.drop_label.dnd_bind("<<Drop>>", self._on_drop)

    def _on_drop(self, event):
        self._add_paths(parse_drop_files(event.data))

    def _add_paths(self, paths: List[Path]):
        added = 0
        for p in paths:
            if p.is_dir():
                for f in p.rglob("*.pdf"):
                    if f.is_file():
                        added += self._add_one(f)
            else:
                added += self._add_one(p)
        self._log(f"{added} fichier(s) ajouté(s)." if added else "Aucun PDF valide ajouté.")

    def _add_one(self, p: Path) -> int:
        if not p.exists() or not p.is_file():
            return 0
        if p.suffix.lower() not in SUPPORTED_EXTS:
            return 0
        p = p.resolve()
        if p in self.files:
            return 0
        self.files.append(p)
        self.listbox.insert(tk.END, str(p))
        return 1

    def add_files(self):
        fpaths = filedialog.askopenfilenames(title="Choisir des PDF", filetypes=[("PDF", "*.pdf")])
        self._add_paths([Path(x) for x in fpaths])

    def remove_selected(self):
        sel = list(self.listbox.curselection())
        for idx in reversed(sel):
            self.listbox.delete(idx)
            del self.files[idx]
        if sel:
            self._log("Sélection retirée.")

    def clear_list(self):
        self.listbox.delete(0, tk.END)
        self.files.clear()
        self._log("Liste vidée.")

    def choose_output_dir(self):
        d = filedialog.askdirectory(title="Choisir le dossier de sortie")
        if d:
            self.single_dir.set(d)
            self.output_mode.set("single_folder")
            self._refresh_output_state()

    def _refresh_output_state(self):
        self.dir_entry.configure(state="normal" if self.output_mode.get() == "single_folder" else "disabled")

    def _get_out_dir_for_file(self, src: Path) -> Path:
        if self.output_mode.get() == "per_file_convert":
            return src.parent / "convert"
        d = self.single_dir.get().strip()
        if not d:
            raise RuntimeError("Veuillez choisir un dossier de sortie (mode dossier unique).")
        return Path(d)

    def _get_page_range(self):
        if self.page_range_mode.get() != "range":
            return 0, None
        try:
            start = int(self.page_start.get().strip() or "0")
        except ValueError:
            start = 0
        end_raw = self.page_end.get().strip()
        if end_raw == "":
            end = None
        else:
            try:
                end = int(end_raw)
            except ValueError:
                end = None
        return start, end

    def start_convert(self):
        if self.is_converting:
            return
        if not self.files:
            messagebox.showwarning("Aucun fichier", "Veuillez ajouter au moins un PDF.")
            return

        self.is_converting = True
        self.btn_convert.configure(state="disabled")
        self.progress.configure(text="Conversion en cours…")
        self._log("Démarrage de la conversion…")

        threading.Thread(target=self._convert_thread, daemon=True).start()

    def _convert_thread(self):
        ok = 0
        fail = 0
        start, end = self._get_page_range()

        try:
            total = len(self.files)
            for i, src in enumerate(self.files, start=1):
                try:
                    out_dir = self._get_out_dir_for_file(src)
                    out_dir.mkdir(parents=True, exist_ok=True)

                    target = unique_path(out_dir / (src.stem + ".docx"))
                    self._ui_progress(f"{i}/{total} : {src.name}")

                    self._log(f"---\nSource: {src}\nSortie: {target}\nPages: start={start}, end={end}")

                    cv = Converter(str(src))
                    try:
                        cv.convert(str(target), start=start, end=end)
                    finally:
                        cv.close()

                    if not target.exists():
                        raise RuntimeError("pdf2docx a terminé mais le DOCX n'a pas été créé.")
                    self._log(f"OK   : {src.name} → {target}")
                    ok += 1

                except Exception as e:
                    fail += 1
                    self._log(f"ECHEC: {src} ({type(e).__name__}: {e})")
                    self._log(traceback.format_exc())

        except Exception as e:
            self._log(f"ERREUR FATALE: {e}")
            self._log(traceback.format_exc())
        finally:
            self._ui_done(ok, fail)

    def _ui_progress(self, text: str):
        self.root.after(0, lambda: self.progress.configure(text=text))

    def _ui_done(self, ok: int, fail: int):
        def done():
            self.is_converting = False
            self.btn_convert.configure(state="normal")
            self.progress.configure(text=f"Terminé — OK: {ok}, Échecs: {fail}")
            if fail == 0:
                messagebox.showinfo("Conversion terminée", f"Conversion terminée.\nOK: {ok}")
            else:
                messagebox.showwarning("Conversion terminée", f"Conversion terminée.\nOK: {ok}\nÉchecs: {fail}\nVoir journal.")
        self.root.after(0, done)

    def _log(self, msg: str):
        self.root.after(0, lambda: self._append_log(msg))

    def _append_log(self, msg: str):
        self.log.insert(tk.END, msg + "\n")
        self.log.see(tk.END)


def main():
    root = TkinterDnD.Tk() if TkinterDnD is not None else tk.Tk()
    try:
        ttk.Style().theme_use("clam")
    except Exception:
        pass
    App(root)
    root.geometry("980x720")
    root.mainloop()


if __name__ == "__main__":
    main()
