import os
import re
import tkinter as tk
from tkinter import filedialog, messagebox
from pypdf import PdfReader, PdfWriter
from PIL import Image

# ドラッグ＆ドロップ用のライブラリ
from tkinterdnd2 import DND_FILES, TkinterDnD


class PdfToolApp:

    def __init__(self, root):
        self.root = root
        self.root.title("PDF・画像 結合分割ツール")
        self.root.geometry("650x555")

        # --- カラーテーマの設定 (モダン・スマート・ライト) ---
        self.COLOR_BG = "#F8F9FA"  # 全体の背景（少し温かみのある白）
        self.COLOR_PANEL = "#FFFFFF"  # パネル内の背景
        self.COLOR_PRIMARY = "#1E3A8A"  # メインの濃い青（文字や枠線）
        self.COLOR_ACCENT = "#3B82F6"  # アクセントの爽やかな青
        self.COLOR_SUCCESS = "#10B981"  # 成功・結合のグリーン
        self.COLOR_TEXT = "#1F2937"  # メインの文字色（濃いグレー）

        self.root.configure(bg=self.COLOR_BG)
        self.input_files = []

        # --- 全体のフォント設定 ---
        self.font_title = ("Segoe UI", 10, "bold")
        self.font_btn = ("Segoe UI", 9, "bold")

        # --- 1. 結合用UIエリア ---
        label_frame = tk.LabelFrame(
            root,
            text=" 1. PDF・画像の並べ替えと結合 (ドラッグ＆ドロップ対応) ",
            bg=self.COLOR_BG,
            fg=self.COLOR_PRIMARY,
            font=self.font_title,
            bd=2,
            relief="groove",
        )
        label_frame.pack(fill="both", expand=True, padx=15, pady=10)

        # ファイル一覧を表示するリストボックス
        self.listbox = tk.Listbox(
            label_frame,
            selectmode=tk.SINGLE,
            bg=self.COLOR_PANEL,
            fg=self.COLOR_TEXT,
            selectbackground=self.COLOR_ACCENT,
            selectforeground="#FFFFFF",
            font=("Segoe UI", 10),
            bd=1,
            relief="solid",
            highlightthickness=0,
        )
        self.listbox.pack(
            side="left", fill="both", expand=True, padx=10, pady=10
        )

        # --- ドラッグ＆ドロップの設定 ---
        self.listbox.drop_target_register(DND_FILES)
        self.listbox.dnd_bind("<<Drop>>", self.handle_drop)

        # 操作ボタンの右側配置
        btn_frame = tk.Frame(label_frame, bg=self.COLOR_BG)
        btn_frame.pack(side="right", fill="y", padx=10, pady=10)

        # スタイリッシュな共通ボタン作成関数
        def create_btn(parent, text, command, bg, fg="#FFFFFF"):
            return tk.Button(
                parent,
                text=text,
                command=command,
                bg=bg,
                fg=fg,
                font=self.font_btn,
                relief="flat",
                overrelief="raised",
                activebackground=bg,
                cursor="hand2",
                width=13,
                bd=0,
                pady=4,
            )

        create_btn(
            btn_frame, "ファイル追加", self.add_files, self.COLOR_ACCENT
        ).pack(pady=3)
        create_btn(
            btn_frame, "フォルダ追加", self.add_folder, self.COLOR_ACCENT
        ).pack(pady=3)

        # --- 【追加】ソートボタンの配置 ---
        create_btn(
            btn_frame, "昇順ソート 順▼", lambda: self.sort_files(reverse=False), "#4B5563"
        ).pack(pady=3)
        create_btn(
            btn_frame, "逆順ソート 逆▲", lambda: self.sort_files(reverse=True), "#4B5563"
        ).pack(pady=3)

        # 区切りスペース
        tk.Frame(btn_frame, height=15, bg=self.COLOR_BG).pack()

        create_btn(
            btn_frame, "上へ移動 ▲", self.move_up, self.COLOR_PRIMARY
        ).pack(pady=3)
        create_btn(
            btn_frame, "下へ移動 ▼", self.move_down, self.COLOR_PRIMARY
        ).pack(pady=3)

        # 区切りスペース
        tk.Frame(btn_frame, height=15, bg=self.COLOR_BG).pack()

        create_btn(btn_frame, "削除", self.delete_item, "#EF4444").pack(
            pady=3
        )
        create_btn(btn_frame, "リストクリア", self.clear_list, "#6B7280").pack(
            pady=3
        )

        # 結合実行ボタン
        self.merge_btn = tk.Button(
            root,
            text="★ 選択した順でPDF・画像を結合する",
            bg=self.COLOR_SUCCESS,
            fg="white",
            font=("Segoe UI", 12, "bold"),
            relief="flat",
            cursor="hand2",
            command=self.merge_files,
            pady=8,
        )
        self.merge_btn.pack(fill="x", padx=15, pady=5)

        # --- 2. 分割用UIエリア ---
        split_frame = tk.LabelFrame(
            root,
            text=" 2. フォルダを指定して全ページ一括分割 ",
            bg=self.COLOR_BG,
            fg=self.COLOR_PRIMARY,
            font=self.font_title,
            bd=2,
            relief="groove",
        )
        split_frame.pack(fill="x", padx=15, pady=15)

        self.split_btn = tk.Button(
            split_frame,
            text="PDFを選んで1ページずつバラバラに分割出力",
            bg=self.COLOR_ACCENT,
            fg="white",
            font=("Segoe UI", 10, "bold"),
            relief="flat",
            cursor="hand2",
            command=self.split_pdf_to_folder,
            pady=6,
        )
        self.split_btn.pack(fill="x", padx=10, pady=10)

    # --- ドラッグ＆ドロップ処理 ---
    def handle_drop(self, event):
        data = event.data
        files = []

        # Windows特有の中括弧ペア形式を解析
        if "{" in data:
            files = re.findall(r"\{(.*?)\}", data)
            for clean_f in files:
                data = data.replace(f"{{{clean_f}}}", "")
        if data.strip():
            files.extend(data.strip().split())

        valid_extensions = (".pdf", ".jpg", ".jpeg", ".png")
        for path in files:
            path = path.strip('"')  # 余計なクォーテーションを除去
            if os.path.isdir(path):
                # フォルダがドロップされた場合
                sub_files = [
                    os.path.join(path, f)
                    for f in os.listdir(path)
                    if f.lower().endswith(valid_extensions)
                ]
                sub_files.sort()
                for sf in sub_files:
                    if sf not in self.input_files:
                        self.input_files.append(sf)
            elif os.path.isfile(path) and path.lower().endswith(
                valid_extensions
            ):
                # ファイルがドロップされた場合
                if path not in self.input_files:
                    self.input_files.append(path)

        self.update_listbox()

    def add_files(self):
        files = filedialog.askopenfilenames(
            filetypes=[
                ("PDF/Images", "*.pdf *.jpg *.jpeg *.png"),
                ("PDF Files", "*.pdf"),
                ("Image Files", "*.jpg *.jpeg *.png"),
            ]
        )
        # ダイアログでキャンセルされた場合は処理しない
        if not files:
            return

        for f in files:
            if f not in self.input_files:
                self.input_files.append(f)
        self.update_listbox()

    def add_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            valid_extensions = (".pdf", ".jpg", ".jpeg", ".png")
            files = [
                os.path.join(folder, f)
                for f in os.listdir(folder)
                if f.lower().endswith(valid_extensions)
            ]
            files.sort()
            for f in files:
                if f not in self.input_files:
                    self.input_files.append(f)
            self.update_listbox()

    def update_listbox(self):
        self.listbox.delete(0, tk.END)
        for f in self.input_files:
            # もし「f」が間違ってタプル型になっていた場合、文字列に変換して保護する処理を追加
            if isinstance(f, tuple):
                f = "".join(f)

            ext = os.path.splitext(f)[1].lower()
            tag = "[画像]" if ext in [".jpg", ".jpeg", ".png"] else "[PDF]"
            self.listbox.insert(tk.END, f"{tag} {os.path.basename(f)}")

    def move_up(self):
        selected = self.listbox.curselection()
        if not selected:
            return
        idx = selected[0]
        if idx == 0:
            return
        self.input_files[idx], self.input_files[idx - 1] = (
            self.input_files[idx - 1],
            self.input_files[idx],
        )
        self.update_listbox()
        self.listbox.select_set(idx - 1)

    def move_down(self):
        selected = self.listbox.curselection()
        if not selected:
            return
        idx = selected[0]
        if idx == len(self.input_files) - 1:
            return
        self.input_files[idx], self.input_files[idx + 1] = (
            self.input_files[idx + 1],
            self.input_files[idx],
        )
        self.update_listbox()
        self.listbox.select_set(idx + 1)

    # --- 【追加】ソート機能のロジックメソッド ---
    def sort_files(self, reverse=False):
        """ファイル名（自然順/数値考慮）でリストをソートする"""
        if not self.input_files:
            return

        def natural_sort_key(s):
            """文字列の中の数値を正しく並べ替えるためのキー（例: img10 を img2 より後ろにする）"""
            if isinstance(s, tuple):
                s = "".join(s)
            filename = os.path.basename(s).lower()
            # 正規表現で数字と文字列を分割し、数字部分は数値型(int)に変換して返す
            return [int(text) if text.isdigit() else text for text in re.split(r'(\d+)', filename)]

        # 内部リストをソート
        self.input_files.sort(key=natural_sort_key, reverse=reverse)
        # 画面のListboxを再描画
        self.update_listbox()

    def delete_item(self):
        selected = self.listbox.curselection()
        if not selected:
            return
        idx = selected[0]
        del self.input_files[idx]
        self.update_listbox()

    def clear_list(self):
        self.input_files.clear()
        self.update_listbox()

    def merge_files(self):
        if not self.input_files:
            messagebox.showwarning(
                "警告", "結合するファイルがリストにありません。"
            )
            return

        save_path = filedialog.asksaveasfilename(
            defaultextension=".pdf", filetypes=[("PDF Files", "*.pdf")]
        )
        if not save_path:
            return

        temp_pdf_files = []

        try:
            writer = PdfWriter()

            for f in self.input_files:
                if isinstance(f, tuple):
                    f = "".join(f)

                ext = os.path.splitext(f)[1].lower()

                if ext in [".jpg", ".jpeg", ".png"]:
                    img = Image.open(f)
                    if img.mode != "RGB":
                        img = img.convert("RGB")

                    temp_pdf_path = f + "_temp_converted.pdf"
                    img.save(temp_pdf_path, "PDF")
                    temp_pdf_files.append(temp_pdf_path)
                    writer.append(temp_pdf_path)
                else:
                    writer.append(f)

            with open(save_path, "wb") as output:
                writer.write(output)
            writer.close()

            messagebox.showinfo("成功", "PDF・画像の結合が完了しました！")

        except Exception as e:
            messagebox.showerror(
                "エラー", f"結合中にエラーが発生しました:\n{e}"
            )

        finally:
            for temp_f in temp_pdf_files:
                if os.path.exists(temp_f):
                    try:
                        os.remove(temp_f)
                    except:
                        pass

    # --- 2. 分割関連 of 処理 ---
    def split_pdf_to_folder(self):
        source_pdf = filedialog.askopenfilename(
            filetypes=[("PDF Files", "*.pdf")]
        )
        if not source_pdf:
            return

        output_folder = filedialog.askdirectory(
            title="分割したPDFの保存先フォルダを選択"
        )
        if not output_folder:
            return

        try:
            reader = PdfReader(source_pdf)
            base_name = os.path.splitext(os.path.basename(source_pdf))[0]

            for page_num in range(len(reader.pages)):
                writer = PdfWriter()
                writer.add_page(reader.pages[page_num])

                out_file_name = f"{base_name}_page_{page_num + 1:03d}.pdf"
                out_path = os.path.join(output_folder, out_file_name)

                with open(out_path, "wb") as f:
                    writer.write(f)
                writer.close()

            messagebox.showinfo(
                "成功", f"全 {len(reader.pages)} ページを分割保存しました！"
            )
        except Exception as e:
            messagebox.showerror(
                "エラー", f"分割中にエラーが発生しました:\n{e}"
            )


if __name__ == "__main__":
    root = TkinterDnD.Tk()
    app = PdfToolApp(root)
    root.mainloop()
