# 批量重命名 · 窗口版 · 课堂演示件
#
# 用法：先运行 第12章演示_生成测试文件夹.py，再运行本文件。
#       运行后弹出一个窗口，点按钮操作，不需要在终端里输入任何东西。
#
# 与命令行版的区别只有一处：下半段的外壳换成了窗口。
# 上半段的两个函数是从命令行版原样抄过来的，一个字都没有改。

from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox

# ==================== 逻辑区 · 与命令行版完全相同，一行未改 ====================


def preview_rename(folder, prefix):
    """预演：算出每个文件将要改成什么名字。
    返回一份清单，每一项是（原路径, 新路径）。本函数不改动任何文件。"""
    plan = []
    number = 0
    for old in sorted(p for p in Path(folder).iterdir() if p.is_file()):
        # 只处理图片，其余文件跳过
        if old.suffix.lower() not in ['.jpg', '.png']:
            continue
        number = number + 1
        new = old.with_name(prefix + '_' + str(number).zfill(3) + old.suffix.lower())
        # 目标名称已被占用时，在末尾追加 -2
        if new.exists() and new != old:
            new = old.with_name(new.stem + '-2' + new.suffix)
        plan.append((old, new))
    return plan


def do_rename(plan, on_progress=None):
    """执行：按清单真正改名，返回成功的条数。
    每改完一个就调用一次 on_progress，由外壳自己决定怎么显示进度。
    出错时不作处理，直接抛出，由外壳决定怎么告诉使用者。"""
    done = 0
    for old, new in plan:
        old.rename(new)
        done = done + 1
        if on_progress is not None:
            on_progress(done, len(plan))
    return done

# ==================== 窗口外壳 · 本文件新增的部分 ====================

# 界面要用到的两项状态，选文件夹与预演的结果都存在这里
current_folder = None
current_plan = []


def show_message(text):
    """把一行状态显示在窗口底部"""
    status_label.config(text=text)
    root.update()


def update_progress(done, total):
    """交给 do_rename 的进度显示方式：改一个更新一次状态行"""
    show_message('正在执行：已处理 ' + str(done) + ' / 共 ' + str(total))


def on_choose_folder():
    """点「选择文件夹」按钮时执行：弹出系统对话框，选完立即预演"""
    global current_folder, current_plan

    picked = filedialog.askdirectory(title='选择要处理的文件夹')
    if not picked:                      # 用户点了取消
        return
    current_folder = Path(picked)

    prefix = prefix_entry.get().strip()
    if not prefix:
        messagebox.showwarning('还差一项', '请先填写文件名前缀，再选择文件夹。')
        return

    try:
        current_plan = preview_rename(current_folder, prefix)
    except Exception as error:
        messagebox.showerror('读取失败', '打开这个文件夹时出了问题：\n\n' + str(error))
        return

    # 把预演结果显示在文本框里，与命令行版打印的内容一致
    result_box.delete('1.0', tk.END)
    for old, new in current_plan:
        result_box.insert(tk.END, old.name + '　→　' + new.name + '\n')

    folder_label.config(text='当前文件夹：' + str(current_folder))
    show_message('预演完成，共 ' + str(len(current_plan)) + ' 个文件待改名。核对无误后点「确认执行」。')


def on_run():
    """点「确认执行」按钮时执行：先二次确认，再调用逻辑区的 do_rename"""
    if not current_plan:
        messagebox.showwarning('还不能执行', '请先选择文件夹，看到预演结果之后再执行。')
        return

    agreed = messagebox.askyesno(
        '确认执行',
        '将要改动 ' + str(len(current_plan)) + ' 个文件的名称。\n\n'
        '这一步没有撤销，确定继续吗？')
    if not agreed:
        show_message('已取消，没有改动任何文件。')
        return

    try:
        done = do_rename(current_plan, update_progress)
    except Exception as error:
        messagebox.showerror(
            '执行中断',
            '出错的文件已停下，后面的文件保持原样。\n\n' + str(error))
        show_message('执行中断，请检查后重新预演。')
        return

    current_plan.clear()
    result_box.delete('1.0', tk.END)
    show_message('完成，共改名 ' + str(done) + ' 个文件。')
    messagebox.showinfo('完成', '共改名 ' + str(done) + ' 个文件。')


# ---------- 以下是窗口的搭建，程序从这里开始 ----------

root = tk.Tk()
root.title('批量重命名')
root.geometry('620x460')

top = tk.Frame(root, padx=12, pady=10)
top.pack(fill='x')

tk.Label(top, text='文件名前缀：').pack(side='left')
prefix_entry = tk.Entry(top, width=14)
prefix_entry.insert(0, '课堂照片')
prefix_entry.pack(side='left', padx=(0, 12))

tk.Button(top, text='选择文件夹', command=on_choose_folder).pack(side='left')
tk.Button(top, text='确认执行', command=on_run).pack(side='left', padx=8)

folder_label = tk.Label(root, text='当前文件夹：尚未选择', anchor='w', padx=12)
folder_label.pack(fill='x')

result_box = tk.Text(root, height=16)
result_box.pack(fill='both', expand=True, padx=12, pady=8)

status_label = tk.Label(root, text='请先选择一个文件夹', anchor='w',
                        padx=12, pady=6, relief='sunken')
status_label.pack(fill='x', side='bottom')

# 交给窗口自己去等用户操作，下面不会再有代码被执行
root.mainloop()
