#!/usr/bin/env python3
"""
考試題目解析工具 v2.3
自動操作 Gemini 網頁版 | 使用已登入的 Chrome | 自動啟動/連接 | 現代 UI
"""

import threading, json, os, sys, time, datetime, csv, subprocess  # noqa

# ─── 自動安裝 UI 套件 ───
try:
    import customtkinter as ctk
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "customtkinter", "--quiet"])
    import customtkinter as ctk

import tkinter as tk
from tkinter import filedialog, messagebox

HAS_SELENIUM = False
try:
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    HAS_SELENIUM = True
except ImportError:
    pass

HAS_REQUESTS = False
try:
    import requests as req_lib
    HAS_REQUESTS = True
except ImportError:
    pass

# ─── 鍵盤/滑鼠自動化 ───
HAS_PYAUTOGUI = False
try:
    import pyautogui
    import pyperclip
    pyautogui.FAILSAFE = True
    pyautogui.PAUSE = 0.3
    HAS_PYAUTOGUI = True
except ImportError:
    try:
        subprocess.check_call([sys.executable, "-m", "pip", "install", "pyautogui", "pyperclip", "--quiet"])
        import pyautogui
        import pyperclip
        pyautogui.FAILSAFE = True
        pyautogui.PAUSE = 0.3
        HAS_PYAUTOGUI = True
    except:
        pass

# ─── 常數 ───
VER = "2.3"
CONFIG_FILE = "config.json"
GEMINI_URL = "https://gemini.google.com/app"
AISTUDIO_URL = "https://aistudio.google.com/prompts/new_chat"

DEFAULT_PROMPT = """請解析以下考試檔案中的所有題目，並以 JSON 格式回傳。

每一題請包含以下欄位：
- question_text: 完整題目文字
- question_type: 題型（multiple_choice / true_false / fill_blank / short_answer / essay）
- options: 選擇題提供 {"A":"...","B":"...","C":"...","D":"..."}，其他填 null
- correct_answer: 正確答案，無法判斷填 null
- difficulty: 難度 1~3
- explanation: 詳細解析說明

只回傳純 JSON 陣列，不要 markdown 標記。"""

def base_dir():
    return os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))


# ═══════════════════════════════════════
# 設定
# ═══════════════════════════════════════
class Config:
    KEYS = ['prompt', 'site', 'wait_timeout', 'theme',
            'chrome_profile', 'chrome_user_data']

    def __init__(self):
        self.prompt = DEFAULT_PROMPT
        self.site = "gemini"
        self.wait_timeout = 180
        self.theme = "dark"
        self.chrome_profile = ""      # 空白 = 自動偵測
        self.chrome_user_data = ""    # 空白 = 系統預設路徑
        try:
            with open(os.path.join(base_dir(), CONFIG_FILE), 'r', encoding='utf-8') as f:
                d = json.load(f)
                for k in self.KEYS:
                    if k in d: setattr(self, k, d[k])
        except: pass

    def save(self):
        with open(os.path.join(base_dir(), CONFIG_FILE), 'w', encoding='utf-8') as f:
            json.dump({k: getattr(self, k) for k in self.KEYS}, f, ensure_ascii=False, indent=2)


# ═══════════════════════════════════════
# Chrome 工具
# ═══════════════════════════════════════
DEBUG_PORT = 9222

def is_chrome_running():
    """檢查 Chrome 是否正在執行"""
    try:
        if sys.platform == 'win32':
            out = subprocess.check_output(['tasklist', '/FI', 'IMAGENAME eq chrome.exe'], stderr=subprocess.DEVNULL, text=True)
            return 'chrome.exe' in out.lower()
        else:
            out = subprocess.check_output(['pgrep', '-f', 'chrome'], stderr=subprocess.DEVNULL, text=True)
            return bool(out.strip())
    except:
        return False


def is_debug_port_active(port=DEBUG_PORT):
    """檢查 Chrome debug port 是否有回應（用 socket 更可靠）"""
    import socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(2)
        s.connect(('127.0.0.1', port))
        s.close()
        return True
    except:
        return False


def get_chrome_path():
    """取得 Chrome 執行檔路徑"""
    if sys.platform == 'win32':
        candidates = [
            os.path.expandvars(r"%ProgramFiles%\Google\Chrome\Application\chrome.exe"),
            os.path.expandvars(r"%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe"),
            os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"),
        ]
        for p in candidates:
            if os.path.exists(p):
                return p
    elif sys.platform == 'darwin':
        p = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
        if os.path.exists(p):
            return p
    else:
        import shutil
        for name in ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium']:
            found = shutil.which(name)
            if found:
                return found
    return None


def get_chrome_user_data():
    """取得 Chrome 預設 User Data 路徑"""
    if sys.platform == 'win32':
        return os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data")
    elif sys.platform == 'darwin':
        return os.path.expanduser("~/Library/Application Support/Google/Chrome")
    else:
        return os.path.expanduser("~/.config/google-chrome")


def list_chrome_profiles(user_data_dir=None):
    """列出所有 Chrome Profile 及其名稱"""
    ud = user_data_dir or get_chrome_user_data()
    profiles = []
    if not os.path.exists(ud):
        return profiles
    for name in os.listdir(ud):
        pref_file = os.path.join(ud, name, "Preferences")
        if not os.path.isfile(pref_file):
            continue
        display_name = name
        email = ""
        try:
            with open(pref_file, 'r', encoding='utf-8') as f:
                prefs = json.load(f)
            # 取得 profile 顯示名稱
            pinfo = prefs.get("profile", {})
            display_name = pinfo.get("name", name)
            # 取得登入的 Google 帳號 email
            gaia = prefs.get("account_info", [])
            if gaia:
                email = gaia[0].get("email", "")
            if not email:
                # 另一種位置
                signin = prefs.get("signin", {})
                email = signin.get("email", "")
        except:
            pass
        profiles.append({
            "dir": name,           # e.g. "Default", "Profile 1"
            "name": display_name,  # e.g. "個人", "工作"
            "email": email,        # e.g. "user@gmail.com"
        })
    return profiles


def auto_detect_profile(user_data_dir=None):
    """自動找到有登入 Google 帳號的 Profile"""
    profiles = list_chrome_profiles(user_data_dir)
    # 優先找有 email 的
    for p in profiles:
        if p["email"]:
            return p["dir"]
    # 沒有就用 Default
    for p in profiles:
        if p["dir"] == "Default":
            return "Default"
    # 都沒有就用第一個
    if profiles:
        return profiles[0]["dir"]
    return "Default"


def kill_chrome(log_cb=None):
    """徹底關閉所有 Chrome 程序，等待 profile lock 釋放"""
    log = log_cb or (lambda m: None)
    try:
        if sys.platform == 'win32':
            # 先嘗試正常關閉
            subprocess.call(['taskkill', '/IM', 'chrome.exe'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
            time.sleep(1)
            # 強制關閉所有殘留（包含背景）
            subprocess.call(['taskkill', '/F', '/IM', 'chrome.exe'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
            subprocess.call(['taskkill', '/F', '/IM', 'chromedriver.exe'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
        else:
            subprocess.call(['pkill', '-f', 'chrome'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
            time.sleep(1)
            subprocess.call(['pkill', '-9', '-f', 'chrome'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
    except:
        pass

    # 等待 profile lock 檔案釋放
    user_data = get_chrome_user_data()
    lock_file = os.path.join(user_data, "SingletonLock")
    lock_file2 = os.path.join(user_data, "lockfile")
    for i in range(10):
        if not os.path.exists(lock_file) and not os.path.exists(lock_file2):
            log(f"✅ Chrome 已完全關閉（等待 {i+1} 秒）")
            return True
        # Windows 上嘗試刪除 lock 檔
        for lf in [lock_file, lock_file2]:
            try:
                if os.path.exists(lf):
                    os.remove(lf)
            except:
                pass
        time.sleep(1)
    log("⚠️ Chrome lock 檔案可能未釋放，繼續嘗試...")
    return False


def launch_chrome_with_debug(port=DEBUG_PORT, profile_dir="", user_data_path="", log_cb=None, open_url=""):
    """用 remote-debugging-port 啟動 Chrome（保留登入狀態），可直接開啟指定網址"""
    log = log_cb or (lambda m: None)
    chrome_path = get_chrome_path()
    if not chrome_path:
        log("❌ 找不到 Chrome 執行檔")
        return False

    user_data = user_data_path or get_chrome_user_data()
    # 決定使用哪個 profile
    if not profile_dir:
        profile_dir = auto_detect_profile(user_data)
    log(f"📂 使用 Chrome Profile：{profile_dir}")
    log(f"📂 User Data 路徑：{user_data}")

    cmd = [
        chrome_path,
        f"--remote-debugging-port={port}",
        "--start-maximized",
        "--disable-blink-features=AutomationControlled",
        "--no-first-run",
        "--no-default-browser-check",
    ]
    if os.path.exists(user_data):
        cmd.append(f"--user-data-dir={user_data}")
        cmd.append(f"--profile-directory={profile_dir}")

    # 直接在啟動指令帶上網址，Chrome 開啟就會導航到該頁面
    if open_url:
        cmd.append(open_url)
        log(f"🌐 啟動時直接開啟：{open_url}")

    log(f"🚀 啟動指令：{' '.join(cmd)}")
    subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    # 等待 debug port 就緒
    for i in range(25):
        time.sleep(1)
        if is_debug_port_active(port):
            log(f"✅ debug port {port} 就緒（等待 {i+1} 秒）")
            return True
    log(f"❌ debug port {port} 等待超時")
    return False


# ═══════════════════════════════════════
# 瀏覽器控制
# ═══════════════════════════════════════
class GeminiBrowser:
    """subprocess 開 Chrome，純鍵盤操作（不需要 Selenium、不需要座標）"""

    def __init__(self, cfg=None, log_cb=None):
        self.driver = None
        self.log = log_cb or print
        self.cfg = cfg
        self.profile_dir = (cfg.chrome_profile if cfg and cfg.chrome_profile else "")
        self.user_data_path = (cfg.chrome_user_data if cfg and cfg.chrome_user_data else "")

    def _safe_clip(self, text):
        """安全地複製文字到剪貼簿（帶重試）"""
        for attempt in range(5):
            try:
                if HAS_PYAUTOGUI:
                    pyperclip.copy(text)
                    return True
            except:
                time.sleep(0.5)
        # fallback: 用 Windows clip 指令
        try:
            p = subprocess.Popen(['clip'], stdin=subprocess.PIPE)
            p.communicate(text.encode('utf-8'))
            return True
        except:
            pass
        return False

    def start(self, target_url=""):
        """subprocess 開 Chrome（帶網址，不需要 debug port）"""
        user_data = self.user_data_path or get_chrome_user_data()
        profile = self.profile_dir

        if not profile:
            profiles = list_chrome_profiles(user_data)
            if profiles:
                self.log("📋 偵測到的 Chrome Profiles：")
                for p in profiles:
                    marker = f" ← {p['email']}" if p["email"] else ""
                    self.log(f"   • {p['dir']}（{p['name']}）{marker}")
            profile = auto_detect_profile(user_data)
            self.log(f"🎯 自動選擇 Profile：{profile}")

        if is_chrome_running():
            self.log("🔄 正在關閉 Chrome...")
            kill_chrome(log_cb=self.log)
            time.sleep(2)

        chrome_path = get_chrome_path()
        if not chrome_path:
            raise Exception("找不到 Chrome，請確認已安裝 Google Chrome")

        cmd = [
            chrome_path,
            "--start-maximized",
            "--no-first-run",
            "--no-default-browser-check",
        ]
        if os.path.exists(user_data):
            cmd.append(f"--user-data-dir={user_data}")
            cmd.append(f"--profile-directory={profile}")
        if target_url:
            cmd.append(target_url)

        self.log(f"🚀 啟動 Chrome...")
        self.log(f"📂 Profile：{profile}")
        subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

        self.log(f"⏳ 等待 Chrome 開啟...")
        time.sleep(8)
        self.log("✅ Chrome 已啟動")

    def wait_for_page(self, timeout=12):
        self.log(f"⏳ 等待 Gemini 頁面載入（{timeout} 秒）...")
        time.sleep(timeout)
        self.log("✅ 頁面應該已載入")

    def open(self, site="gemini"):
        pass  # start() 已帶網址

    def _get_chrome_window(self):
        """找到 Chrome 視窗，回傳 (left, top, width, height) 或 None"""
        if not HAS_PYAUTOGUI:
            return None
        try:
            for title_kw in ['Gemini', 'Google Chrome', 'Chrome']:
                wins = pyautogui.getWindowsWithTitle(title_kw)
                for w in wins:
                    if w.width > 200 and w.height > 200:
                        w.activate()
                        time.sleep(0.5)
                        self.log(f"✅ 找到 Chrome 視窗：{w.title[:40]}（{w.left},{w.top} {w.width}x{w.height}）")
                        return w
        except Exception as e:
            self.log(f"⚠️ 找視窗失敗：{e}")
        return None

    def _save_screenshot(self, label=""):
        """截圖存檔，方便除錯"""
        try:
            img = pyautogui.screenshot()
            path = os.path.join(base_dir(), f"debug_screenshot_{label}.png")
            img.save(path)
            self.log(f"📸 截圖已存：{path}")
        except:
            pass

    def _get_dpi_scale(self):
        """取得 Windows DPI 縮放比例"""
        try:
            import ctypes
            # 設定 DPI 感知
            try:
                ctypes.windll.shcore.SetProcessDpiAwareness(2)
            except:
                try:
                    ctypes.windll.user32.SetProcessDPIAware()
                except:
                    pass
            # 取得主螢幕 DPI
            hdc = ctypes.windll.user32.GetDC(0)
            dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88)  # LOGPIXELSX
            ctypes.windll.user32.ReleaseDC(0, hdc)
            scale = dpi / 96.0
            self.log(f"📐 DPI：{dpi}，縮放：{scale}")
            return scale
        except:
            return 1.0

    def _is_file_dialog_open(self):
        """檢查 Windows 檔案對話框是否已開啟"""
        try:
            # 檔案對話框的標題通常是「開啟」「Open」「選擇檔案」等
            for kw in ['開啟', 'Open', '選擇', 'Choose', 'Select', 'Upload']:
                wins = pyautogui.getWindowsWithTitle(kw)
                if wins:
                    for w in wins:
                        if w.width > 200 and w.height > 200:
                            self.log(f"✅ 偵測到檔案對話框：{w.title}")
                            return True
        except:
            pass
        return False

    def _get_input_positions(self, w, is_new_chat=True):
        """根據對話狀態計算輸入框和「+」按鈕位置"""
        visible_w = w.width - 16
        visible_h = w.height - 16

        if is_new_chat:
            # 新對話：輸入框在頁面中間
            # 根據實際截圖：
            #   - 輸入框文字區 ≈ 47% 高度
            #   - 「+」按鈕行 ≈ 52% 高度（在輸入框下方）
            #   - 「+」按鈕 ≈ 33% 寬度（輸入框左下角）
            #   - 輸入框中心 ≈ 50% 寬度
            input_x = int(visible_w * 0.50)
            input_y = int(visible_h * 0.47)
            plus_x = int(visible_w * 0.33)
            plus_y = int(visible_h * 0.52)
            self.log(f"📐 新對話模式：輸入框({input_x},{input_y}) +按鈕({plus_x},{plus_y})")
        else:
            # 已有對話：輸入框在底部
            input_x = int(visible_w * 0.50)
            input_y = int(visible_h - 70)
            plus_x = int(visible_w * 0.33)
            plus_y = int(visible_h - 70)
            self.log(f"📐 對話模式：輸入框({input_x},{input_y}) +按鈕({plus_x},{plus_y})")

        return plus_x, plus_y, input_x, input_y

    def upload(self, path, is_new_chat=True):
        """上傳檔案：點「+」→「上傳檔案」→ 確認對話框開啟 → 填路徑"""
        self.log(f"📎 上傳：{os.path.basename(path)}")
        abs_path = os.path.abspath(path).replace('/', '\\')

        if not HAS_PYAUTOGUI:
            self.log("⚠️ 請手動上傳檔案")
            return False

        try:
            w = self._get_chrome_window()
            if not w:
                self.log("⚠️ 找不到 Chrome 視窗")
                return False

            plus_x, plus_y, _, _ = self._get_input_positions(w, is_new_chat)

            self._save_screenshot("before_upload")

            # 點「+」按鈕
            self.log(f"🖱️ 點擊「+」({plus_x}, {plus_y})")
            pyautogui.moveTo(plus_x, plus_y, duration=0.5)
            time.sleep(0.3)
            pyautogui.click()
            time.sleep(2)

            self._save_screenshot("after_plus_click")

            # 點「上傳檔案」選單（選單在「+」按鈕上方彈出）
            # 選單項通常在按鈕正上方，第一項約 50-70px 上方
            menu_x = plus_x + 30
            menu_y = plus_y - 60
            self.log(f"🖱️ 點擊「上傳檔案」({menu_x}, {menu_y})")
            pyautogui.moveTo(menu_x, menu_y, duration=0.3)
            time.sleep(0.3)
            pyautogui.click()
            time.sleep(2)

            self._save_screenshot("after_menu_click")

            # ★ 確認檔案對話框已開啟，才填入路徑
            if self._is_file_dialog_open():
                self.log(f"📂 檔案對話框已開啟，輸入：{abs_path}")
                self._safe_clip(abs_path)
                time.sleep(0.5)
                pyautogui.hotkey('ctrl', 'a')
                time.sleep(0.2)
                pyautogui.hotkey('ctrl', 'v')
                time.sleep(0.5)
                pyautogui.press('enter')
                time.sleep(3)
                self._save_screenshot("after_upload")
                self.log(f"✅ 已上傳：{os.path.basename(path)}")
                return True
            else:
                self.log("⚠️ 檔案對話框未開啟，按 Escape 取消...")
                pyautogui.press('escape')
                time.sleep(0.5)
                self.log(
                    f"⚠️ 自動上傳失敗，請手動上傳：\n"
                    f"   點「+」→「上傳檔案」→ 選擇 {abs_path}"
                )
                return False

        except Exception as e:
            self.log(f"⚠️ 上傳失敗：{e}")
            return False

    def send(self, text, is_new_chat=True):
        """輸入 Prompt：點輸入框 → Ctrl+V 貼上 → Enter"""
        self.log("✏️ 準備送出 Prompt...")

        if not HAS_PYAUTOGUI:
            self._safe_clip(text)
            self.log("⚠️ Prompt 已複製，請手動 Ctrl+V + Enter")
            return False

        try:
            if not self._safe_clip(text):
                self.log("⚠️ 剪貼簿操作失敗")
                return False
            time.sleep(0.5)

            w = self._get_chrome_window()
            if not w:
                self.log("⚠️ 找不到 Chrome 視窗")
                return False

            _, _, input_x, input_y = self._get_input_positions(w, is_new_chat)

            self.log(f"🖱️ 點擊輸入框 ({input_x}, {input_y})")
            pyautogui.moveTo(input_x, input_y, duration=0.5)
            time.sleep(0.3)

            self._save_screenshot("before_paste")

            pyautogui.click()
            time.sleep(1)

            # Ctrl+V 貼上
            self.log("📋 Ctrl+V 貼上...")
            pyautogui.hotkey('ctrl', 'v')
            time.sleep(2)

            self._save_screenshot("after_paste")

            # Enter 送出
            self.log("📤 Enter 送出...")
            pyautogui.press('enter')
            time.sleep(1)
            self.log("✅ 已送出 Prompt")
            return True

        except Exception as e:
            self._safe_clip(text)
            self.log(f"⚠️ 送出失敗：{e}")
            return False

    def wait_response(self, timeout=180):
        self.log("⏳ 請等待 AI 回應完成後，複製回應 → 點「從剪貼簿解析」")
        return None

    def _clip(self, text):
        self._safe_clip(text)

    def close(self):
        pass


# ═══════════════════════════════════════
# 主應用程式 UI
# ═══════════════════════════════════════
class App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.cfg = Config()
        ctk.set_appearance_mode(self.cfg.theme)
        ctk.set_default_color_theme("blue")

        self.title(f"考試題目解析工具 v{VER}")
        self.geometry("1050x820")
        self.minsize(950, 720)

        self.files = []
        self.parsed = []
        self.processing = False

        # 主色
        self.ACCENT = "#6366f1"
        self.ACCENT2 = "#818cf8"
        self.GREEN = "#10b981"
        self.ORANGE = "#f59e0b"
        self.RED = "#ef4444"

        self._build()

    def _build(self):
        # ═══ Header ═══
        hdr = ctk.CTkFrame(self, height=60, corner_radius=0)
        hdr.pack(fill="x")
        hdr.pack_propagate(False)

        # 左：Logo
        ctk.CTkLabel(hdr, text=" 📝  考試題目解析工具",
                      font=ctk.CTkFont("Microsoft JhengHei", 17, "bold")).pack(side="left", padx=20)

        # 右：主題切換
        self.theme_var = ctk.StringVar(value=self.cfg.theme)
        theme_seg = ctk.CTkSegmentedButton(
            hdr, values=["dark", "light"],
            variable=self.theme_var,
            command=self._switch_theme,
            font=ctk.CTkFont(size=12),
            width=160, height=32, corner_radius=16,
            selected_color=self.ACCENT,
            selected_hover_color=self.ACCENT2,
        )
        theme_seg.pack(side="right", padx=20)
        ctk.CTkLabel(hdr, text="主題：", font=ctk.CTkFont(size=12)).pack(side="right")

        # ═══ Tabs ═══
        self.tabs = ctk.CTkTabview(self, corner_radius=14,
                                    segmented_button_selected_color=self.ACCENT,
                                    segmented_button_selected_hover_color=self.ACCENT2)
        self.tabs.pack(fill="both", expand=True, padx=16, pady=(8, 16))

        self._build_tab1(self.tabs.add("  🚀 上傳解析  "))
        self._build_tab2(self.tabs.add("  📋 解析結果  "))
        self._build_tab3(self.tabs.add("  ⚙️ 設定  "))

    def _switch_theme(self, val):
        ctk.set_appearance_mode(val)
        self.cfg.theme = val
        self.cfg.save()

    # ──────────────────────────────
    # Tab 1：上傳解析
    # ──────────────────────────────
    def _build_tab1(self, parent):
        # 提醒卡片
        warn = ctk.CTkFrame(parent, corner_radius=12, fg_color="#fef3c7", border_color="#f59e0b", border_width=2)
        warn.pack(fill="x", pady=(0, 12))
        wi = ctk.CTkFrame(warn, fg_color="transparent")
        wi.pack(fill="x", padx=14, pady=10)
        ctk.CTkLabel(wi, text="⚠️", font=ctk.CTkFont(size=20)).pack(side="left", padx=(0, 10))
        warn_text = ctk.CTkFrame(wi, fg_color="transparent")
        warn_text.pack(side="left", fill="x", expand=True)
        ctk.CTkLabel(warn_text, text="程式會自動啟動 Chrome 並使用您已登入的帳號",
                      font=ctk.CTkFont("Microsoft JhengHei", 13, "bold"), text_color="#92400e").pack(anchor="w")
        ctk.CTkLabel(warn_text, text="如果 Chrome 正在執行，程式會自動重啟它（請先儲存未完成的工作）",
                      font=ctk.CTkFont(size=11), text_color="#a16207", wraplength=700).pack(anchor="w")

        # 流程步驟
        steps_frame = ctk.CTkFrame(parent, corner_radius=12)
        steps_frame.pack(fill="x", pady=(0, 12))
        steps = [
            ("1", "📁", "選檔案", "選擇 PDF 或圖片"),
            ("2", "🚀", "開始解析", "自動開啟 Chrome"),
            ("3", "🤖", "AI 處理", "Gemini 解析中"),
            ("4", "✨", "取得結果", "AI 解析完成"),
        ]
        for i, (num, icon, title, desc) in enumerate(steps):
            sf = ctk.CTkFrame(steps_frame, fg_color="transparent")
            sf.pack(side="left", expand=True, padx=8, pady=10)
            # 圓形數字
            ctk.CTkLabel(sf, text=num, width=28, height=28, corner_radius=14,
                          fg_color=self.ACCENT, text_color="white",
                          font=ctk.CTkFont(size=12, weight="bold")).pack()
            ctk.CTkLabel(sf, text=f"{icon} {title}", font=ctk.CTkFont("Microsoft JhengHei", 12, "bold")).pack(pady=(4, 0))
            ctk.CTkLabel(sf, text=desc, font=ctk.CTkFont(size=10),
                          text_color="gray").pack()
            # 箭頭
            if i < len(steps) - 1:
                ctk.CTkLabel(steps_frame, text="→", font=ctk.CTkFont(size=16),
                              text_color="gray").pack(side="left", before=sf.master.winfo_children()[-1] if False else None)

        # 主區域（左右）
        main = ctk.CTkFrame(parent, fg_color="transparent")
        main.pack(fill="both", expand=True)
        main.grid_columnconfigure(0, weight=2)
        main.grid_columnconfigure(1, weight=3)
        main.grid_rowconfigure(0, weight=1)

        # 左：檔案
        left = ctk.CTkFrame(main, corner_radius=12)
        left.grid(row=0, column=0, sticky="nsew", padx=(0, 6))

        ctk.CTkLabel(left, text="📁 考試檔案",
                      font=ctk.CTkFont("Microsoft JhengHei", 14, "bold")).pack(anchor="w", padx=14, pady=(12, 6))

        bf = ctk.CTkFrame(left, fg_color="transparent")
        bf.pack(fill="x", padx=14)
        ctk.CTkButton(bf, text="＋ 選擇檔案", width=110, height=34, corner_radius=8,
                       fg_color=self.ACCENT, hover_color=self.ACCENT2,
                       font=ctk.CTkFont("Microsoft JhengHei", 12),
                       command=self._sel_files).pack(side="left")
        ctk.CTkButton(bf, text="清除", width=55, height=34, corner_radius=8,
                       fg_color="transparent", border_width=1,
                       hover_color=("gray85", "gray30"),
                       command=self._clr_files).pack(side="left", padx=(8, 0))
        self.file_count = ctk.CTkLabel(bf, text="0 個檔案", font=ctk.CTkFont(size=11), text_color="gray")
        self.file_count.pack(side="right")

        self.file_box = ctk.CTkTextbox(left, font=ctk.CTkFont("Consolas", 11), corner_radius=8,
                                        border_width=1, height=60)
        self.file_box.pack(fill="both", expand=True, padx=14, pady=(8, 4))
        self.file_box.insert("end", "  拖曳或點擊上方按鈕選擇檔案...\n")
        self.file_box.configure(state="disabled")

        ctk.CTkLabel(left, text="支援 PDF / PNG / JPG / JPEG / WEBP", font=ctk.CTkFont(size=10),
                      text_color="gray").pack(padx=14, pady=(0, 10))

        # 右：Prompt
        right = ctk.CTkFrame(main, corner_radius=12)
        right.grid(row=0, column=1, sticky="nsew", padx=(6, 0))

        ph = ctk.CTkFrame(right, fg_color="transparent")
        ph.pack(fill="x", padx=14, pady=(12, 6))
        ctk.CTkLabel(ph, text="✏️ AI 提示詞 (Prompt)",
                      font=ctk.CTkFont("Microsoft JhengHei", 14, "bold")).pack(side="left")
        ctk.CTkButton(ph, text="還原預設", width=70, height=26, corner_radius=6,
                       font=ctk.CTkFont(size=10), fg_color="transparent", border_width=1,
                       hover_color=("gray85", "gray30"), command=self._reset_prompt).pack(side="right")

        self.prompt_box = ctk.CTkTextbox(right, font=ctk.CTkFont("Microsoft JhengHei", 10),
                                          corner_radius=8, border_width=1, wrap="word")
        self.prompt_box.pack(fill="both", expand=True, padx=14, pady=(0, 10))
        self.prompt_box.insert("end", self.cfg.prompt)

        # 底部操作列
        bot = ctk.CTkFrame(parent, corner_radius=12)
        bot.pack(fill="x", pady=(12, 0))

        bi = ctk.CTkFrame(bot, fg_color="transparent")
        bi.pack(fill="x", padx=14, pady=12)

        self.run_btn = ctk.CTkButton(
            bi, text="🚀  開啟 Gemini 並解析", height=48, corner_radius=12,
            font=ctk.CTkFont("Microsoft JhengHei", 15, "bold"),
            fg_color=self.ACCENT, hover_color=self.ACCENT2,
            command=self._start)
        self.run_btn.pack(side="left")

        ctk.CTkButton(bi, text="📋 複製 Prompt", height=48, corner_radius=12,
                       fg_color="transparent", border_width=1,
                       hover_color=("gray85", "gray30"), font=ctk.CTkFont(size=12),
                       command=self._copy_prompt).pack(side="left", padx=(10, 0))

        ctk.CTkButton(bi, text="📥 從剪貼簿解析", height=48, corner_radius=12,
                       fg_color="transparent", border_width=1,
                       hover_color=("gray85", "gray30"), font=ctk.CTkFont(size=12),
                       command=self._from_clip).pack(side="left", padx=(10, 0))

        self.progress = ctk.CTkProgressBar(bot, mode="indeterminate", height=4,
                                            progress_color=self.ACCENT)
        self.progress.pack(fill="x", padx=14, pady=(0, 2))
        self.progress.set(0)

        self.status = ctk.CTkLabel(bot, text="就緒 — 選擇檔案後點擊「開啟 Gemini 並解析」",
                                    font=ctk.CTkFont("Microsoft JhengHei", 11), text_color="gray", anchor="w")
        self.status.pack(fill="x", padx=14, pady=(0, 10))

    # ──────────────────────────────
    # Tab 2：結果
    # ──────────────────────────────
    def _build_tab2(self, parent):
        top = ctk.CTkFrame(parent, corner_radius=12)
        top.pack(fill="x", pady=(0, 10))
        ti = ctk.CTkFrame(top, fg_color="transparent")
        ti.pack(fill="x", padx=14, pady=10)
        self.res_label = ctk.CTkLabel(ti, text="尚無解析結果",
                                       font=ctk.CTkFont("Microsoft JhengHei", 14, "bold"))
        self.res_label.pack(side="left")

        for txt, clr, cmd in [("📦 輸出 JSON", self.GREEN, self._export_json),
                               ("📤 CSV", self.ORANGE, self._csv),
                               ("💾 Markdown", self.ACCENT, self._md)]:
            ctk.CTkButton(ti, text=txt, height=34, corner_radius=8, fg_color=clr,
                           hover_color=clr, command=cmd,
                           font=ctk.CTkFont(size=12)).pack(side="right", padx=(6, 0))

        self.res_box = ctk.CTkTextbox(parent, font=ctk.CTkFont("Microsoft JhengHei", 11),
                                       corner_radius=12, wrap="word")
        self.res_box.pack(fill="both", expand=True)
        self.res_box.insert("end", "\n\n\t\t解析結果將顯示在此處\n\n\t\t請先到「上傳解析」頁籤選擇檔案並開始解析")

    # ──────────────────────────────
    # Tab 3：設定
    # ──────────────────────────────
    def _build_tab3(self, parent):
        # 外觀
        g0 = ctk.CTkFrame(parent, corner_radius=12)
        g0.pack(fill="x", pady=(0, 10))
        ctk.CTkLabel(g0, text="🎨 外觀", font=ctk.CTkFont("Microsoft JhengHei", 14, "bold")).pack(anchor="w", padx=14, pady=(12, 8))
        r0 = ctk.CTkFrame(g0, fg_color="transparent")
        r0.pack(fill="x", padx=14, pady=(0, 12))
        ctk.CTkLabel(r0, text="主題模式：", width=100, anchor="e").pack(side="left")
        self.theme_menu = ctk.CTkOptionMenu(r0, values=["dark", "light"], width=160,
                                             command=self._switch_theme)
        self.theme_menu.set(self.cfg.theme)
        self.theme_menu.pack(side="left", padx=(8, 0))
        ctk.CTkLabel(r0, text="dark = 暗黑模式 ｜ light = 明亮模式", font=ctk.CTkFont(size=10), text_color="gray").pack(side="left", padx=(12, 0))

        # Gemini
        g1 = ctk.CTkFrame(parent, corner_radius=12)
        g1.pack(fill="x", pady=(0, 10))
        ctk.CTkLabel(g1, text="🌐 Gemini 設定", font=ctk.CTkFont("Microsoft JhengHei", 14, "bold")).pack(anchor="w", padx=14, pady=(12, 8))
        for label, attr, default, hint in [
            ("開啟網站：", "site", self.cfg.site, "gemini / aistudio"),
            ("等待秒數：", "wait_timeout", str(self.cfg.wait_timeout), "AI 回應最長等待時間"),
        ]:
            r = ctk.CTkFrame(g1, fg_color="transparent")
            r.pack(fill="x", padx=14, pady=(0, 4))
            ctk.CTkLabel(r, text=label, width=100, anchor="e").pack(side="left")
            e = ctk.CTkEntry(r, width=200)
            e.insert(0, default)
            e.pack(side="left", padx=(8, 0))
            setattr(self, f"_set_{attr}", e)
            ctk.CTkLabel(r, text=hint, font=ctk.CTkFont(size=10), text_color="gray").pack(side="left", padx=(10, 0))
        ctk.CTkFrame(g1, fg_color="transparent", height=8).pack()

        # Chrome Profile
        g_chrome = ctk.CTkFrame(parent, corner_radius=12)
        g_chrome.pack(fill="x", pady=(0, 10))
        ctk.CTkLabel(g_chrome, text="🌐 Chrome 設定",
                      font=ctk.CTkFont("Microsoft JhengHei", 14, "bold")).pack(anchor="w", padx=14, pady=(12, 8))

        # Profile 下拉選單
        rp = ctk.CTkFrame(g_chrome, fg_color="transparent")
        rp.pack(fill="x", padx=14, pady=(0, 4))
        ctk.CTkLabel(rp, text="Chrome Profile：", width=120, anchor="e").pack(side="left")

        # 偵測可用 profiles
        profiles = list_chrome_profiles()
        profile_options = ["（自動偵測）"]
        self._profile_map = {"（自動偵測）": ""}
        for p in profiles:
            label = f"{p['dir']}"
            if p['name'] and p['name'] != p['dir']:
                label += f"（{p['name']}）"
            if p['email']:
                label += f" - {p['email']}"
            profile_options.append(label)
            self._profile_map[label] = p['dir']

        self._set_chrome_profile = ctk.CTkOptionMenu(rp, values=profile_options, width=350)
        # 設定目前選中的值
        current_profile = self.cfg.chrome_profile
        selected = "（自動偵測）"
        if current_profile:
            for label, dir_name in self._profile_map.items():
                if dir_name == current_profile:
                    selected = label
                    break
        self._set_chrome_profile.set(selected)
        self._set_chrome_profile.pack(side="left", padx=(8, 0))

        # User Data 路徑
        rud = ctk.CTkFrame(g_chrome, fg_color="transparent")
        rud.pack(fill="x", padx=14, pady=(4, 4))
        ctk.CTkLabel(rud, text="User Data 路徑：", width=120, anchor="e").pack(side="left")
        self._set_chrome_user_data = ctk.CTkEntry(rud, width=400,
            placeholder_text=get_chrome_user_data())
        self._set_chrome_user_data.insert(0, self.cfg.chrome_user_data)
        self._set_chrome_user_data.pack(side="left", padx=(8, 0))

        ctk.CTkLabel(g_chrome,
            text="💡 空白 = 自動偵測。如果登入帳號找不到，請選擇正確的 Profile",
            font=ctk.CTkFont(size=10), text_color="gray").pack(anchor="w", padx=14, pady=(4, 12))

        self._cfg_entries = {}

        ctk.CTkButton(parent, text="💾  儲存所有設定", height=44, corner_radius=12,
                       fg_color=self.ACCENT, hover_color=self.ACCENT2,
                       font=ctk.CTkFont("Microsoft JhengHei", 13, "bold"),
                       command=self._save_cfg).pack(anchor="w", pady=(8, 0))

    # ═══ 操作邏輯 ═══
    def _sel_files(self):
        ps = filedialog.askopenfilenames(filetypes=[("支援格式", "*.pdf *.png *.jpg *.jpeg *.webp"), ("所有", "*.*")])
        for p in ps:
            if p not in self.files: self.files.append(p)
        self._refresh_files()

    def _clr_files(self):
        self.files.clear(); self._refresh_files()

    def _refresh_files(self):
        self.file_box.configure(state="normal")
        self.file_box.delete("1.0", "end")
        for f in self.files:
            self.file_box.insert("end", f"  📄 {os.path.basename(f)}\n")
        if not self.files:
            self.file_box.insert("end", "  拖曳或點擊上方按鈕選擇檔案...\n")
        self.file_box.configure(state="disabled")
        self.file_count.configure(text=f"{len(self.files)} 個檔案")

    def _reset_prompt(self):
        self.prompt_box.delete("1.0", "end")
        self.prompt_box.insert("end", DEFAULT_PROMPT)

    def _copy_prompt(self):
        self.clipboard_clear()
        self.clipboard_append(self.prompt_box.get("1.0", "end").strip())
        self._log("✅ Prompt 已複製到剪貼簿，正在開啟 Gemini...", self.GREEN)
        import webbrowser; webbrowser.open(GEMINI_URL)

    def _log(self, msg, color="gray"):
        self.after(0, lambda: self.status.configure(text=msg, text_color=color))
        # 同時寫入日誌檔，方便除錯
        try:
            log_path = os.path.join(base_dir(), "debug.log")
            with open(log_path, 'a', encoding='utf-8') as f:
                f.write(f"[{datetime.datetime.now():%H:%M:%S}] {msg}\n")
        except:
            pass

    def _start(self):
        if not self.files:
            messagebox.showwarning("提示", "請先選擇考試檔案")
            return
        if not HAS_SELENIUM:
            messagebox.showerror("缺少套件", "請執行以下指令安裝：\n\npip install selenium")
            return
        if self.processing:
            return

        # Chrome 正在運行時提示（但不阻止，程式會自動處理）
        if is_chrome_running():
            result = messagebox.askokcancel(
                "Chrome 正在執行",
                "偵測到 Chrome 正在執行。\n\n"
                "程式將會：\n"
                "• 自動關閉 Chrome\n"
                "• 以 debug 模式重新啟動\n"
                "• 保留您的登入狀態\n\n"
                "請先儲存 Chrome 中未完成的工作，然後點擊「確定」。",
                icon="info")
            if not result:
                return

        self.processing = True
        self.run_btn.configure(state="disabled")
        self.progress.start()
        self.cfg.prompt = self.prompt_box.get("1.0", "end").strip()
        self.cfg.save()
        threading.Thread(target=self._do, daemon=True).start()

    def _do(self):
        # 清空舊的 debug log
        try:
            log_path = os.path.join(base_dir(), "debug.log")
            with open(log_path, 'w', encoding='utf-8') as f:
                f.write(f"=== 開始執行 {datetime.datetime.now():%Y-%m-%d %H:%M:%S} ===\n")
        except:
            pass

        try:
            target_url = AISTUDIO_URL if self.cfg.site == "aistudio" else GEMINI_URL
            self._log(f"🎯 目標：{target_url}", self.ACCENT)

            b = GeminiBrowser(cfg=self.cfg, log_cb=lambda m: self._log(m, self.ACCENT))

            # 步驟 1：開啟 Chrome 到 Gemini
            self._log("📌 步驟 1/4：開啟 Chrome...", self.ACCENT)
            b.start(target_url=target_url)

            # 步驟 2：等待頁面載入
            self._log("📌 步驟 2/4：等待頁面載入（15 秒）...", self.ACCENT)
            b.wait_for_page(timeout=15)

            # 步驟 3：上傳檔案（新對話模式 — 輸入框在中間）
            self._log(f"📌 步驟 3/4：上傳 {len(self.files)} 個檔案...", self.ACCENT)
            for i, f in enumerate(self.files):
                self._log(f"📎 上傳 {i+1}/{len(self.files)}：{os.path.basename(f)}", self.ACCENT)
                b.upload(f, is_new_chat=True)  # 新對話，輸入框在中間
                time.sleep(2)

            # 步驟 4：送出 Prompt
            # 如果檔案上傳成功，輸入框可能還在中間（檔案附在輸入框但還沒送出）
            self._log("📌 步驟 4/4：送出 Prompt...", self.ACCENT)
            time.sleep(2)
            b.send(self.cfg.prompt, is_new_chat=True)  # 第一次送出，輸入框在中間

            self._log(
                "✅ 操作完成！\n\n"
                "請確認 Gemini 頁面：\n"
                "   1. 檔案已上傳（顯示在輸入框上方）\n"
                "   2. Prompt 已送出\n"
                "   3. 等 AI 回應完成\n"
                "   4. 選取全部回應 → Ctrl+C\n"
                "   5. 回到本程式點「從剪貼簿解析」",
                self.GREEN
            )
            self.after(0, self._finish)

        except Exception as e:
            self._log(f"❌ {str(e)[:120]}", self.RED)
            self.after(0, lambda: messagebox.showerror("錯誤", str(e)))
            self.after(0, self._finish)

    def _extract(self, text):
        try:
            s, e = text.find('['), text.rfind(']')
            if s == -1 or e == -1: raise Exception("回應中找不到 JSON 陣列")
            self.parsed = json.loads(text[s:e+1])
            if not self.parsed: raise Exception("JSON 中沒有題目")
            n = len(self.parsed)
            self._log(f"✅ 解析完成！共 {n} 題", self.GREEN)
            self.after(0, lambda: self._show(n))
        except json.JSONDecodeError:
            self._log("⚠️ JSON 格式錯誤 → 請手動複製 Gemini 回應後點「從剪貼簿解析」", self.ORANGE)
        except Exception as ex:
            self._log(f"❌ {ex}", self.RED)
        self.after(0, self._finish)

    def _from_clip(self):
        try: self._extract(self.clipboard_get())
        except: messagebox.showinfo("提示", "剪貼簿是空的，請先從 Gemini 複製 AI 的回應")

    def _finish(self):
        self.processing = False
        self.run_btn.configure(state="normal")
        self.progress.stop(); self.progress.set(0)

    def _show(self, n):
        self._finish()
        self.res_label.configure(text=f"✅ 共解析出 {n} 題")
        self.res_box.delete("1.0", "end")
        T = {'multiple_choice':'選擇題','true_false':'是非題','fill_blank':'填充題','short_answer':'簡答題','essay':'問答題'}
        D = {1:'簡單',2:'中等',3:'困難'}
        for i, q in enumerate(self.parsed, 1):
            self.res_box.insert("end", f"\n {'━'*48}\n")
            self.res_box.insert("end", f"   第 {i} 題    {T.get(q.get('question_type',''),'?')}  ·  {D.get(q.get('difficulty',2),'中等')}\n")
            self.res_box.insert("end", f" {'━'*48}\n\n")
            self.res_box.insert("end", f" {q.get('question_text','')}\n\n")
            if q.get('options'):
                for k, v in q['options'].items():
                    mark = "  ✅" if q.get('correct_answer') == k else ""
                    self.res_box.insert("end", f"    {k}.  {v}{mark}\n")
                self.res_box.insert("end", "\n")
            if q.get('correct_answer'):
                self.res_box.insert("end", f"   🎯 正確答案：{q['correct_answer']}\n")
            if q.get('explanation'):
                self.res_box.insert("end", f"\n   📖 詳解：\n   {q['explanation']}\n")
        self.tabs.set("  📋 解析結果  ")

    # ═══ 匯出 ═══
    def _md(self):
        if not self.parsed: return
        p = filedialog.asksaveasfilename(defaultextension=".md", initialfile=f"exam_{datetime.datetime.now():%Y%m%d_%H%M%S}.md")
        if not p: return
        T = {'multiple_choice':'選擇題','true_false':'是非題','fill_blank':'填充題','short_answer':'簡答題','essay':'問答題'}
        D = {1:'簡單',2:'中等',3:'困難'}
        with open(p,'w',encoding='utf-8') as f:
            f.write(f"# 考試解析結果\n\n> 產生時間：{datetime.datetime.now():%Y-%m-%d %H:%M}\n> 共 {len(self.parsed)} 題\n\n---\n\n")
            for i,q in enumerate(self.parsed,1):
                f.write(f"## 第 {i} 題　`{T.get(q.get('question_type',''),'?')}`　`{D.get(q.get('difficulty',2),'中等')}`\n\n{q.get('question_text','')}\n\n")
                if q.get('options'):
                    for k,v in q['options'].items(): f.write(f"- **{k}.** {v}{' ✅' if q.get('correct_answer')==k else ''}\n")
                    f.write("\n")
                if q.get('correct_answer'): f.write(f"**🎯 正確答案：** `{q['correct_answer']}`\n\n")
                if q.get('explanation'): f.write(f"### 📖 詳解\n\n{q['explanation']}\n\n")
                f.write("---\n\n")
        messagebox.showinfo("完成", f"已儲存 Markdown：\n{p}")

    def _csv(self):
        if not self.parsed: return
        p = filedialog.asksaveasfilename(defaultextension=".csv", initialfile=f"exam_{datetime.datetime.now():%Y%m%d_%H%M%S}.csv")
        if not p: return
        with open(p,'w',encoding='utf-8-sig',newline='') as f:
            w = csv.writer(f)
            w.writerow(['題目文字','題型','選項A','選項B','選項C','選項D','正確答案','難度','詳解'])
            for q in self.parsed:
                o = q.get('options') or {}
                w.writerow([q.get('question_text',''),q.get('question_type',''),o.get('A',''),o.get('B',''),o.get('C',''),o.get('D',''),q.get('correct_answer',''),q.get('difficulty',2),q.get('explanation','')])
        messagebox.showinfo("完成", f"已儲存 CSV（可在系統中匯入）：\n{p}")

    def _export_json(self):
        if not self.parsed: return
        p = filedialog.asksaveasfilename(defaultextension=".json",
            initialfile=f"exam_{datetime.datetime.now():%Y%m%d_%H%M%S}.json",
            filetypes=[("JSON 檔案", "*.json")])
        if not p: return
        with open(p, 'w', encoding='utf-8') as f:
            json.dump({"questions": self.parsed}, f, ensure_ascii=False, indent=2)
        self._log(f"✅ 已儲存 JSON：{p}", self.GREEN)
        messagebox.showinfo("完成", f"已儲存 JSON（{len(self.parsed)} 題）：\n{p}\n\n請在伺服器上執行：\nphp artisan import:questions {os.path.basename(p)}")

    def _save_cfg(self):
        for k, e in self._cfg_entries.items():
            setattr(self.cfg, k, e.get())
        self.cfg.prompt = self.prompt_box.get("1.0", "end").strip()
        self.cfg.site = self._set_site.get()
        self.cfg.theme = self.theme_menu.get()
        try: self.cfg.wait_timeout = int(self._set_wait_timeout.get())
        except: self.cfg.wait_timeout = 180
        # Chrome Profile 設定
        selected_profile = self._set_chrome_profile.get()
        self.cfg.chrome_profile = self._profile_map.get(selected_profile, "")
        self.cfg.chrome_user_data = self._set_chrome_user_data.get().strip()
        self.cfg.save()
        messagebox.showinfo("完成", "✅ 所有設定已儲存")


if __name__ == "__main__":
    try:
        from ctypes import windll
        windll.shcore.SetProcessDpiAwareness(1)
    except: pass
    App().mainloop()
