import { t, resolveLocale } from "../i18n/index.js";
import { runContextRefresh } from "./context-refresh.js";
import { runGracefulShutdown } from "./state.js";
import { saveOwnerConfig, loadConfig, saveConfig, buildPricingUrl, writePendingRestart } from "../config/index.js";
import { execSudo } from "../utils/exec.js";
import { mkdir, stat } from "node:fs/promises";
import path from "node:path";
import { tmpdir } from "node:os";
import { logger } from "../logger.js";
import { buildHelpText, buildStatusReport } from "./status.js";
import { formatSubagentLine } from "./activity-tracker.js";
import { DEFAULT_BASE_URL as WEIXIN_DEFAULT_BASE_URL, saveWeixinAccount, registerWeixinAccountId, } from "@tencent-weixin/openclaw-weixin/src/auth/accounts.js";
import { startWeixinLoginWithQr, waitForWeixinLogin, } from "@tencent-weixin/openclaw-weixin/src/auth/login-qr.js";
import { normalizeAccountId } from "openclaw/plugin-sdk";
import { scheduleRestart } from "../utils/restart.js";
/**
 * Register all slash-command event handlers on the channel manager.
 * These handle /stop, /sleep, /console, /restart,
 * /tunnel, /help, and /status — responding directly without entering
 * the message queue.
 */
export function registerCommandHandlers(deps) {
    const { agentState, channelManager, messageQueue, session, config, ownerConfig, heartbeat, wakeMainLoop } = deps;
    const pricingUrl = buildPricingUrl(config);
    const getLocale = () => resolveLocale(ownerConfig.language);
    channelManager.on("stop_request", (req) => {
        if (agentState.busy && !agentState.stopRequested) {
            logger.system(`Agent stop requested via ${req.channel} by ${req.sender}`);
            // Enqueue a system message so the agent sees it at the start of the next wake cycle.
            messageQueue.enqueue({
                id: `stop_cmd_${Date.now()}`,
                channel: "system",
                sender: "system",
                text: "User manually triggered stop command, do NOT work on the task anymore.",
                attachments: [],
                timestamp: new Date().toISOString(),
            });
            agentState.requestStop();
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.stop.sent", getLocale()))
                .catch((err) => {
                logger.warn(`Failed to send stop confirmation: ${err instanceof Error ? err.message : String(err)}`);
            });
        }
        else {
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.stop.not_working", getLocale()))
                .catch((err) => {
                logger.warn(`Failed to send stop reply: ${err instanceof Error ? err.message : String(err)}`);
            });
        }
    });
    channelManager.on("sleep_request", (req) => {
        const minutes = Math.max(1, Math.min(1440, req.minutes));
        const oldMs = ownerConfig.heartbeatIntervalMs;
        ownerConfig.heartbeatIntervalMs = minutes * 60 * 1000;
        saveOwnerConfig(ownerConfig);
        logger.system(`Sleep interval changed from ${oldMs / 60000}m to ${minutes}m by ${req.sender} via ${req.channel}`);
        channelManager
            .sendMessage(req.channel, req.sender, t("cmd.sleep.changed", getLocale(), String(oldMs / 60000), String(minutes)))
            .catch((err) => {
            logger.warn(`Failed to send sleep confirmation: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("remote_desktop_request", (req) => {
        logger.system(`RDP link requested by ${req.sender} via ${req.channel}`);
        const doRdp = async () => {
            const { generateToken } = await import("../obs/token.js");
            const tunnelUrl = agentState.consoleUrl;
            if (!tunnelUrl) {
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.rdp.unavailable", getLocale()));
                return;
            }
            const ttl = req.ttlMinutes && req.ttlMinutes > 0 ? Math.min(req.ttlMinutes, 480) : 30;
            const { token, expiresAt } = generateToken(ttl, `Requested via /rdp by ${req.sender}`);
            const baseUrl = tunnelUrl.replace(/\/(obs|console)(\/.*)?$/, "").replace(/\/$/, "");
            const url = `${baseUrl}/rdp/${token}`;
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.rdp.link", getLocale(), String(ttl), url, expiresAt));
        };
        doRdp().catch((err) => {
            logger.warn(`Failed to handle remote_desktop_request: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("realtime_request", (req) => {
        logger.system(`Realtime Mini App requested by ${req.sender} via ${req.channel}`);
        const doRealtime = async () => {
            const tunnelUrl = agentState.consoleUrl;
            if (!tunnelUrl) {
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.realtime.unavailable", getLocale()));
                return;
            }
            const baseUrl = tunnelUrl.replace(/\/(obs|console)(\/.*)?$/, "").replace(/\/$/, "");
            const realtimeUrl = `${baseUrl}/realtime`;
            if (req.channel === "telegram" && req.chatId) {
                await channelManager.sendTelegramMiniAppButton(req.chatId, "🎙️ VisionClaw Realtime Voice", "Open Realtime Voice", realtimeUrl);
                return;
            }
            await channelManager.sendMessage(req.channel, req.sender, realtimeUrl);
        };
        doRealtime().catch((err) => {
            logger.warn(`Failed to handle realtime_request: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("console_request", (req) => {
        const url = agentState.consoleUrl;
        const reply = url
            ? `${url}/console`
            : t("cmd.console.unavailable", getLocale());
        channelManager
            .sendMessage(req.channel, req.sender, reply)
            .catch((err) => {
            logger.warn(`Failed to send console URL: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("restart_request", (req) => {
        logger.system(`Restart requested by ${req.sender} via ${req.channel}`);
        // Write a marker so the agent can notify the owner after restart.
        writePendingRestart({
            channel: req.channel,
            sender: req.sender,
            requestedAt: new Date().toISOString(),
        });
        // Enqueue a persistent system message so the agent sees it on the next wake cycle after restart.
        messageQueue.enqueue({
            id: `restart_cmd_${Date.now()}`,
            channel: "system",
            sender: "system",
            text: "The user manually triggered restart. Please be careful if you should continue the way you do the task. Some actions may be stuck.",
            attachments: [],
            timestamp: new Date().toISOString(),
        });
        channelManager
            .sendMessage(req.channel, req.sender, t("cmd.restart.confirm", getLocale()))
            .catch((err) => {
            logger.warn(`Failed to send restart confirmation: ${err instanceof Error ? err.message : String(err)}`);
        });
        const doRestart = async () => {
            const { runGracefulShutdown } = await import("./state.js");
            const shutdownHook = async () => {
                if (deps.realtimeClose)
                    await deps.realtimeClose();
                if (deps.obsClose)
                    await deps.obsClose();
                await runGracefulShutdown();
            };
            const { scheduleRestart } = await import("../utils/restart.js");
            scheduleRestart(5_000, shutdownHook);
        };
        doRestart().catch((err) => {
            logger.err(`Failed to schedule restart: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("tunnel_request", (req) => {
        logger.system(`Tunnel restart requested by ${req.sender} via ${req.channel}`);
        if (!agentState.restartTunnel) {
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.tunnel.not_configured", getLocale()))
                .catch((err) => {
                logger.warn(`Failed to send tunnel reply: ${err instanceof Error ? err.message : String(err)}`);
            });
            return;
        }
        channelManager
            .sendMessage(req.channel, req.sender, t("cmd.tunnel.restarting", getLocale()))
            .catch((err) => {
            logger.warn(`Failed to send tunnel reply: ${err instanceof Error ? err.message : String(err)}`);
        });
        agentState.restartTunnel().catch((err) => {
            logger.warn(`Failed to restart tunnel: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("help_request", (req) => {
        channelManager
            .sendMessage(req.channel, req.sender, buildHelpText())
            .catch((err) => {
            logger.warn(`Failed to send help reply: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("status_request", (req) => {
        logger.system(`Status requested by ${req.sender} via ${req.channel}${req.forceRefresh ? " (refresh)" : ""}`);
        const doStatus = async () => {
            const tunnelUrl = agentState.consoleUrl;
            if (req.channel === "telegram" && req.chatId && tunnelUrl && !req.forceRefresh) {
                const miniAppUrl = `${tunnelUrl}/mini/status`;
                try {
                    await channelManager.sendTelegramMiniAppButton(req.chatId, "🤖 VisionClaw Status", "Open Status Dashboard", miniAppUrl);
                    return;
                }
                catch (err) {
                    logger.warn(`Failed to send status Mini App, falling back to text: ${err instanceof Error ? err.message : String(err)}`);
                }
            }
            const report = await buildStatusReport({
                agentState,
                sessionManager: session,
                messageQueue,
                config,
                ownerConfig,
                forceRefresh: req.forceRefresh,
            });
            await channelManager.sendMessage(req.channel, req.sender, report);
        };
        doStatus().catch((err) => {
            logger.warn(`Failed to send status reply: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("backup_request", (req) => {
        logger.system(`Backup requested by ${req.sender} via ${req.channel}`);
        channelManager
            .sendMessage(req.channel, req.sender, t("cmd.backup.starting", getLocale()))
            .catch((err) => {
            logger.warn(`Failed to send backup start message: ${err instanceof Error ? err.message : String(err)}`);
        });
        const doBackup = async () => {
            const { runBackup } = await import("./backup.js");
            const { formatBackupResult } = await import("../backup.js");
            const result = await runBackup(config, ownerConfig.ownerEmail);
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.backup.complete", getLocale(), formatBackupResult(result)));
        };
        doBackup().catch((err) => {
            logger.err(`Backup failed: ${err instanceof Error ? err.message : String(err)}`);
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.backup.failed", getLocale(), err instanceof Error ? err.message : String(err)))
                .catch((sendErr) => {
                logger.warn(`Failed to send backup error: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
            });
        });
    });
    channelManager.on("silent_mode_request", (req) => {
        ownerConfig.silentCompaction = req.enabled;
        saveOwnerConfig(ownerConfig);
        const label = req.enabled ? "on" : "off";
        logger.system(`Silent compaction mode set to ${label} by ${req.sender} via ${req.channel}`);
        channelManager
            .sendMessage(req.channel, req.sender, req.enabled ? t("cmd.silent.on", getLocale()) : t("cmd.silent.off", getLocale()))
            .catch((err) => {
            logger.warn(`Failed to send silent mode confirmation: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("weixin_login_request", (req) => {
        logger.system(`WeChat login requested by ${req.sender} via ${req.channel}`);
        const doWeixinLogin = async () => {
            const apiBaseUrl = config.channels.weixin?.baseUrl ?? WEIXIN_DEFAULT_BASE_URL;
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.weixin.starting", getLocale()));
            const startResult = await startWeixinLoginWithQr({
                apiBaseUrl,
                verbose: true,
                force: true,
            });
            if (!startResult.qrcodeUrl) {
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.weixin.failed", getLocale(), startResult.message));
                return;
            }
            const fs = await import("node:fs");
            const os = await import("node:os");
            const path = await import("node:path");
            const QRCode = await import("qrcode");
            const qrDir = path.join(os.tmpdir(), "visionclaw", "weixin");
            fs.mkdirSync(qrDir, { recursive: true });
            const qrImagePath = path.join(qrDir, `qr-login-${Date.now()}.png`);
            await QRCode.toFile(qrImagePath, startResult.qrcodeUrl, {
                width: 512,
                margin: 2,
            });
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.weixin.scan_qr", getLocale()), [{ type: "image", localPath: qrImagePath, mimeType: "image/png" }]);
            fs.unlink(qrImagePath, () => { });
            const result = await waitForWeixinLogin({
                sessionKey: startResult.sessionKey,
                apiBaseUrl,
                verbose: true,
                timeoutMs: 480_000,
            });
            if (!result.connected || !result.accountId) {
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.weixin.failed", getLocale(), result.message));
                return;
            }
            const accountId = normalizeAccountId(result.accountId);
            saveWeixinAccount(accountId, {
                token: result.botToken,
                baseUrl: result.baseUrl,
                userId: result.userId,
            });
            registerWeixinAccountId(accountId);
            const freshConfig = loadConfig();
            freshConfig.channels.weixin = {
                enabled: true,
                accountId,
                baseUrl: apiBaseUrl,
                cdnBaseUrl: freshConfig.channels.weixin?.cdnBaseUrl ?? "https://novac2c.cdn.weixin.qq.com/c2c",
            };
            saveConfig(freshConfig);
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.weixin.success", getLocale(), result.message, accountId));
            const shutdownHook = async () => {
                if (deps.realtimeClose)
                    await deps.realtimeClose();
                if (deps.obsClose)
                    await deps.obsClose();
                await runGracefulShutdown();
            };
            scheduleRestart(5_000, shutdownHook);
        };
        doWeixinLogin().catch((err) => {
            logger.err(`WeChat login failed: ${err instanceof Error ? err.message : String(err)}`);
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.weixin.error", getLocale(), err instanceof Error ? err.message : String(err)))
                .catch((sendErr) => {
                logger.warn(`Failed to send weixin login error: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
            });
        });
    });
    channelManager.on("usage_request", (req) => {
        logger.system(`Usage report requested by ${req.sender} via ${req.channel}`);
        const doUsage = async () => {
            const calendarPeriodKey = new Date().toISOString().slice(0, 7);
            const cacheDir = path.join(tmpdir(), "visionclaw-usage-pdfs");
            await mkdir(cacheDir, { recursive: true });
            let localPath = path.join(cacheDir, `${config.agentName}-current.pdf`);
            let useLocal = false;
            const usageStatus = heartbeat.lastUsageStatus;
            if (usageStatus?.periodStart) {
                localPath = path.join(cacheDir, `${config.agentName}-${usageStatus.periodStart.slice(0, 10)}.pdf`);
            }
            try {
                const cached = await stat(localPath);
                useLocal = Date.now() - cached.mtimeMs < 30 * 60 * 1000;
            }
            catch {
                useLocal = false;
            }
            if (!useLocal && config.onboardingServiceUrl && config.visionclawServiceApiKey) {
                const client = new (await import("../onboarding/onboarding-service-client.js")).OnboardingServiceClient(config.onboardingServiceUrl, config.agentName, config.visionclawServiceApiKey);
                const download = await client.downloadUsagePdf(localPath);
                useLocal = download.success;
                if (download.success) {
                    localPath = download.finalPath;
                }
            }
            const periodKey = /\d{4}-\d{2}-\d{2}/u.exec(path.basename(localPath))?.[0] ?? calendarPeriodKey;
            if (useLocal) {
                await channelManager.sendMessage(req.channel, req.sender, "Here is your usage report. There may be up to a 30 minute delay in this report.", [{ type: "document", localPath, filename: `visionclaw-usage-${periodKey}.pdf`, mimeType: "application/pdf" }]);
                return;
            }
            const { buildToolUsageReport } = await import("./tool-usage.js");
            const report = await buildToolUsageReport();
            await channelManager.sendMessage(req.channel, req.sender, report);
        };
        doUsage().catch((err) => {
            logger.warn(`Failed to send usage report: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("screenshot_request", (req) => {
        logger.system(`Screenshot requested by ${req.sender} via ${req.channel}`);
        const doScreenshot = async () => {
            const { takeScreenshot } = await import("../tools/screenshot.js");
            const { filePath } = await takeScreenshot();
            await channelManager.sendMessage(req.channel, req.sender, "", [{ type: "image", localPath: filePath, mimeType: "image/png" }]);
        };
        doScreenshot().catch((err) => {
            logger.warn(`Failed to send screenshot: ${err instanceof Error ? err.message : String(err)}`);
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.screenshot.failed", getLocale(), err instanceof Error ? err.message : String(err)))
                .catch((sendErr) => {
                logger.warn(`Failed to send screenshot error: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
            });
        });
    });
    channelManager.on("upgrade_beta_request", (req) => {
        logger.system(`Beta upgrade requested by ${req.sender} via ${req.channel}`);
        const doUpgradeBeta = async () => {
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.upgrade_beta.checking", getLocale()));
            const { performUpgrade } = await import("../tools/upgrade.js");
            const result = await performUpgrade({ beta: true });
            await channelManager.sendMessage(req.channel, req.sender, result.message);
        };
        doUpgradeBeta().catch((err) => {
            logger.err(`Beta upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.upgrade_beta.failed", getLocale(), err instanceof Error ? err.message : String(err)))
                .catch((sendErr) => {
                logger.warn(`Failed to send beta upgrade error: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
            });
        });
    });
    channelManager.on("upgrade_dev_request", (req) => {
        logger.system(`Dev upgrade requested by ${req.sender} via ${req.channel} (tag: ${req.tag})`);
        const doUpgradeDev = async () => {
            await channelManager.sendMessage(req.channel, req.sender, t("cmd.upgrade_dev.checking", getLocale(), req.tag));
            const { performUpgrade } = await import("../tools/upgrade.js");
            const result = await performUpgrade({ tag: req.tag });
            await channelManager.sendMessage(req.channel, req.sender, result.message);
        };
        doUpgradeDev().catch((err) => {
            logger.err(`Dev upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
            channelManager
                .sendMessage(req.channel, req.sender, t("cmd.upgrade_dev.failed", getLocale(), err instanceof Error ? err.message : String(err)))
                .catch((sendErr) => {
                logger.warn(`Failed to send dev upgrade error: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
            });
        });
    });
    channelManager.on("subscribe_request", (req) => {
        logger.system(`Subscribe status requested by ${req.sender} via ${req.channel}`);
        const doSubscribe = async () => {
            const portal = await heartbeat.getBillingPortal();
            if (!portal) {
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.subscribe.unavailable", getLocale()));
                return;
            }
            const lines = [];
            lines.push(t("cmd.subscribe.title", getLocale()));
            // Free trial / no subscription — direct to pricing page
            if (portal.status === "none" || (!portal.planName && !portal.portalUrl)) {
                lines.push(t("cmd.subscribe.free_trial", getLocale()));
                lines.push("");
                lines.push(t("cmd.subscribe.no_subscription", getLocale()));
                lines.push(pricingUrl);
                await channelManager.sendMessage(req.channel, req.sender, lines.join("\n"));
                return;
            }
            if (portal.planName) {
                lines.push(`Plan: ${portal.planName}`);
            }
            const statusLabel = portal.status === "active" ? t("cmd.subscribe.status_active", getLocale())
                : portal.status === "past_due" ? t("cmd.subscribe.status_past_due", getLocale())
                    : portal.status === "pending" ? t("cmd.subscribe.status_pending", getLocale())
                        : portal.status.charAt(0).toUpperCase() + portal.status.slice(1);
            lines.push(`Status: ${statusLabel}`);
            if (portal.currentPeriodEnd) {
                const date = new Date(portal.currentPeriodEnd);
                lines.push(`Renews: ${date.toISOString().split("T")[0]}`);
            }
            if (portal.portalUrl) {
                lines.push("");
                lines.push(`${t("cmd.subscribe.manage_link", getLocale())}\n${portal.portalUrl}`);
            }
            await channelManager.sendMessage(req.channel, req.sender, lines.join("\n"));
        };
        doSubscribe().catch((err) => {
            logger.warn(`Failed to send subscribe reply: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("refresh_request", (req) => {
        logger.system(`Context refresh requested by ${req.sender} via ${req.channel}`);
        const doRefresh = async () => {
            try {
                // Shared refresh runner owns the `compacting` mutex and the
                // stop-and-wait; we just map the outcome to requester-facing messages.
                const outcome = await runContextRefresh({ session, config, messageQueue, agentState }, {
                    reason: "manual",
                    stopActiveTurn: true,
                    hooks: {
                        onBusyStopping: () => channelManager.sendMessage(req.channel, req.sender, t("cmd.refresh.busy", getLocale())),
                        onRefreshing: () => channelManager.sendMessage(req.channel, req.sender, t("cmd.refresh.refreshing", getLocale())),
                    },
                });
                switch (outcome.status) {
                    case "stop_timeout":
                        await channelManager.sendMessage(req.channel, req.sender, t("cmd.refresh.stop_timeout", getLocale()));
                        break;
                    case "no_session":
                        await channelManager.sendMessage(req.channel, req.sender, t("cmd.refresh.no_session", getLocale()));
                        break;
                    case "refreshed":
                        await channelManager.sendMessage(req.channel, req.sender, t("cmd.refresh.done", getLocale()));
                        break;
                }
            }
            finally {
                wakeMainLoop?.();
            }
        };
        doRefresh().catch((err) => {
            logger.warn(`Failed to handle refresh request: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("tasks_request", (req) => {
        logger.system(`Tasks requested by ${req.sender} via ${req.channel}`);
        const sendTextFallback = () => {
            const tracker = agentState.activityTracker;
            const todos = tracker.getTodos();
            const taskDesc = tracker.getTaskDescription();
            const elapsedMs = tracker.getTaskElapsedMs();
            const recentActions = tracker.getRecentActions();
            const subagents = tracker.getSubagents();
            const activeMode = session.activeMode;
            const queueSize = messageQueue.size();
            // Treat tracked subagents as "something to show" too. Otherwise the
            // window between busy=false and the next startTask() (which clears
            // the tracker) would hide recently-completed subagent info that the
            // new operator-facing surface is meant to expose.
            if (!agentState.busy && todos.length === 0 && subagents.length === 0) {
                channelManager
                    .sendMessage(req.channel, req.sender, t("cmd.tasks.no_tasks", getLocale()))
                    .catch((err) => {
                    logger.warn(`Failed to send tasks reply: ${err instanceof Error ? err.message : String(err)}`);
                });
                return;
            }
            const lines = [];
            lines.push(t("cmd.tasks.title", getLocale()));
            lines.push("");
            // Status line
            if (agentState.busy && elapsedMs !== null) {
                const sec = Math.floor(elapsedMs / 1000);
                const m = Math.floor(sec / 60);
                const s = sec % 60;
                lines.push(t("cmd.tasks.status_working", getLocale(), String(m), String(s)));
            }
            else {
                lines.push(t("cmd.tasks.status_idle", getLocale()));
            }
            if (taskDesc) {
                lines.push(t("cmd.tasks.task_label", getLocale(), taskDesc));
            }
            lines.push(t("cmd.tasks.session_label", getLocale(), activeMode));
            // Todo list
            if (todos.length > 0) {
                lines.push("");
                lines.push(t("cmd.tasks.todo_list", getLocale()));
                for (const todo of todos) {
                    const icon = todo.status === "in_progress" ? "🔵" : todo.status === "completed" ? "✅" : "⚪";
                    lines.push(`  ${icon} ${todo.status === "in_progress" ? "in progress" : todo.status} — ${todo.content}`);
                }
            }
            // Recent steps (last 5)
            if (recentActions.length > 0) {
                lines.push("");
                lines.push(t("cmd.tasks.recent_steps", getLocale()));
                const last5 = recentActions.slice(-5);
                for (const action of last5) {
                    lines.push(`  ${action}`);
                }
            }
            // Subagents — show active first, then any recently-completed ones from
            // this task. Uses the shared formatter so /tasks and /status agree.
            if (subagents.length > 0) {
                const active = subagents.filter((s) => s.status === "running");
                const completed = subagents.filter((s) => s.status !== "running").slice(-3);
                if (active.length > 0 || completed.length > 0) {
                    lines.push("");
                    lines.push("🤖 Subagents:");
                    for (const s of active) {
                        lines.push(`  ${formatSubagentLine(s)}`);
                    }
                    for (const s of completed) {
                        lines.push(`  ${formatSubagentLine(s)}`);
                    }
                }
            }
            lines.push("");
            lines.push(t("cmd.tasks.queue", getLocale(), String(queueSize), queueSize === 1 ? "" : "s"));
            channelManager
                .sendMessage(req.channel, req.sender, lines.join("\n"))
                .catch((err) => {
                logger.warn(`Failed to send tasks reply: ${err instanceof Error ? err.message : String(err)}`);
            });
        };
        const tunnelUrl = agentState.consoleUrl;
        if (req.channel === "telegram" && req.chatId && tunnelUrl) {
            const miniAppUrl = `${tunnelUrl}/mini/tasks`;
            channelManager
                .sendTelegramMiniAppButton(req.chatId, "📋 VisionClaw Tasks", "Open Tasks Dashboard", miniAppUrl)
                .then(() => undefined)
                .catch((err) => {
                logger.warn(`Failed to send tasks Mini App, falling back to text: ${err instanceof Error ? err.message : String(err)}`);
                sendTextFallback();
            });
            return;
        }
        sendTextFallback();
    });
    channelManager.on("android_use_request", (req) => {
        const prev = ownerConfig.androidUseEnabled;
        ownerConfig.androidUseEnabled = req.enabled;
        saveOwnerConfig(ownerConfig);
        logger.system(`Android-use toggled from ${prev} to ${req.enabled} by ${req.sender} via ${req.channel}`);
        const emoji = req.enabled ? "📱" : "🚫";
        const state = req.enabled ? t("common.on", getLocale()) : t("common.off", getLocale());
        channelManager
            .sendMessage(req.channel, req.sender, t("cmd.android_use.toggle", getLocale(), emoji, state))
            .then(() => {
            // Schedule restart so the new tool set takes effect
            scheduleRestart(2000);
        })
            .catch((err) => {
            logger.warn(`Failed to send android-use confirmation: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("chat_monitor_request", (req) => {
        const emoji = req.enabled ? "💬" : "🚫";
        const state = req.enabled ? t("common.on", getLocale()) : t("common.off", getLocale());
        const pm2Action = req.enabled ? "startChatMonitor" : "stopChatMonitor";
        void (async () => {
            try {
                const mod = await import("../utils/chat-monitor.js");
                if (req.enabled && !mod.hasAnyChatDb()) {
                    await channelManager.sendMessage(req.channel, req.sender, t("cmd.chat_monitor.no_db", getLocale(), emoji));
                    return;
                }
                const prev = ownerConfig.chatMonitorEnabled;
                ownerConfig.chatMonitorEnabled = req.enabled;
                saveOwnerConfig(ownerConfig);
                logger.system(`Chat monitor toggled from ${prev} to ${req.enabled} by ${req.sender} via ${req.channel}`);
                mod[pm2Action]();
                await channelManager.sendMessage(req.channel, req.sender, t("cmd.chat_monitor.toggle", getLocale(), emoji, state));
            }
            catch (err) {
                const errMsg = err instanceof Error ? err.message : String(err);
                logger.warn(`Failed to ${pm2Action}: ${errMsg}`);
                await channelManager
                    .sendMessage(req.channel, req.sender, t("cmd.chat_monitor.error", getLocale(), emoji, state, errMsg))
                    .catch((_sendErr) => {
                    logger.warn(`Failed to send chat-monitor error reply: ${_sendErr instanceof Error ? _sendErr.message : String(_sendErr)}`);
                });
            }
        })();
    });
    channelManager.on("set_request", (req) => {
        logger.system(`Settings menu requested by ${req.sender} via ${req.channel}`);
        channelManager
            .sendTelegramSetMenu(req.chatId)
            .catch((err) => {
            logger.warn(`Failed to send /settings menu: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("set_language_selected", (event) => {
        const newLocale = resolveLocale(event.language);
        ownerConfig.language = event.language;
        saveOwnerConfig(ownerConfig);
        logger.system(`Language changed to "${event.language}" via /settings`);
        const langLabel = event.language === "zh" ? "中文" : "English";
        channelManager
            .sendMessage("telegram", String(event.chatId), t("cmd.set.language_changed", newLocale, langLabel))
            .catch((err) => {
            logger.warn(`Failed to send language change confirmation: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("set_timezone_selected", (event) => {
        const currentLocale = resolveLocale(ownerConfig.language);
        ownerConfig.timezone = event.timezone;
        saveOwnerConfig(ownerConfig);
        logger.system(`Timezone changed to "${event.timezone}" via /settings`);
        const doSetTimezone = async () => {
            try {
                await execSudo(["systemsetup", "-settimezone", event.timezone], {
                    password: config.systemPassword,
                    timeoutMs: 10_000,
                });
                await channelManager.sendMessage("telegram", String(event.chatId), t("cmd.set.timezone_changed", currentLocale, event.timezone));
            }
            catch (err) {
                const errMsg = err instanceof Error ? err.message : String(err);
                logger.warn(`Failed to set system timezone: ${errMsg}`);
                await channelManager.sendMessage("telegram", String(event.chatId), t("cmd.set.timezone_failed", currentLocale, event.timezone, errMsg));
            }
        };
        doSetTimezone().catch((err) => {
            logger.warn(`Failed to handle timezone change: ${err instanceof Error ? err.message : String(err)}`);
        });
    });
    channelManager.on("e2e_request", (req) => {
        logger.system(`E2E requested by ${req.sender} via ${req.channel}`);
        const run = async () => {
            const { parseE2ECommandText, runE2E, formatE2EResult } = await import("../e2e/index.js");
            const options = parseE2ECommandText(req.rawText);
            const result = await runE2E(options);
            const message = typeof result === "string"
                ? result
                : formatE2EResult(result, options.json);
            await channelManager.sendMessage(req.channel, req.sender, message);
        };
        run().catch(async (err) => {
            const errMsg = err instanceof Error ? err.message : String(err);
            logger.warn(`Failed to run E2E command: ${errMsg}`);
            await channelManager.sendMessage(req.channel, req.sender, `E2E failed: ${errMsg}`).catch(() => { });
        });
    });
}
//# sourceMappingURL=command-handlers.js.map