// ==UserScript==
// @name         管理系统自动化-本地
// @namespace    xyzw-helper-auto
// @version      0.1.0
// @description  在 tokens 与 game-features 页，自动循环每个角色执行：一键补差→启动服务→领取奖励→加钟→立即签到→一键答题。通过正则匹配名称并点击最近的可点击控件。
// @author       you
// @match        http://localhost:3001/tokens
// @match        http://localhost:3001/game-features
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function () {
    "use strict";

    // -------------- 基础工具 --------------
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    const nowStr = () => new Date().toLocaleTimeString("zh-CN");

    // -------------- 运行日志 --------------
    const LOG_KEY = "automation_logs";
    const LOG_LIMIT_KEY = "automation_log_limit";
    const ACTION_DELAY_KEY = "automation_action_delay_ms";
    const TASKS_KEY = "automation_tasks_config";
    const TASKS_UI_OPEN_KEY = "automation_tasks_ui_open";
    const TASK_PROGRESS_KEY = "automation_tasks_progress";
    const RESTART_INTERVAL_KEY = "automation_restart_interval_minutes";
    const PAGE_READY_DELAY_MS = 2000;
    const CONFIG_STORE_KEY = "automation_config_store_v1";
    const DEFAULT_PROFILE_NAME = "default";

    let configCache = null;
    let configManagerOpen = false;

    function clampNumber(value, min, max, fallback) {
        const num = Number(value);
        if (!Number.isFinite(num)) return fallback;
        if (min !== undefined && num < min) return min;
        if (max !== undefined && num > max) return max;
        return num;
    }

    function deepClone(value) {
        try {
            return JSON.parse(JSON.stringify(value));
        } catch (_) {
            return value;
        }
    }

    function defaultProfileSettings() {
        return {
            logLimit: 200,
            actionDelayMs: 3000,
            restartIntervalMinutes: 0,
            tasks: defaultTasksConfig(),
            tasksUiOpen: false,
            description: "",
        };
    }

    const TASK_NAME_MAP = {
        dailyFix: "一键补差",
        answer: "一键答题",
        claimReward: "领取奖励",
        addClock: "加钟",
        signIn: "立即签到",
        startService: "启动/重启服务",
    };

    function normalizeTasks(tasks) {
        const base = defaultTasksConfig();
        const input = tasks && typeof tasks === "object" ? tasks : {};
        const next = { ...base };
        for (const key in base) {
            next[key] = !!input[key];
        }
        return next;
    }

    function normalizeProfile(profile) {
        const base = defaultProfileSettings();
        const input = profile && typeof profile === "object" ? profile : {};
        const next = {
            ...base,
            ...deepClone(input),
        };
        next.logLimit = clampNumber(next.logLimit, 20, 2000, base.logLimit);
        next.actionDelayMs = clampNumber(
            next.actionDelayMs,
            0,
            600000,
            base.actionDelayMs
        );
        next.restartIntervalMinutes = clampNumber(
            next.restartIntervalMinutes,
            0,
            1440,
            base.restartIntervalMinutes
        );
        next.tasks = normalizeTasks(next.tasks);
        next.tasksUiOpen = !!next.tasksUiOpen;
        if (typeof next.description !== "string") next.description = "";
        return next;
    }

    function createEmptyStore() {
        const profile = normalizeProfile(null);
        const now = Date.now();
        return {
            version: 1,
            active: DEFAULT_PROFILE_NAME,
            profiles: {
                [DEFAULT_PROFILE_NAME]: profile,
            },
            createdAt: now,
            updatedAt: now,
        };
    }

    function normalizeConfigStore(raw) {
        const base = createEmptyStore();
        const input = raw && typeof raw === "object" ? raw : {};
        const store = {
            version: Number.isFinite(input.version) ? input.version : base.version,
            active:
                typeof input.active === "string" && input.active.trim()
                    ? input.active.trim()
                    : base.active,
            profiles: {},
            createdAt: Number.isFinite(input.createdAt)
                ? input.createdAt
                : base.createdAt,
            updatedAt: Number.isFinite(input.updatedAt)
                ? input.updatedAt
                : base.updatedAt,
        };

        const rawProfiles = input.profiles && typeof input.profiles === "object"
            ? input.profiles
            : {};

        for (const [name, profile] of Object.entries(rawProfiles)) {
            if (!name) continue;
            store.profiles[name] = normalizeProfile(profile);
        }

        if (!Object.keys(store.profiles).length) {
            store.profiles[DEFAULT_PROFILE_NAME] = normalizeProfile(null);
        }

        if (!store.profiles[store.active]) {
            store.active = Object.keys(store.profiles)[0] || DEFAULT_PROFILE_NAME;
        }

        return store;
    }

    function loadConfigStoreFromStorage() {
        try {
            const stored = gmGet(CONFIG_STORE_KEY, null);
            if (stored && typeof stored === "object" && stored.profiles) {
                return stored;
            }
        } catch (_) { }
        return null;
    }

    function migrateLegacySettings() {
        const store = createEmptyStore();
        const profile = store.profiles[DEFAULT_PROFILE_NAME];

        const legacyLogLimit = gmGet(LOG_LIMIT_KEY, null);
        if (legacyLogLimit != null) {
            profile.logLimit = clampNumber(legacyLogLimit, 20, 2000, profile.logLimit);
        }

        const legacyDelay = gmGet(ACTION_DELAY_KEY, null);
        if (legacyDelay != null) {
            profile.actionDelayMs = clampNumber(
                legacyDelay,
                0,
                600000,
                profile.actionDelayMs
            );
        }

        const legacyInterval = gmGet(RESTART_INTERVAL_KEY, null);
        if (legacyInterval != null) {
            profile.restartIntervalMinutes = clampNumber(
                legacyInterval,
                0,
                1440,
                profile.restartIntervalMinutes
            );
        }

        const legacyTasks = gmGet(TASKS_KEY, null);
        if (legacyTasks && typeof legacyTasks === "object") {
            profile.tasks = normalizeTasks(legacyTasks);
        }

        const legacyTasksUiOpen = gmGet(TASKS_UI_OPEN_KEY, null);
        if (legacyTasksUiOpen != null) {
            profile.tasksUiOpen = !!legacyTasksUiOpen;
        }

        store.updatedAt = Date.now();
        return store;
    }

    function saveConfigStore(store) {
        configCache = normalizeConfigStore(store);
        configCache.updatedAt = Date.now();
        gmSet(CONFIG_STORE_KEY, configCache);
    }

    function getConfigStore() {
        if (configCache) return configCache;
        let raw = loadConfigStoreFromStorage();
        if (!raw) {
            raw = migrateLegacySettings();
        }
        configCache = normalizeConfigStore(raw);
        saveConfigStore(configCache);
        return configCache;
    }

    function getActiveProfileName(store = getConfigStore()) {
        return store.active && store.profiles[store.active]
            ? store.active
            : DEFAULT_PROFILE_NAME;
    }

    function setActiveProfileName(name) {
        const store = getConfigStore();
        const key = typeof name === "string" ? name.trim() : "";
        if (!key || !store.profiles[key]) return false;
        if (store.active !== key) {
            store.active = key;
            saveConfigStore(store);
        }
        return true;
    }

    function getActiveProfile(store = getConfigStore()) {
        const name = getActiveProfileName(store);
        if (!store.profiles[name]) {
            store.profiles[name] = normalizeProfile(null);
            saveConfigStore(store);
        }
        return store.profiles[name];
    }

    function updateActiveProfileConfig(updater) {
        const store = getConfigStore();
        const name = getActiveProfileName(store);
        const current = getActiveProfile(store);
        const draft = { ...current, tasks: { ...current.tasks } };
        const nextRaw =
            typeof updater === "function"
                ? updater(draft) || draft
                : { ...draft, ...(updater || {}) };
        store.profiles[name] = normalizeProfile(nextRaw);
        saveConfigStore(store);
        return store.profiles[name];
    }

    function listProfileNames() {
        const store = getConfigStore();
        return Object.keys(store.profiles);
    }

    function ensureUniqueProfileName(name) {
        const trimmed = (name || "").trim();
        if (!trimmed) return null;
        return trimmed;
    }

    function hasProfile(name) {
        const store = getConfigStore();
        return !!store.profiles[name];
    }

    function createProfile(name, baseProfile) {
        const store = getConfigStore();
        const key = ensureUniqueProfileName(name);
        if (!key || store.profiles[key]) return false;
        const source = baseProfile && typeof baseProfile === "object"
            ? baseProfile
            : defaultProfileSettings();
        store.profiles[key] = normalizeProfile(source);
        saveConfigStore(store);
        return true;
    }

    function deleteProfile(name) {
        const store = getConfigStore();
        const key = ensureUniqueProfileName(name);
        if (!key || key === DEFAULT_PROFILE_NAME || !store.profiles[key]) {
            return false;
        }
        delete store.profiles[key];
        if (store.active === key) {
            store.active = DEFAULT_PROFILE_NAME;
            if (!store.profiles[store.active]) {
                const fallback = Object.keys(store.profiles)[0];
                store.active = fallback || DEFAULT_PROFILE_NAME;
            }
        }
        saveConfigStore(store);
        return true;
    }

    function renameProfile(oldName, newName) {
        const store = getConfigStore();
        const from = ensureUniqueProfileName(oldName);
        const to = ensureUniqueProfileName(newName);
        if (
            !from ||
            !to ||
            from === to ||
            !store.profiles[from] ||
            store.profiles[to] ||
            from === DEFAULT_PROFILE_NAME
        ) {
            return false;
        }
        store.profiles[to] = store.profiles[from];
        delete store.profiles[from];
        if (store.active === from) {
            store.active = to;
        }
        saveConfigStore(store);
        return true;
    }

    function summarizeTasksForProfile(profile) {
        if (!profile) return "无";
        const enabled = Object.entries(profile.tasks || {})
            .filter(([, flag]) => flag)
            .map(([key]) => TASK_NAME_MAP[key] || key);
        return enabled.length ? enabled.join("、") : "无";
    }

    function isConfigManagerOpen() {
        return configManagerOpen;
    }

    function setConfigManagerOpen(v) {
        configManagerOpen = !!v;
    }

    function toggleConfigManagerPanel() {
        setConfigManagerOpen(!isConfigManagerOpen());
        updatePanel();
    }

    function openConfigManager() {
        setConfigManagerOpen(true);
        updatePanel();
        notify("已打开配置管理，请在面板中操作。", "info");
    }

    function handleConfigUse(name) {
        if (!name || !hasProfile(name)) {
            alert(`配置「${name}」不存在`);
            return;
        }
        if (getActiveProfileName() === name) {
            return;
        }
        if (setActiveProfileName(name)) {
            notify(`已切换到配置「${name}」`, "success");
            updatePanel();
        }
    }

    function handleConfigRename(name) {
        if (!name || !hasProfile(name)) {
            alert(`配置「${name}」不存在`);
            return;
        }
        if (name === DEFAULT_PROFILE_NAME) {
            alert("默认配置不可重命名");
            return;
        }
        const input = prompt("输入新的配置名称", name);
        if (input == null) return;
        const trimmed = input.trim();
        if (!trimmed) {
            alert("名称不能为空");
            return;
        }
        if (hasProfile(trimmed)) {
            alert(`配置「${trimmed}」已存在`);
            return;
        }
        if (renameProfile(name, trimmed)) {
            notify(`已将配置「${name}」重命名为「${trimmed}」`, "success");
            updatePanel();
        } else {
            alert("重命名失败");
        }
    }

    function handleConfigDelete(name) {
        if (!name || !hasProfile(name)) {
            alert(`配置「${name}」不存在`);
            return;
        }
        if (name === DEFAULT_PROFILE_NAME) {
            alert("默认配置不可删除");
            return;
        }
        if (!confirm(`确定要删除配置「${name}」吗？该操作不可恢复。`)) {
            return;
        }
        if (deleteProfile(name)) {
            notify(`已删除配置「${name}」`, "success");
            updatePanel();
        } else {
            alert("删除配置失败");
        }
    }

    function handleConfigCreate() {
        const input = prompt("输入新配置名称");
        if (input == null) return;
        const name = input.trim();
        if (!name) {
            alert("名称不能为空");
            return;
        }
        if (hasProfile(name)) {
            alert(`配置「${name}」已存在`);
            return;
        }
        const useCurrent = confirm("是否复制当前配置？选择“取消”则使用默认配置。");
        const base = useCurrent ? deepClone(getActiveProfile()) : defaultProfileSettings();
        if (!createProfile(name, base)) {
            alert("创建配置失败");
            return;
        }
        setActiveProfileName(name);
        notify(`已创建并切换到配置「${name}」`, "success");
        updatePanel();
    }

    function renderConfigList(listEl) {
        if (!listEl) return;
        const store = getConfigStore();
        const active = getActiveProfileName(store);
        const names = listProfileNames();

        while (listEl.firstChild) listEl.removeChild(listEl.firstChild);

        if (!names.length) {
            const empty = document.createElement("div");
            empty.className = "config-empty";
            empty.textContent = "暂无配置";
            listEl.appendChild(empty);
            return;
        }

        names.forEach((name) => {
            const profile = store.profiles[name];
            const item = document.createElement("div");
            item.className = "config-item";
            item.dataset.profileName = name;
            if (name === active) item.classList.add("active");

            const header = document.createElement("div");
            header.className = "config-item-header";
            const title = document.createElement("span");
            title.textContent = name;
            header.appendChild(title);
            if (name === active) {
                const badge = document.createElement("span");
                badge.className = "config-badge";
                badge.textContent = "当前";
                header.appendChild(badge);
            }

            const meta = document.createElement("div");
            meta.className = "config-item-meta";
            meta.textContent = `任务：${summarizeTasksForProfile(profile)}｜等待：${profile.actionDelayMs}ms｜间隔：${profile.restartIntervalMinutes}分｜日志：${profile.logLimit}`;

            const buttons = document.createElement("div");
            buttons.className = "config-item-buttons";

            const useBtn = document.createElement("button");
            useBtn.type = "button";
            useBtn.dataset.configAction = "use";
            useBtn.dataset.profileName = name;
            useBtn.textContent = name === active ? "已启用" : "启用";
            if (name === active) useBtn.disabled = true;

            const renameBtn = document.createElement("button");
            renameBtn.type = "button";
            renameBtn.dataset.configAction = "rename";
            renameBtn.dataset.profileName = name;
            renameBtn.textContent = "重命名";
            if (name === DEFAULT_PROFILE_NAME) renameBtn.disabled = true;

            const deleteBtn = document.createElement("button");
            deleteBtn.type = "button";
            deleteBtn.dataset.configAction = "delete";
            deleteBtn.dataset.profileName = name;
            deleteBtn.textContent = "删除";
            if (name === DEFAULT_PROFILE_NAME) deleteBtn.disabled = true;

            buttons.append(useBtn, renameBtn, deleteBtn);
            item.append(header, meta, buttons);
            listEl.appendChild(item);
        });
    }

    function getLogLimit() {
        const profile = getActiveProfile();
        return profile?.logLimit ?? defaultProfileSettings().logLimit;
    }
    function setLogLimit(n) {
        const v = parseInt(n, 10);
        if (!Number.isFinite(v) || v <= 0) return false;
        updateActiveProfileConfig({ logLimit: clampNumber(v, 20, 2000, 200) });
        return true;
    }

    // 动作等待配置（默认 3000ms，可自定义）
    function getActionDelayMs() {
        const profile = getActiveProfile();
        const v = parseInt(profile?.actionDelayMs, 10);
        if (!Number.isFinite(v) || v < 0) return defaultProfileSettings().actionDelayMs;
        return Math.min(Math.max(v, 0), 600000); // 0ms ~ 10分钟
    }
    function setActionDelayMs(n) {
        const v = parseInt(n, 10);
        if (!Number.isFinite(v) || v < 0) return false;
        updateActiveProfileConfig({
            actionDelayMs: clampNumber(v, 0, 600000, defaultProfileSettings().actionDelayMs),
        });
        return true;
    }
    async function waitBetweenSteps() {
        const ms = getActionDelayMs();
        if (ms > 0) await sleep(ms);
    }

    // 下次启动间隔配置（默认 0 分钟，可自定义）
    function getRestartIntervalMinutes() {
        const profile = getActiveProfile();
        const v = parseInt(profile?.restartIntervalMinutes, 10);
        if (!Number.isFinite(v) || v < 0) {
            return defaultProfileSettings().restartIntervalMinutes;
        }
        return Math.min(Math.max(v, 0), 1440); // 0分钟 ~ 24小时
    }
    function setRestartIntervalMinutes(n) {
        const v = parseInt(n, 10);
        if (!Number.isFinite(v) || v < 0) return false;
        updateActiveProfileConfig({
            restartIntervalMinutes: clampNumber(
                v,
                0,
                1440,
                defaultProfileSettings().restartIntervalMinutes
            ),
        });
        return true;
    }
    async function waitForRestartInterval() {
        const minutes = getRestartIntervalMinutes();
        if (minutes > 0) {
            const ms = minutes * 60 * 1000;
            const nextTime = new Date(Date.now() + ms);
            const nextTimeStr = nextTime.toLocaleTimeString("zh-CN");
            notify(`等待下次启动间隔 ${minutes} 分钟，将在 ${nextTimeStr} 开始下一轮...`, "info");
            await sleep(ms);
        }
    }

    // 任务选择配置（默认全启用）
    function defaultTasksConfig() {
        return {
            dailyFix: true, // 一键补差
            answer: true, // 一键答题
            claimReward: true, // 领取奖励
            addClock: true, // 加钟
            signIn: true, // 立即签到
            startService: true, // 启动/重启服务
        };
    }
    function getTasksConfig() {
        const profile = getActiveProfile();
        const cfg = profile?.tasks && typeof profile.tasks === "object"
            ? profile.tasks
            : defaultTasksConfig();
        return { ...defaultTasksConfig(), ...deepClone(cfg) };
    }
    function setTasksConfig(cfg) {
        const base = defaultTasksConfig();
        const next = { ...base };
        for (const k in base) {
            next[k] = !!(cfg && typeof cfg === "object" && cfg[k]);
        }
        updateActiveProfileConfig({ tasks: next });
        return next;
    }
    function isTasksUIOpen() {
        const profile = getActiveProfile();
        return !!profile?.tasksUiOpen;
    }
    function setTasksUIOpen(v) {
        updateActiveProfileConfig({ tasksUiOpen: !!v });
    }

    // 任务进度记录
    // 状态：pending | ok | fail | skip
    function defaultTaskProgress(cfg) {
        const base = {
            dailyFix: "skip",
            answer: "skip",
            claimReward: "skip",
            addClock: "skip",
            signIn: "skip",
            startService: "skip",
        };
        const c = cfg || getTasksConfig();
        const res = { ...base };
        for (const k in base) res[k] = c[k] ? "pending" : "skip";
        return res;
    }
    function getTaskProgress() {
        const p = gmGet(TASK_PROGRESS_KEY, null);
        if (!p || typeof p !== "object") return defaultTaskProgress();
        // 兼容缺失字段
        return { ...defaultTaskProgress(), ...p };
    }
    function setTaskProgress(patch) {
        const cur = getTaskProgress();
        const next = { ...cur, ...(patch || {}) };
        gmSet(TASK_PROGRESS_KEY, next);
        try {
            updatePanel();
        } catch (_) { }
        return next;
    }
    function clearTaskProgress() {
        gmSet(TASK_PROGRESS_KEY, defaultTaskProgress());
        try {
            updatePanel();
        } catch (_) { }
    }
    function initTaskProgressFromConfig() {
        const next = defaultTaskProgress();
        gmSet(TASK_PROGRESS_KEY, next);
        return next;
    }
    function summarizeTaskProgress() {
        const p = getTaskProgress();
        const order = [
            ["dailyFix", "补差"],
            ["answer", "答题"],
            ["claimReward", "领奖"],
            ["addClock", "加钟"],
            ["signIn", "签到"],
            ["startService", "启动"],
        ];
        const icon = (s) =>
            s === "ok" ? "✓" : s === "fail" ? "✗" : s === "skip" ? "—" : "…";
        let okCnt = 0,
            totalCnt = 0;
        order.forEach(([k]) => {
            if (p[k] !== "skip") totalCnt += 1;
            if (p[k] === "ok") okCnt += 1;
        });
        const parts = order.map(([k, name]) => `${name}${icon(p[k])}`);
        return { text: parts.join(" "), okCnt, totalCnt };
    }

    function getLogs() {
        const arr = gmGet(LOG_KEY, []);
        return Array.isArray(arr) ? arr : [];
    }
    function setLogs(arr) {
        gmSet(LOG_KEY, Array.isArray(arr) ? arr : []);
    }
    function clearLogs() {
        setLogs([]);
    }

    function addLog(type, message) {
        const limit = getLogLimit();
        const list = getLogs();
        const item = {
            ts: Date.now(),
            time: nowStr(),
            type: String(type || "info"),
            message: String(message || ""),
        };
        list.push(item);
        if (list.length > limit) {
            list.splice(0, list.length - limit);
        }
        setLogs(list);
        // 实时刷新面板日志视图（若存在）
        try {
            renderLogsToPanel();
        } catch (_) { }
    }

    function notify(text, type = "info") {
        // 仅写入控制台与面板日志，不触发浏览器通知
        console.log(`[自动化][${nowStr()}] ${text}`);
        addLog(type, text);
    }

    function gmGet(key, defVal) {
        try {
            if (typeof GM_getValue === "function")
                return GM_getValue(key, defVal);
        } catch (_) { }
        try {
            const raw = localStorage.getItem("__tm__" + key);
            return raw ? JSON.parse(raw) : defVal;
        } catch (_) {
            return defVal;
        }
    }

    function gmSet(key, val) {
        try {
            if (typeof GM_setValue === "function") return GM_setValue(key, val);
        } catch (_) { }
        try {
            localStorage.setItem("__tm__" + key, JSON.stringify(val));
        } catch (_) { }
    }

    function isVisible(el) {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = getComputedStyle(el);
        return (
            rect.width > 0 &&
            rect.height > 0 &&
            style.visibility !== "hidden" &&
            style.display !== "none" &&
            style.opacity !== "0"
        );
    }

    function isClickable(el) {
        if (!el || !isVisible(el)) return false;
        const style = getComputedStyle(el);
        const tag = (el.tagName || "").toLowerCase();
        if (tag === "button") return !el.disabled;
        if (tag === "input" && /^(button|submit)$/i.test(el.type))
            return !el.disabled;
        if (tag === "a" && (el.href || el.getAttribute("href"))) return true;
        if (el.getAttribute && el.getAttribute("role") === "button")
            return true;
        if (
            el.classList &&
            (el.classList.contains("action-button") ||
                el.classList.contains("execute-button"))
        ) {
            return !el.disabled;
        }
        if (
            style.pointerEvents !== "none" &&
            (el.onclick || el.getAttribute("onclick"))
        )
            return true;
        const innerBtn = el.querySelector && el.querySelector("button");
        if (innerBtn && isVisible(innerBtn) && !innerBtn.disabled) return true;
        return false;
    }

    function findNearestClickableFrom(el, root = document.body) {
        let cur = el;
        const excludeSelector = "#automation-control-panel";
        const isInExcluded = (el2) => {
            try {
                return !!(el2 && el2.closest && el2.closest(excludeSelector));
            } catch (_) {
                return false;
            }
        };
        if (isInExcluded(cur)) return null;
        for (
            let depth = 0;
            depth < 6 && cur && cur !== root && cur !== document.body;
            depth++
        ) {
            if (isClickable(cur)) return cur;
            if (cur.querySelector) {
                const found = cur.querySelector(
                    'button, a[href], [role="button"], .action-button, .execute-button, .n-button, .n-button button'
                );
                if (found && !isInExcluded(found) && isClickable(found))
                    return found;
            }
            cur = cur.parentElement;
        }
        if (el.parentElement && !isInExcluded(el.parentElement)) {
            const candidates = el.parentElement.querySelectorAll(
                'button, a[href], [role="button"], .action-button, .execute-button, .n-button, .n-button button'
            );
            for (const c of candidates) {
                if (!isInExcluded(c) && isClickable(c)) return c;
            }
        }
        return null;
    }

    function findClickableByText(pattern, root = document.body) {
        const regex =
            pattern instanceof RegExp ? pattern : new RegExp(pattern, "i");
        const excludeSelector = "#automation-control-panel";

        const isInExcluded = (el) => {
            try {
                return !!(el && el.closest && el.closest(excludeSelector));
            } catch (_) {
                return false;
            }
        };
        const isTextNodeVisible = (nodeEl) => {
            try {
                let el = nodeEl;
                let hops = 0;
                while (el && el !== document.body && hops++ < 20) {
                    const style = getComputedStyle(el);
                    if (
                        style.display === "none" ||
                        style.visibility === "hidden" ||
                        style.opacity === "0"
                    )
                        return false;
                    el = el.parentElement;
                }
                return true;
            } catch (_) {
                return true;
            }
        };

        // 1) 按钮/可点击控件文本直配
        const clickableSelectors =
            'button, a[href], [role="button"], .action-button, .execute-button, .n-button, .n-button button';
        const direct = root.querySelectorAll(clickableSelectors);
        for (const btn of direct) {
            if (isInExcluded(btn)) continue;
            const text = (btn.innerText || btn.textContent || "").trim();
            if (regex.test(text) && isClickable(btn)) return btn;
        }

        // 2) 文本节点匹配 -> 邻近可点击控件
        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
            acceptNode(node) {
                const s = (node.nodeValue || "").trim();
                if (!s) return NodeFilter.FILTER_SKIP;
                const parent = node.parentElement;
                if (parent && isInExcluded(parent))
                    return NodeFilter.FILTER_SKIP;
                if (parent && !isTextNodeVisible(parent))
                    return NodeFilter.FILTER_SKIP;
                return regex.test(s)
                    ? NodeFilter.FILTER_ACCEPT
                    : NodeFilter.FILTER_SKIP;
            },
        });
        let node;
        while ((node = walker.nextNode())) {
            const parent = node.parentElement;
            if (isInExcluded(parent)) continue;
            const candidate = findNearestClickableFrom(parent, root);
            if (candidate) return candidate;
        }
        return null;
    }

    async function clickByText(pattern, opts = {}) {
        const { retries = 40, interval = 500, waitAfter = 600 } = opts;
        for (let i = 0; i < retries; i++) {
            if (!isRunning()) return false;
            const btn = findClickableByText(pattern);
            if (btn) {
                try {
                    btn.scrollIntoView({ behavior: "smooth", block: "center" });
                    await sleep(200 + Math.random() * 150);
                    const innerBtn =
                        btn.querySelector && btn.querySelector("button");
                    (innerBtn || btn).click();
                    await sleep(waitAfter);
                    return true;
                } catch (e) { }
            }
            await sleep(interval);
        }
        return false;
    }

    // 在限定超时时间内尝试点击文本按钮（默认5秒）
    async function clickByTextWithTimeout(
        pattern,
        timeoutMs = 5000,
        intervalMs = 250,
        waitAfterMs = 600
    ) {
        const retries = Math.max(1, Math.floor(timeoutMs / intervalMs));
        return clickByText(pattern, {
            retries,
            interval: intervalMs,
            waitAfter: waitAfterMs,
        });
    }

    function waitForText(
        pattern,
        { root = document.body, timeout = 120000 } = {}
    ) {
        const regex =
            pattern instanceof RegExp ? pattern : new RegExp(pattern, "i");
        return new Promise((resolve, reject) => {
            const initial = (
                root.innerText ||
                root.textContent ||
                ""
            ).toString();
            if (regex.test(initial)) return resolve(true);
            const obs = new MutationObserver(() => {
                const txt = (
                    root.innerText ||
                    root.textContent ||
                    ""
                ).toString();
                if (regex.test(txt)) {
                    clearTimeout(timer);
                    obs.disconnect();
                    resolve(true);
                }
            });
            obs.observe(root, {
                childList: true,
                subtree: true,
                characterData: true,
            });
            const timer = setTimeout(() => {
                obs.disconnect();
                reject(new Error("等候文本超时: " + regex));
            }, timeout);
        });
    }

    // 等待指定正则文本对应的可点击控件出现
    async function waitForClickableByText(
        pattern,
        { timeout = 120000, interval = 400 } = {}
    ) {
        const start = Date.now();
        while (Date.now() - start < timeout) {
            if (!isRunning()) return false;
            const btn = findClickableByText(pattern);
            if (btn && isClickable(btn)) return true;
            await sleep(interval);
        }
        return false;
    }

    // 等待文本（排除指定容器中的文本，例如面板日志），默认排除自动化面板
    function waitForTextExcluding(
        pattern,
        {
            root = document.body,
            timeout = 120000,
            exclude = ["#automation-control-panel"],
        } = {}
    ) {
        const regex =
            pattern instanceof RegExp ? pattern : new RegExp(pattern, "i");
        const excludeSelector = Array.isArray(exclude)
            ? exclude.join(",")
            : exclude || "";

        const hasMatch = () => {
            try {
                const walker = document.createTreeWalker(
                    root,
                    NodeFilter.SHOW_TEXT,
                    {
                        acceptNode(node) {
                            const s = (node.nodeValue || "").trim();
                            if (!s) return NodeFilter.FILTER_SKIP;
                            if (
                                excludeSelector &&
                                node.parentElement &&
                                node.parentElement.closest &&
                                node.parentElement.closest(excludeSelector)
                            ) {
                                return NodeFilter.FILTER_SKIP;
                            }
                            return regex.test(s)
                                ? NodeFilter.FILTER_ACCEPT
                                : NodeFilter.FILTER_SKIP;
                        },
                    }
                );
                return !!walker.nextNode();
            } catch (_) {
                // 兜底：如果出错则不匹配
                return false;
            }
        };

        return new Promise((resolve, reject) => {
            if (hasMatch()) return resolve(true);
            const obs = new MutationObserver(() => {
                if (hasMatch()) {
                    clearTimeout(timer);
                    obs.disconnect();
                    resolve(true);
                }
            });
            obs.observe(root, {
                childList: true,
                subtree: true,
                characterData: true,
            });
            const timer = setTimeout(() => {
                obs.disconnect();
                reject(new Error("等候文本超时: " + regex));
            }, timeout);
        });
    }

    async function waitForConnected({ timeout = 20000 } = {}) {
        try {
            await waitForText(/已连接/, { timeout });
            await sleep(800);
            return true;
        } catch {
            return false;
        }
    }

    // -------------- 状态管理 --------------
    const STATE_KEY = "automationState";
    function getState() {
        return gmGet(STATE_KEY, {
            running: false,
            total: 0,
            current: 0,
        });
    }
    function setState(s) {
        gmSet(STATE_KEY, s);
    }
    function clearState() {
        setState({ running: false, total: 0, current: 0 });
    }
    function resetQueueProgress() {
        const st = getState();
        const next = {
            ...st,
            current: 0,
            currentIndex: 0,
            currentRoleName: null,
        };
        setState(next);
        try {
            updatePanel();
        } catch (_) { }
        notify("已重置队列进度，当前索引回到 0", "success");
    }
    function isRunning() {
        try {
            const s = getState();
            return !!(s && s.running);
        } catch (_) {
            return false;
        }
    }
    function updateState(patch) {
        try {
            const cur = getState() || {};
            const next = { ...cur, ...patch };
            setState(next);
            return next;
        } catch (_) {
            setState(patch || {});
            return patch || {};
        }
    }

    // -------------- 页面判断 --------------
    function isTokensPage() {
        return location.pathname.replace(/\/+$/, "") === "/tokens";
    }
    function isFeaturesPage() {
        return location.pathname.replace(/\/+$/, "") === "/game-features";
    }

    // -------------- tokens 页：角色卡片 --------------
    function getTokenCards() {
        return Array.from(document.querySelectorAll(".token-card"));
    }

    function probeCurrentRoleNameFromDom() {
        try {
            const el = document.querySelector(".page-subtitle");
            const t = (el?.innerText || "").trim();
            if (t && !/未选择Token/.test(t)) return t;
        } catch (_) { }
        return null;
    }

    function getCardName(card) {
        try {
            return (
                card.querySelector(".token-name")?.innerText ||
                card.innerText ||
                ""
            ).trim();
        } catch (_) {
            return "";
        }
    }

    async function waitForActiveCardByName(
        targetName,
        { timeout = 8000 } = {}
    ) {
        const start = Date.now();
        while (Date.now() - start < timeout) {
            if (!isRunning()) return false;
            const active = document.querySelector(
                ".token-card.active .token-name"
            );
            const name = (active?.innerText || "").trim();
            if (name && targetName && name === targetName) return true;
            await sleep(200);
        }
        return false;
    }
    async function waitForTokenCards({ timeout = 15000 } = {}) {
        const start = Date.now();
        while (Date.now() - start < timeout) {
            if (!isRunning()) return [];
            const cards = getTokenCards();
            if (cards.length > 0) return cards;
            await sleep(300);
        }
        return [];
    }

    async function processNextOnTokensPage() {
        const st = getState();
        if (!st.running) return;

        const cards = await waitForTokenCards();
        if (!isRunning()) return;
        // 持久化总数到状态，便于面板回显
        if (cards && cards.length) updateState({ total: cards.length });
        if (!cards.length) {
            notify("未找到任何角色卡片，停止。", "error");
            clearState();
            return;
        }
        if (st.current >= cards.length) {
            notify("所有角色已处理完成，稍后将重新开始下一轮。", "success");
            const loopDelay = Math.max(0, getActionDelayMs());
            updateState({
                running: true,
                total: cards.length,
                current: 0,
                currentIndex: 0,
                currentRoleName: null,
            });
            try {
                updatePanel();
            } catch (_) { }
            if (!isRunning()) return;
            // 应用下次启动间隔延迟
            await waitForRestartInterval();
            if (!isRunning()) return;
            if (loopDelay > 0) await sleep(loopDelay);
            if (!isRunning()) return;
            return processNextOnTokensPage();
        }

        const card = cards[st.current];
        try {
            if (!isRunning()) return;
            const targetName = getCardName(card);
            updateState({
                currentRoleName: targetName,
                currentIndex: st.current,
            });
            card.scrollIntoView({ behavior: "smooth", block: "center" });
            await sleep(200);
            if (!isRunning()) return;
            card.click();
            await sleep(600);
            // 等待该卡片成为 active，确保已选中正确角色
            const ok = await waitForActiveCardByName(targetName, {
                timeout: 8000,
            });
            if (!ok) {
                // 再尝试一次点击
                notify(
                    `未检测到已选中目标卡片「${targetName}」，重试点击一次...`,
                    "warning"
                );
                card.click();
                await sleep(800);
                await waitForActiveCardByName(targetName, { timeout: 5000 });
            }
        } catch (e) {
            notify(
                `点击第 ${st.current + 1} 个角色失败，尝试继续。`,
                "warning"
            );
        }

        if (!isRunning()) return;
        notify(
            `前往任务页处理：第 ${st.current + 1}/${cards.length} 个角色`,
            "info"
        );
        await sleep(500);
        if (isRunning()) location.assign(`${location.origin}/game-features`);
    }

    // -------------- 任务页：执行流程 --------------
    async function runTaskFlowOnFeaturesPage() {
        const st = getState();
        if (!st.running) return;

        await waitForConnected({ timeout: 20000 });
        if (!isRunning()) return;

        const tasks = getTasksConfig();

        // 初始化本角色任务进度
        initTaskProgressFromConfig();

        // 尝试从页面头部回显当前角色名称
        if (!st.currentRoleName) {
            const n = probeCurrentRoleNameFromDom();
            if (n) updateState({ currentRoleName: n });
        }

        // 1. 一键补差（点击后等待日志：最终角色信息刷新完成；排除面板日志）
        if (tasks.dailyFix) {
            if (!isRunning()) return;
            notify("开始：一键补差", "info");
            const fixClicked = await clickByText(/一键补差/);
            if (fixClicked) {
                // 点击后一秒再开始任务“停止”检查与按钮恢复检查
                await sleep(1000);
                if (!isRunning()) return;
                let ok = false;
                try {
                    await waitForTextExcluding(/最终角色信息刷新完成/, {
                timeout: 18000,
                exclude: ["#automation-control-panel"],
            });
                    ok = true;
                } catch (e) {
                    ok = false;
                }
                if (ok) {
                    notify(
                        "一键补差完成（检测到“最终角色信息刷新完成”）",
                        "success"
                    );
                    setTaskProgress({ dailyFix: "ok" });
                } else {
                    notify(
                        "等待“最终角色信息刷新完成”超时，继续后续步骤。",
                        "warning"
                    );
                    // 已经点击但未检测到完成，也视为已尝试成功
                    setTaskProgress({ dailyFix: "ok" });
                }
            } else {
                notify("未找到“一键补差”按钮，继续后续步骤。", "warning");
                setTaskProgress({ dailyFix: "fail" });
            }
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ dailyFix: "skip" });
        }

        // 2. 一键答题
        if (tasks.answer) {
            if (!isRunning()) return;
            notify("尝试：一键答题", "info");
            const ok = await clickByTextWithTimeout(/一键答题/, 2000, 250, 300);
            setTaskProgress({ answer: ok ? "ok" : "fail" });
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ answer: "skip" });
        }

        // 3. 领取奖励
        if (tasks.claimReward) {
            if (!isRunning()) return;
            notify("尝试：领取奖励", "info");
            const ok = await clickByTextWithTimeout(/领取奖励/, 2000, 250, 300);
            setTaskProgress({ claimReward: ok ? "ok" : "fail" });
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ claimReward: "skip" });
        }

        // 4. 加钟
        if (tasks.addClock) {
            if (!isRunning()) return;
            notify("尝试：加钟", "info");
            const ok = await clickByTextWithTimeout(/加钟/, 2000, 250, 300);
            setTaskProgress({ addClock: ok ? "ok" : "fail" });
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ addClock: "skip" });
        }

        // 5. 立即签到
        if (tasks.signIn) {
            if (!isRunning()) return;
            // 检查是否已签到
            const alreadySignedIn = findClickableByText(/已签到/);
            if (alreadySignedIn) {
                notify("检测到已签到，跳过立即签到任务", "info");
                setTaskProgress({ signIn: "skip" });
            } else {
                notify("尝试：立即签到", "info");
                const ok = await clickByTextWithTimeout(/立即签到/, 2000, 250, 300);
                setTaskProgress({ signIn: ok ? "ok" : "fail" });
            }
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ signIn: "skip" });
        }

        // 6. 启动/重启服务
        if (tasks.startService) {
            if (!isRunning()) return;
            notify("尝试：启动/重启服务", "info");
            const ok = await clickByTextWithTimeout(
                /启动服务|重启服务/,
                2000,
                250,
                300
            );
            setTaskProgress({ startService: ok ? "ok" : "fail" });
            await waitBetweenSteps();
            if (!isRunning()) return;
        } else {
            setTaskProgress({ startService: "skip" });
        }

        // 末尾再等待一次，确保后续状态稳定
        await waitBetweenSteps();
        if (!isRunning()) return;

        const nextState = updateState({ current: st.current + 1 });

        notify(
            `当前角色处理完成，返回 Token 管理页（下一位：${(nextState.current ?? st.current) + 1
            }/${nextState.total ?? "-"}）`,
            "info"
        );
        // 根据需求：跳转到 tokens 页前固定等待 2 秒
        if (!isRunning()) return;
        await sleep(PAGE_READY_DELAY_MS);
        if (!isRunning()) return;
        location.assign(`${location.origin}/tokens`);
    }

    // -------------- 启停与菜单 --------------
    function startAllRoles() {
        const cards = getTokenCards();
        const total = cards.length;
        if (!isTokensPage() || total === 0) {
            notify("请在 Token 管理页启动，且确保已有角色！", "warning");
            return;
        }
        const prev = getState() || {};
        let current = Number.isFinite(prev.current) ? prev.current : 0;
        // 边界保护：若上次索引超出范围，则从 0 开始
        if (total <= 0) current = 0;
        else if (current >= total) current = 0;
        updateState({
            running: true,
            total,
            current,
            currentRoleName: null,
            currentIndex: current,
        });
        notify(
            `开始循环自动做任务，共 ${total} 个角色（从第 ${current + 1
            } 个继续）`,
            "info"
        );
        processNextOnTokensPage();
    }

    function stopAll() {
        // 仅停止运行，保留队列与任务进度
        updateState({ running: false });
        notify("已停止（保留队列进度）。", "info");
    }

    // 暂停（与停止一致：不清空进度，仅置为非运行态）
    function pauseRun() {
        updateState({ running: false });
        notify("已暂停（保留队列进度）。", "info");
        try {
            updatePanel();
        } catch (_) { }
    }

    // 恢复（从上次队列索引继续）
    function resumeRun() {
        updateState({ running: true });
        notify("已恢复（从上次进度继续）。", "success");
        try {
            updatePanel();
        } catch (_) { }
        if (isTokensPage()) {
            startAllRoles();
        } else {
            location.assign(`${location.origin}/tokens`);
        }
    }

    try {
        GM_registerMenuCommand("开始：循环所有角色自动做任务", startAllRoles);
        GM_registerMenuCommand("停止：暂停运行", stopAll);
        GM_registerMenuCommand("设置日志最大行数", () => {
            const cur = getLogLimit();
            const input = prompt("输入日志最大行数（20~2000）", String(cur));
            if (input == null) return;
            if (setLogLimit(input)) {
                notify("日志最大行数已更新为 " + getLogLimit(), "success");
            } else {
                notify("输入无效，未更新日志行数", "warning");
            }
        });
        GM_registerMenuCommand("清空运行日志", () => {
            clearLogs();
            renderLogsToPanel();
            notify("已清空运行日志", "success");
        });
        GM_registerMenuCommand("设置动作等待时间(ms)", () => {
            const cur = getActionDelayMs();
            const input = prompt(
                "输入每步动作之间的等待时间（毫秒，0~600000）",
                String(cur)
            );
            if (input == null) return;
            if (setActionDelayMs(input)) {
                notify(
                    "动作等待时间已更新为 " + getActionDelayMs() + "ms",
                    "success"
                );
                updatePanel();
            } else {
                notify("输入无效，未更新动作等待时间", "warning");
            }
        });
        GM_registerMenuCommand("设置下次启动间隔(分钟)", () => {
            const cur = getRestartIntervalMinutes();
            const input = prompt(
                "输入下次启动间隔时间（分钟，0~1440）",
                String(cur)
            );
            if (input == null) return;
            if (setRestartIntervalMinutes(input)) {
                notify(
                    "下次启动间隔已更新为 " + getRestartIntervalMinutes() + " 分钟",
                    "success"
                );
                updatePanel();
            } else {
                notify("输入无效，未更新下次启动间隔", "warning");
            }
        });
        GM_registerMenuCommand("配置管理", openConfigManager);
    } catch (_) { }

    // -------------- 管理面板（可折叠，10px 字体） --------------
    const PANEL_ID = "automation-control-panel";
    const PANEL_COLLAPSE_KEY = "automation_panel_collapsed";

    function ensurePanelStyles() {
        const css = `
      #${PANEL_ID} { position: fixed; right: 12px; bottom: 12px; z-index: 999999; font-size: 10px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial; color: #111; }
      #${PANEL_ID} .panel-wrap { background: rgba(255,255,255,.98); border: 1px solid #e5e7eb; box-shadow: 0 6px 18px rgba(0,0,0,.12); border-radius: 8px; overflow: hidden; min-width: 200px; }
      #${PANEL_ID} .panel-header { display:flex; align-items:center; justify-content:space-between; gap:6px; padding:6px 8px; background:#f3f4f6; border-bottom:1px solid #e5e7eb; }
      #${PANEL_ID} .panel-title { font-weight:600; }
      #${PANEL_ID} .panel-actions { display:flex; align-items:center; gap:6px; }
      #${PANEL_ID} button { font-size:10px; line-height:1; padding:4px 6px; border-radius:4px; border:1px solid #d1d5db; background:#fff; cursor:pointer; }
      #${PANEL_ID} button:hover { background:#f9fafb; }
      #${PANEL_ID} .collapse-btn { color:#374151; }
      #${PANEL_ID} .panel-body { padding:8px; display:grid; gap:6px; }
      #${PANEL_ID} .row { display:flex; align-items:center; justify-content:space-between; gap:8px; }
      #${PANEL_ID} .label { color:#6b7280; }
      #${PANEL_ID} .value { color:#111827; font-weight:600; }
      #${PANEL_ID} .tasks-config { display:grid; grid-template-columns: 1fr 1fr; gap:4px 8px; padding:6px; border:1px dashed #d1d5db; border-radius:6px; background:#fff; }
      #${PANEL_ID} .tasks-config label { display:flex; align-items:center; gap:6px; font-weight:500; }
      #${PANEL_ID} .tasks-actions { grid-column: 1 / -1; display:flex; gap:6px; justify-content:flex-end; margin-top:4px; }
      #${PANEL_ID} .config-manager { display:grid; gap:6px; padding:6px; border:1px dashed #d1d5db; border-radius:6px; background:#fff; }
      #${PANEL_ID} .config-list { display:flex; flex-direction:column; gap:4px; max-height:180px; overflow:auto; }
      #${PANEL_ID} .config-item { border:1px solid #e5e7eb; border-radius:6px; padding:6px; background:#fff; display:flex; flex-direction:column; gap:4px; }
      #${PANEL_ID} .config-item.active { border-color:#2563eb; box-shadow:0 0 0 1px rgba(37,99,235,.35); }
      #${PANEL_ID} .config-item-header { display:flex; align-items:center; justify-content:space-between; gap:6px; font-weight:600; }
      #${PANEL_ID} .config-item-meta { font-size:9px; color:#6b7280; }
      #${PANEL_ID} .config-item-buttons { display:flex; gap:4px; flex-wrap:wrap; }
      #${PANEL_ID} .config-item-buttons button { flex:0 0 auto; }
      #${PANEL_ID} .config-actions { display:flex; gap:6px; justify-content:flex-end; flex-wrap:wrap; }
      #${PANEL_ID} .config-badge { font-size:9px; color:#2563eb; background:rgba(37,99,235,.12); padding:2px 4px; border-radius:4px; }
      #${PANEL_ID} .config-empty { font-size:10px; color:#6b7280; text-align:center; padding:6px 0; }
      #${PANEL_ID} .log-box { max-height: 200px; overflow:auto; background:#f9fafb; border:1px solid #e5e7eb; border-radius:6px; padding:6px; font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace; line-height:1.3; }
      #${PANEL_ID} .log-item { margin:1px 0; white-space: pre-wrap; word-break: break-word; }
      #${PANEL_ID} .log-item.info { color:#111827; }
      #${PANEL_ID} .log-item.warn, #${PANEL_ID} .log-item.warning { color:#b45309; }
      #${PANEL_ID} .log-item.error { color:#b91c1c; }
      #${PANEL_ID} .log-item.success, #${PANEL_ID} .log-item.ok { color:#065f46; }
      @media (prefers-color-scheme: dark) {
        #${PANEL_ID} .panel-wrap { background: rgba(31,41,55,.98); border-color:#374151; color:#e5e7eb; }
        #${PANEL_ID} .panel-header { background:#111827; border-color:#374151; }
        #${PANEL_ID} .label { color:#9ca3af; }
        #${PANEL_ID} .value { color:#e5e7eb; }
        #${PANEL_ID} button { background:#111827; color:#e5e7eb; border-color:#374151; }
        #${PANEL_ID} button:hover { background:#1f2937; }
        #${PANEL_ID} .tasks-config { background:#0b1220; border-color:#374151; }
        #${PANEL_ID} .config-manager { background:#0b1220; border-color:#374151; }
        #${PANEL_ID} .config-item { background:#111827; border-color:#374151; }
        #${PANEL_ID} .config-item.active { border-color:#3b82f6; box-shadow:0 0 0 1px rgba(59,130,246,.35); }
        #${PANEL_ID} .config-item-meta { color:#9ca3af; }
        #${PANEL_ID} .config-empty { color:#9ca3af; }
        #${PANEL_ID} .config-badge { color:#bfdbfe; background:rgba(59,130,246,.25); }
        #${PANEL_ID} .log-box { background:#111827; border-color:#374151; }
      }
    `;
        try {
            if (typeof GM_addStyle === "function") GM_addStyle(css);
        } catch (_) {
            const style = document.createElement("style");
            style.textContent = css;
            document.head.appendChild(style);
        }
    }

    function getCollapsed() {
        return !!gmGet(PANEL_COLLAPSE_KEY, false);
    }
    function setCollapsed(v) {
        gmSet(PANEL_COLLAPSE_KEY, !!v);
    }

    function currentPageTag() {
        if (isTokensPage()) return "tokens";
        if (isFeaturesPage()) return "game-features";
        return location.pathname.replace(/\/+$/, "") || "/";
    }

    function renderPanel() {
        ensurePanelStyles();
        let root = document.getElementById(PANEL_ID);
        if (!root) {
            root = document.createElement("div");
            root.id = PANEL_ID;
            root.innerHTML = `
        <div class="panel-wrap">
          <div class="panel-header">
            <div class="panel-title">自动化控制面板</div>
            <div class="panel-actions">
              <button type="button" class="collapse-btn">折叠</button>
            </div>
          </div>
            <div class="panel-body">
            <div class="row"><span class="label">状态</span><span class="value" data-field="status">-</span></div>
            <div class="row"><span class="label">配置</span><span class="value" data-field="profile">-</span></div>
            <div class="row"><span class="label">队列</span><span class="value" data-field="queue">0/0</span></div>
            <div class="row"><span class="label">页面</span><span class="value" data-field="page">-</span></div>
            <div class="row"><span class="label">当前角色</span><span class="value" data-field="role">-</span></div>
            <div class="row"><span class="label">任务进度</span><span class="value" data-field="task-progress">-</span></div>
            <div class="row"><span class="label">动作等待(ms)</span><span class="value" data-field="delay">-</span></div>
            <div class="row"><span class="label">下次启动间隔(分钟)</span><span class="value" data-field="restart-interval">-</span></div>
            <div class="row"><span class="label">日志行数</span><span class="value" data-field="limit">-</span></div>
            <div class="row"><span class="label">任务选择</span>
              <span class="value" data-field="tasks-summary">-</span>
            </div>
            <div class="tasks-config" data-field="tasks" style="display:none;">
              <label><input type="checkbox" data-task="dailyFix"> 一键补差</label>
              <label><input type="checkbox" data-task="answer"> 一键答题</label>
              <label><input type="checkbox" data-task="claimReward"> 领取奖励</label>
              <label><input type="checkbox" data-task="addClock"> 加钟</label>
              <label><input type="checkbox" data-task="signIn"> 立即签到</label>
              <label><input type="checkbox" data-task="startService"> 启动/重启服务</label>
              <div class="tasks-actions">
                <button type="button" data-action="tasks-all">全选</button>
                <button type="button" data-action="tasks-none">全不选</button>
                <button type="button" data-action="tasks-toggle">收起</button>
              </div>
            </div>
            <div class="config-manager" data-field="configs" style="display:none;">
              <div class="config-list" data-field="config-list"></div>
              <div class="config-actions">
                <button type="button" data-action="config-create">新建配置</button>
                <button type="button" data-action="config-close">收起</button>
              </div>
            </div>
            <div class="row" style="gap:6px; justify-content:flex-end;">
              <button type="button" data-action="start">开始</button>
              <button type="button" data-action="pause">暂停</button>
              <button type="button" data-action="stop">停止</button>
            </div>
            <div class="row" style="gap:6px;">
              <button type="button" data-action="set-delay">设置等待</button>
              <button type="button" data-action="set-restart-interval">设置间隔</button>
              <button type="button" data-action="set-limit">设置行数</button>
              <button type="button" data-action="clear-logs">清空日志</button>
              <button type="button" data-action="open-tasks">设置任务</button>
              <button type="button" data-action="open-config">配置管理</button>
            </div>
            <div class="row" style="gap:6px;">
              <button type="button" data-action="reset-queue">清除队列进度</button>
              <button type="button" data-action="clear-task-progress">清除任务进度</button>
            </div>
            <div class="log-box" data-field="logs"></div>
          </div>
        </div>`;
            document.body.appendChild(root);

            // 事件绑定
            const startBtn = root.querySelector('button[data-action="start"]');
            const stopBtn = root.querySelector('button[data-action="stop"]');
            const pauseBtn = root.querySelector('button[data-action="pause"]');
            const collapseBtn = root.querySelector(".collapse-btn");
            const setLimitBtn = root.querySelector(
                'button[data-action="set-limit"]'
            );
            const setDelayBtn = root.querySelector(
                'button[data-action="set-delay"]'
            );
            const setRestartIntervalBtn = root.querySelector(
                'button[data-action="set-restart-interval"]'
            );
            const clearLogsBtn = root.querySelector(
                'button[data-action="clear-logs"]'
            );
            const openTasksBtn = root.querySelector(
                'button[data-action="open-tasks"]'
            );
            const openConfigBtn = root.querySelector(
                'button[data-action="open-config"]'
            );
            const configCreateBtn = root.querySelector(
                'button[data-action="config-create"]'
            );
            const configCloseBtn = root.querySelector(
                'button[data-action="config-close"]'
            );
            const configListEl = root.querySelector('[data-field="config-list"]');
            const resetQueueBtn = root.querySelector(
                'button[data-action="reset-queue"]'
            );
            const clearTaskProgBtn = root.querySelector(
                'button[data-action="clear-task-progress"]'
            );
            const tasksBox = root.querySelector('[data-field="tasks"]');
            const tasksAllBtn = root.querySelector(
                'button[data-action="tasks-all"]'
            );
            const tasksNoneBtn = root.querySelector(
                'button[data-action="tasks-none"]'
            );
            const tasksToggleBtn = root.querySelector(
                'button[data-action="tasks-toggle"]'
            );
            startBtn.addEventListener("click", () => {
                // 允许任意页面启动：若不在 tokens 页则先设置状态后跳转
                if (!isTokensPage()) {
                    const cardsHint = "（将跳转到 tokens 页）";
                    // 保持上次队列进度，仅设置为运行中
                    updateState({ running: true });
                    notify("已开始 " + cardsHint, "info");
                    location.assign(`${location.origin}/tokens`);
                } else {
                    startAllRoles();
                }
            });
            stopBtn.addEventListener("click", () => stopAll());
            pauseBtn.addEventListener("click", () => {
                const s = getState();
                if (s && s.running) {
                    pauseRun();
                } else {
                    resumeRun();
                }
            });
            collapseBtn.addEventListener("click", toggleCollapse);
            setDelayBtn.addEventListener("click", () => {
                const cur = getActionDelayMs();
                const input = prompt(
                    "输入每步动作之间的等待时间（毫秒，0~600000）",
                    String(cur)
                );
                if (input == null) return;
                if (setActionDelayMs(input)) {
                    notify(
                        "动作等待时间已更新为 " + getActionDelayMs() + "ms",
                        "success"
                    );
                    updatePanel();
                } else {
                    notify("输入无效，未更新动作等待时间", "warning");
                }
            });
            setRestartIntervalBtn.addEventListener("click", () => {
                const cur = getRestartIntervalMinutes();
                const input = prompt(
                    "输入下次启动间隔时间（分钟，0~1440）",
                    String(cur)
                );
                if (input == null) return;
                if (setRestartIntervalMinutes(input)) {
                    notify(
                        "下次启动间隔已更新为 " + getRestartIntervalMinutes() + " 分钟",
                        "success"
                    );
                    updatePanel();
                } else {
                    notify("输入无效，未更新下次启动间隔", "warning");
                }
            });
            setLimitBtn.addEventListener("click", () => {
                const cur = getLogLimit();
                const input = prompt(
                    "输入日志最大行数（20~2000）",
                    String(cur)
                );
                if (input == null) return;
                if (setLogLimit(input)) {
                    notify("日志最大行数已更新为 " + getLogLimit(), "success");
                    updatePanel();
                } else {
                    notify("输入无效，未更新日志行数", "warning");
                }
            });
            clearLogsBtn.addEventListener("click", () => {
                clearLogs();
                renderLogsToPanel();
                notify("已清空运行日志", "success");
            });
            resetQueueBtn.addEventListener("click", () => {
                resetQueueProgress();
            });
            clearTaskProgBtn.addEventListener("click", () => {
                clearTaskProgress();
                notify("已清空任务进度", "success");
            });

            // 任务选择 UI
            openTasksBtn.addEventListener("click", () => {
                const open = tasksBox.style.display !== "none";
                const nextOpen = !open;
                tasksBox.style.display = nextOpen ? "grid" : "none";
                setTasksUIOpen(nextOpen);
                // 初始化勾选状态
                if (nextOpen) syncTasksUI();
            });
            if (openConfigBtn && !openConfigBtn.dataset.listenerAttached) {
                openConfigBtn.dataset.listenerAttached = "true";
                openConfigBtn.addEventListener("click", () => {
                    toggleConfigManagerPanel();
                });
            }
            if (configCreateBtn && !configCreateBtn.dataset.listenerAttached) {
                configCreateBtn.dataset.listenerAttached = "true";
                configCreateBtn.addEventListener("click", () => {
                    handleConfigCreate();
                });
            }
            if (configCloseBtn && !configCloseBtn.dataset.listenerAttached) {
                configCloseBtn.dataset.listenerAttached = "true";
                configCloseBtn.addEventListener("click", () => {
                    setConfigManagerOpen(false);
                    updatePanel();
                });
            }
            if (configListEl && !configListEl.dataset.listenerAttached) {
                configListEl.dataset.listenerAttached = "true";
                configListEl.addEventListener("click", (event) => {
                    const targetButton = event.target.closest('button[data-config-action]');
                    if (targetButton) {
                        event.preventDefault();
                        event.stopPropagation();
                        const action = targetButton.dataset.configAction;
                        const profileName = targetButton.dataset.profileName;
                        if (!profileName) return;
                        if (action === "use") {
                            handleConfigUse(profileName);
                        } else if (action === "rename") {
                            handleConfigRename(profileName);
                        } else if (action === "delete") {
                            handleConfigDelete(profileName);
                        }
                        return;
                    }
                    const item = event.target.closest('[data-profile-name]');
                    if (item) {
                        const profileName = item.dataset.profileName;
                        if (profileName) {
                            handleConfigUse(profileName);
                        }
                    }
                });
            }
            tasksAllBtn.addEventListener("click", () => {
                const cfg = setTasksConfig({
                    dailyFix: true,
                    answer: true,
                    claimReward: true,
                    addClock: true,
                    signIn: true,
                    startService: true,
                });
                syncTasksUI(cfg);
                updatePanel();
                notify("已全选所有任务", "success");
            });
            tasksNoneBtn.addEventListener("click", () => {
                const cfg = setTasksConfig({
                    dailyFix: false,
                    answer: false,
                    claimReward: false,
                    addClock: false,
                    signIn: false,
                    startService: false,
                });
                syncTasksUI(cfg);
                updatePanel();
                notify("已取消所有任务", "success");
            });
            tasksToggleBtn.addEventListener("click", () => {
                const open = tasksBox.style.display !== "none";
                const nextOpen = !open;
                tasksBox.style.display = nextOpen ? "grid" : "none";
                setTasksUIOpen(nextOpen);
            });

            // 复选框勾选事件（即改即存）
            tasksBox
                .querySelectorAll('input[type="checkbox"]')
                .forEach((cb) => {
                    cb.addEventListener("change", () => {
                        const cfg = getTasksConfig();
                        const key = cb.getAttribute("data-task");
                        cfg[key] = cb.checked;
                        setTasksConfig(cfg);
                        updatePanel();
                        notify("任务选择已更新", "info");
                    });
                });
        }

        // 折叠状态
        applyCollapse();
        updatePanel();
    }

    function applyCollapse() {
        const root = document.getElementById(PANEL_ID);
        if (!root) return;
        const body = root.querySelector(".panel-body");
        const btn = root.querySelector(".collapse-btn");
        const collapsed = getCollapsed();
        if (collapsed) {
            body.style.display = "none";
            btn.textContent = "展开";
        } else {
            body.style.display = "grid";
            btn.textContent = "折叠";
        }
    }

    function toggleCollapse() {
        setCollapsed(!getCollapsed());
        applyCollapse();
    }

    function updatePanel() {
        const root = document.getElementById(PANEL_ID);
        if (!root) return;
        // 强制按照持久化的折叠状态渲染，避免被外部样式或渲染干扰导致“自动折叠”
        applyCollapse();
        const st = getState();
        const tasksCfg = getTasksConfig();
        const statusEl = root.querySelector('[data-field="status"]');
        const profileEl = root.querySelector('[data-field="profile"]');
        const queueEl = root.querySelector('[data-field="queue"]');
        const pageEl = root.querySelector('[data-field="page"]');
        const roleEl = root.querySelector('[data-field="role"]');
        const taskProgEl = root.querySelector('[data-field="task-progress"]');
        const delayEl = root.querySelector('[data-field="delay"]');
        const restartIntervalEl = root.querySelector('[data-field="restart-interval"]');
        const limitEl = root.querySelector('[data-field="limit"]');
        const tasksSummaryEl = root.querySelector(
            '[data-field="tasks-summary"]'
        );
        const tasksBox = root.querySelector('[data-field="tasks"]');

        const statusText = st.running ? "运行中" : "空闲";
        statusEl.textContent = statusText;
        if (profileEl) profileEl.textContent = getActiveProfileName();
        queueEl.textContent = `${Math.min(st.current, st.total)}/${st.total}`;
        pageEl.textContent = currentPageTag();
        if (roleEl)
            roleEl.textContent =
                (st.currentRoleName || "-") +
                (Number.isFinite(st.currentIndex)
                    ? ` (#${st.currentIndex + 1})`
                    : "");
        if (taskProgEl) {
            const { text, okCnt, totalCnt } = summarizeTaskProgress();
            taskProgEl.textContent =
                totalCnt > 0 ? `${text}（${okCnt}/${totalCnt}）` : text;
        }
        if (delayEl) delayEl.textContent = String(getActionDelayMs());
        if (restartIntervalEl) restartIntervalEl.textContent = String(getRestartIntervalMinutes());
        if (limitEl) limitEl.textContent = String(getLogLimit());
        if (tasksSummaryEl) {
            const names = [];
            if (tasksCfg.dailyFix) names.push("补差");
            if (tasksCfg.answer) names.push("答题");
            if (tasksCfg.claimReward) names.push("领奖");
            if (tasksCfg.addClock) names.push("加钟");
            if (tasksCfg.signIn) names.push("签到");
            if (tasksCfg.startService) names.push("启动");
            tasksSummaryEl.textContent = names.length
                ? names.join("、")
                : "未选择";
        }
        // 同步 UI 勾选与展开状态
        if (tasksBox) {
            tasksBox.style.display = isTasksUIOpen() ? "grid" : "none";
            syncTasksUI(tasksCfg);
        }
        const configBox = root.querySelector('[data-field="configs"]');
        const configListEl = root.querySelector('[data-field="config-list"]');
        const openConfigBtn = root.querySelector('button[data-action="open-config"]');
        if (configBox) {
            configBox.style.display = isConfigManagerOpen() ? "grid" : "none";
            if (isConfigManagerOpen() && configListEl) {
                renderConfigList(configListEl);
            }
        }
        if (openConfigBtn) {
            openConfigBtn.textContent = isConfigManagerOpen() ? "收起配置" : "配置管理";
        }
        // 更新暂停按钮的文案：运行中->“暂停”，非运行->“恢复”
        const pauseBtn = root.querySelector('button[data-action="pause"]');
        if (pauseBtn) {
            pauseBtn.textContent = st.running ? "暂停" : "恢复";
        }
        renderLogsToPanel();
    }

    function syncTasksUI(cfg) {
        const root = document.getElementById(PANEL_ID);
        if (!root) return;
        const tasks = cfg || getTasksConfig();
        root.querySelectorAll(
            '[data-field="tasks"] input[type="checkbox"]'
        ).forEach((cb) => {
            const key = cb.getAttribute("data-task");
            cb.checked = !!tasks[key];
        });
    }

    // 初始化面板与定时刷新
    function initPanel() {
        renderPanel();
        // 定时刷新显示数据
        setInterval(updatePanel, 1000);
    }

    // 渲染日志到面板
    function renderLogsToPanel() {
        const root = document.getElementById(PANEL_ID);
        if (!root) return;
        const box = root.querySelector('[data-field="logs"]');
        if (!box) return;
        const logs = getLogs();
        const esc = (s) =>
            String(s)
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;");
        const typeLabel = (t) =>
        ({
            info: "INFO",
            warning: "WARN",
            warn: "WARN",
            error: "ERROR",
            success: "OK",
        }[t] || String(t || "").toUpperCase());
        const html = logs
            .map(
                (l) =>
                    `<div class="log-item ${esc(l.type)}">${esc(l.time)} [${esc(
                        typeLabel(l.type)
                    )}] ${esc(l.message)}</div>`
            )
            .join("");
        if (box.innerHTML !== html) {
            const atBottom =
                box.scrollTop + box.clientHeight >= box.scrollHeight - 4;
            box.innerHTML = html || '<div class="log-item">（无日志）</div>';
            if (atBottom) {
                box.scrollTop = box.scrollHeight;
            }
        }
    }

    // -------------- 页面入口 --------------
    (async function main() {
        initPanel();
        await sleep(PAGE_READY_DELAY_MS);
        if (isTokensPage()) {
            if (getState().running) {
                await processNextOnTokensPage();
            }
        }
        if (isFeaturesPage()) {
            if (getState().running) {
                await runTaskFlowOnFeaturesPage();
            }
        }
    })();
})();
