/**
 * Utility functions for managing the unified chat monitor pm2 process.
 *
 * The chat-monitor script watches local SQLite message databases for multiple
 * chat applications (WeChat, Feishu/Lark) and triggers VisionClaw wake-ups
 * via SIGUSR2 when new messages arrive.
 */
import { execSync } from "node:child_process";
import { existsSync, readdirSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { logger } from "../logger.js";
import { getPackageRoot } from "./version-check.js";
const PM2_PROCESS_NAME = "chat-monitor";
/**
 * Look up a pm2 process by name using the structured `pm2 jlist` output.
 * Returns null if pm2 is not available or the process is not registered.
 *
 * Using `jlist` avoids fragile regex parsing of the `pm2 describe` table
 * (whose Unicode box-drawing characters are easy to capture by accident).
 */
function findPm2Process(name) {
    try {
        const out = execSync("pm2 jlist", { encoding: "utf-8" });
        const parsed = JSON.parse(out);
        if (!Array.isArray(parsed))
            return null;
        const match = parsed.find((p) => p.name === name);
        return match ?? null;
    }
    catch {
        return null;
    }
}
const XWECHAT_ROOT = path.join(os.homedir(), "Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files");
const LARKSHELL_ROOT = path.join(os.homedir(), "Library/Application Support/LarkShell/sdk_storage");
/**
 * Resolve the chat-monitor script path to a stable location suitable for PM2.
 *
 * In pnpm global installs, prefer pnpm's stable global symlink rather than the
 * real path under `.pnpm/visionclaw@x.x.x/...`. After an upgrade, pnpm removes
 * the old version directory, breaking PM2 entries saved with that real path.
 * `pnpm root -g` gives us the stable symlink path (e.g.
 * `.../node_modules/visionclaw/scripts/chat-monitor.mjs`) that automatically
 * points to the latest globally installed version.
 *
 * If the global package path is unavailable, fall back to `getPackageRoot()`.
 * This is correct for local development (`<repo>/scripts/...`) and for the
 * external-bundle production runtime, where support files are extracted under
 * `~/.visionclaw/scripts/...` during self-upgrade.
 */
function getScriptPath() {
    try {
        const globalRoot = execSync("pnpm root -g", { encoding: "utf-8" }).trim();
        const stablePath = path.join(globalRoot, "visionclaw", "scripts", "chat-monitor.mjs");
        if (existsSync(stablePath)) {
            return stablePath;
        }
    }
    catch {
        // pnpm not available or not a global install — fall through
    }
    return path.join(getPackageRoot(), "scripts", "chat-monitor.mjs");
}
export function hasWechatMessageDb() {
    try {
        const entries = readdirSync(XWECHAT_ROOT, { withFileTypes: true });
        return entries.some((entry) => entry.isDirectory()
            && entry.name.startsWith("wxid_")
            && existsSync(path.join(XWECHAT_ROOT, entry.name, "db_storage/message/message_0.db")));
    }
    catch {
        return false;
    }
}
export function hasFeishuMessageDb() {
    try {
        const entries = readdirSync(LARKSHELL_ROOT, { withFileTypes: true });
        return entries.some((entry) => entry.isDirectory()
            && existsSync(path.join(LARKSHELL_ROOT, entry.name, "messages.db")));
    }
    catch {
        return false;
    }
}
export function hasAnyChatDb() {
    return hasWechatMessageDb() || hasFeishuMessageDb();
}
export function startChatMonitor() {
    const scriptPath = getScriptPath();
    if (!existsSync(scriptPath)) {
        throw new Error(`Chat monitor script not found: ${scriptPath}`);
    }
    const existing = findPm2Process(PM2_PROCESS_NAME);
    if (existing) {
        // Check if the registered script path matches the current stable path.
        // After a pnpm global upgrade, the old .pnpm/visionclaw@x.x.x/ path may
        // no longer exist. Re-register with the correct path if stale.
        const registeredPath = existing.pm2_env?.pm_exec_path;
        const stale = !!registeredPath &&
            registeredPath !== scriptPath &&
            !existsSync(registeredPath);
        if (stale) {
            logger.system(`Chat monitor has stale path: ${registeredPath}, re-registering with: ${scriptPath}`);
            try {
                execSync(`pm2 delete ${PM2_PROCESS_NAME} 2>/dev/null`, {
                    encoding: "utf-8",
                });
            }
            catch {
                // Best-effort: fall through to fresh start below.
            }
            // Fall through to fresh start below
        }
        else {
            if (existing.pm2_env?.status === "online") {
                logger.system(`Chat monitor already running`);
                return;
            }
            execSync(`pm2 restart ${PM2_PROCESS_NAME}`, { encoding: "utf-8" });
            logger.system(`Chat monitor restarted`);
            return;
        }
    }
    execSync(`pm2 start "${scriptPath}" --name ${PM2_PROCESS_NAME}`, {
        encoding: "utf-8",
    });
    execSync("pm2 save", { encoding: "utf-8" });
    logger.system(`Chat monitor started via pm2`);
}
export function stopChatMonitor() {
    try {
        execSync(`pm2 stop ${PM2_PROCESS_NAME}`, { encoding: "utf-8" });
        logger.system(`Chat monitor stopped`);
    }
    catch {
        logger.system(`Chat monitor was not running`);
    }
}
export function isChatMonitorRunning() {
    return findPm2Process(PM2_PROCESS_NAME)?.pm2_env?.status === "online";
}
// Backward compatibility: stop the old separate monitor processes if they exist
export function stopLegacyMonitors() {
    for (const name of ["wechat-monitor", "feishu-monitor"]) {
        try {
            const status = execSync(`pm2 describe ${name} 2>/dev/null`, { encoding: "utf-8" });
            if (status.includes("online") || status.includes("stopped")) {
                execSync(`pm2 delete ${name} 2>/dev/null`, { encoding: "utf-8" });
                execSync("pm2 save", { encoding: "utf-8" });
                logger.system(`Removed legacy pm2 process: ${name}`);
            }
        }
        catch {
            // Not registered — nothing to clean up
        }
    }
}
//# sourceMappingURL=chat-monitor.js.map