import fs from "node:fs";
import { spawnSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
import prompts from "prompts";
import { getConfigDir, getBaseDir, getOwnerFile, getSessionFile, getOnboardingSessionFile, getTelegramAccessFile, getMemoriesDir, getSkillsDir, getUserCatalogDir, getDynamicMcpServersFile, getProfile, configExists, loadConfig, saveConfig, } from "./config/index.js";
import { GoogleCalendarClient } from "./calendar/google-calendar.js";
import { OnboardingServiceClient } from "./onboarding/onboarding-service-client.js";
import { logger } from "./logger.js";
import { uninstallLaunchAgent } from "./service/launchd.js";
const PID_LOCK_FILE = "visionclaw.pid";
function removeFileIfExists(filePath) {
    if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
        logger.system(`  Removed ${path.basename(filePath)}`);
        return true;
    }
    return false;
}
function clearDirectoryIfExists(dirPath) {
    if (!fs.existsSync(dirPath))
        return false;
    const entries = fs.readdirSync(dirPath);
    if (entries.length === 0)
        return false;
    fs.rmSync(dirPath, { recursive: true });
    fs.mkdirSync(dirPath, { recursive: true });
    logger.system(`  Cleared ${path.relative(getConfigDir(), dirPath)}/`);
    return true;
}
function removeWeixinState() {
    const configDir = getConfigDir();
    const weixinDir = path.join(configDir, "openclaw-weixin");
    let removedAny = false;
    if (fs.existsSync(weixinDir)) {
        fs.rmSync(weixinDir, { recursive: true, force: true });
        logger.system("  Removed openclaw-weixin/");
        removedAny = true;
    }
    if (!configExists()) {
        return removedAny;
    }
    try {
        const config = loadConfig();
        const weixinConfig = config.channels.weixin;
        if (!weixinConfig || (!weixinConfig.enabled && !weixinConfig.accountId)) {
            return removedAny;
        }
        config.channels.weixin = {
            ...weixinConfig,
            enabled: false,
            accountId: undefined,
        };
        saveConfig(config);
        logger.system("  Disabled weixin channel in config");
        return true;
    }
    catch {
        logger.warn("Failed to reset weixin channel config");
        return removedAny;
    }
}
function hasActivePidLock() {
    const pidFile = path.join(getConfigDir(), PID_LOCK_FILE);
    if (!fs.existsSync(pidFile)) {
        return false;
    }
    try {
        const raw = fs.readFileSync(pidFile, "utf-8").trim();
        const pid = Number.parseInt(raw, 10);
        if (Number.isNaN(pid)) {
            return false;
        }
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
function hasRunningPm2VisionClaw() {
    const listResult = spawnSync("pm2", ["jlist"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    if (listResult.status !== 0 || !listResult.stdout) {
        return false;
    }
    try {
        const entries = JSON.parse(listResult.stdout);
        return entries.some((entry) => entry.name === "visionclaw" && entry.pm2_env?.status !== "stopped");
    }
    catch {
        return false;
    }
}
function assertVisionClawNotRunning() {
    // A launchd service can remain registered even when VisionClaw is fully
    // stopped, so only block reset on signals that indicate an actually live
    // process.
    if (hasActivePidLock() || hasRunningPm2VisionClaw()) {
        throw new Error("VisionClaw appears to be running. Stop all VisionClaw processes/services before running reset.");
    }
}
function removeAllCronEntries() {
    const listResult = spawnSync("crontab", ["-l"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    if (listResult.status !== 0 || !listResult.stdout) {
        return false;
    }
    const updateResult = spawnSync("crontab", ["-r"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    if (updateResult.status !== 0) {
        logger.warn("Failed to remove cron entries");
        return false;
    }
    logger.system("  Removed all cron entries");
    return true;
}
function removeAllPm2Entries() {
    const listResult = spawnSync("pm2", ["jlist"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    if (listResult.status !== 0 || !listResult.stdout) {
        return false;
    }
    let entries;
    try {
        entries = JSON.parse(listResult.stdout);
    }
    catch {
        logger.warn("Failed to parse pm2 process list during reset");
        return false;
    }
    if (entries.length === 0) {
        return false;
    }
    const deleteResult = spawnSync("pm2", ["delete", "all"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    if (deleteResult.status !== 0) {
        logger.warn("Failed to remove pm2 processes");
        return false;
    }
    spawnSync("pm2", ["save", "--force"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    spawnSync("pm2", ["kill"], {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "pipe"],
    });
    logger.system("  Removed all pm2 processes");
    return true;
}
function getCloudflaredDir() {
    return path.join(os.homedir(), ".cloudflared");
}
function getObsCloudflaredCredentialFile() {
    if (!configExists()) {
        return null;
    }
    try {
        const config = loadConfig();
        if (config.obs.tunnel !== "cloudflared" || !config.obs.tunnelName || !config.obs.tunnelHostname) {
            return null;
        }
        const configPath = path.join(getCloudflaredDir(), "config.yml");
        if (!fs.existsSync(configPath)) {
            return null;
        }
        const content = fs.readFileSync(configPath, "utf-8");
        const credentialsMatch = /^\s*credentials-file:\s*(.+)\s*$/m.exec(content);
        if (credentialsMatch?.[1]) {
            return path.basename(credentialsMatch[1].trim());
        }
        const tunnelMatch = /^\s*tunnel:\s*([0-9a-f-]+)\s*$/im.exec(content);
        if (tunnelMatch?.[1]) {
            return `${tunnelMatch[1].trim()}.json`;
        }
    }
    catch {
        logger.warn("Failed to inspect cloudflared OBS tunnel config");
    }
    return null;
}
function pruneCloudflaredNonObsTunnels() {
    const cloudflaredDir = getCloudflaredDir();
    if (!fs.existsSync(cloudflaredDir)) {
        return false;
    }
    const preservedCredentialFile = getObsCloudflaredCredentialFile();
    const entries = fs.readdirSync(cloudflaredDir);
    let removedCount = 0;
    for (const entry of entries) {
        if (!entry.endsWith(".json")) {
            continue;
        }
        if (preservedCredentialFile && entry === preservedCredentialFile) {
            continue;
        }
        fs.unlinkSync(path.join(cloudflaredDir, entry));
        removedCount++;
    }
    if (removedCount > 0) {
        logger.system(`  Removed ${removedCount} non-OBS cloudflared tunnel credential file(s)`);
        return true;
    }
    return false;
}
function removeCloudflaredState(options) {
    let removedAny = false;
    if (options.deleteObsTunnel && configExists()) {
        try {
            const config = loadConfig();
            const tunnelName = config.obs.tunnelName?.trim();
            if (tunnelName) {
                // If the profile is using an isolated cert (dev profile), pass it via
                // TUNNEL_ORIGIN_CERT so cloudflared operates on the isolated account.
                // Otherwise the default ~/.cloudflared/cert.pem is inherited from
                // process.env implicitly, matching pre-isolation production behaviour.
                const originCert = config.obs.tunnelOriginCert;
                const envOverride = originCert
                    ? { ...process.env, TUNNEL_ORIGIN_CERT: originCert }
                    : undefined;
                spawnSync("cloudflared", ["tunnel", "cleanup", tunnelName], {
                    encoding: "utf-8",
                    stdio: ["ignore", "pipe", "pipe"],
                    ...(envOverride ? { env: envOverride } : {}),
                });
                const deleteResult = spawnSync("cloudflared", ["tunnel", "delete", tunnelName], {
                    encoding: "utf-8",
                    stdio: ["ignore", "pipe", "pipe"],
                    ...(envOverride ? { env: envOverride } : {}),
                });
                if (deleteResult.status === 0) {
                    logger.system(`  Removed cloudflared tunnel: ${tunnelName}`);
                    removedAny = true;
                }
                else if (deleteResult.status !== null) {
                    logger.warn(`Failed to remove cloudflared tunnel: ${tunnelName}`);
                }
            }
        }
        catch {
            logger.warn("Failed to remove cloudflared tunnel");
        }
    }
    if (options.removeLocalDir) {
        const cloudflaredDir = getCloudflaredDir();
        if (fs.existsSync(cloudflaredDir)) {
            fs.rmSync(cloudflaredDir, { recursive: true, force: true });
            logger.system("  Removed ~/.cloudflared");
            removedAny = true;
        }
    }
    else if (pruneCloudflaredNonObsTunnels()) {
        removedAny = true;
    }
    return removedAny;
}
function removeVisionClawMachineArtifacts(options) {
    let removedAny = false;
    if (process.platform === "darwin") {
        const launchAgentPath = path.join(path.dirname(getBaseDir()), "Library", "LaunchAgents", "dev.visionclaw.agent.plist");
        const wrapperPath = path.join(getBaseDir(), "start.sh");
        if (fs.existsSync(launchAgentPath) || fs.existsSync(wrapperPath)) {
            uninstallLaunchAgent();
            removedAny = true;
        }
    }
    if (removeAllCronEntries()) {
        removedAny = true;
    }
    if (removeAllPm2Entries()) {
        removedAny = true;
    }
    if (removeCloudflaredState({
        removeLocalDir: options.removeCloudflaredLocalDir,
        deleteObsTunnel: true,
    })) {
        removedAny = true;
    }
    if (!removedAny) {
        logger.system("  No cron, service, pm2, or cloudflared items found");
    }
}
function removePreOnboardingArtifacts() {
    let removedAny = false;
    if (removeAllCronEntries()) {
        removedAny = true;
    }
    if (removeAllPm2Entries()) {
        removedAny = true;
    }
    if (removeCloudflaredState({ removeLocalDir: false, deleteObsTunnel: false })) {
        removedAny = true;
    }
    if (!removedAny) {
        logger.system("  No cron, pm2, or cloudflared items found");
    }
}
function createOnboardingClient() {
    if (!configExists())
        return null;
    try {
        const config = loadConfig();
        if (!config.onboardingServiceUrl || !config.visionclawServiceApiKey)
            return null;
        return new OnboardingServiceClient(config.onboardingServiceUrl, config.agentName, config.visionclawServiceApiKey);
    }
    catch {
        return null;
    }
}
async function resetOwnerServerSide() {
    const client = createOnboardingClient();
    if (!client) {
        logger.warn("No onboarding service configured — skipping server-side reset");
        return;
    }
    logger.system("Resetting owner on onboarding service...");
    const ok = await client.resetOwner();
    if (ok) {
        logger.system("  Server-side owner reset successful");
    }
}
async function clearPrimaryCalendar() {
    if (!configExists()) {
        return;
    }
    try {
        const config = loadConfig();
        const calendar = new GoogleCalendarClient(config);
        await calendar.clearPrimaryCalendar();
        logger.system("  Cleared primary Google Calendar events");
    }
    catch {
        logger.warn("Failed to clear primary Google Calendar events");
    }
}
async function resetOwner() {
    const profile = getProfile();
    const configDir = getConfigDir();
    logger.system(`\nResetting owner for profile "${profile}"...\n`);
    // Reset server-side first
    await resetOwnerServerSide();
    await clearPrimaryCalendar();
    removePreOnboardingArtifacts();
    let removedCount = 0;
    // Owner & session state
    if (removeFileIfExists(getOwnerFile()))
        removedCount++;
    if (removeFileIfExists(getSessionFile()))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "session-mailbox.json")))
        removedCount++;
    if (removeFileIfExists(getOnboardingSessionFile()))
        removedCount++;
    if (removeFileIfExists(getTelegramAccessFile()))
        removedCount++;
    if (removeFileIfExists(getDynamicMcpServersFile()))
        removedCount++;
    if (removeWeixinState())
        removedCount++;
    // Databases
    if (removeFileIfExists(path.join(configDir, "transcript-memory.db")))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "transcript-memory.db-wal")))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "transcript-memory.db-shm")))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "queue.db")))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "queue.db-wal")))
        removedCount++;
    if (removeFileIfExists(path.join(configDir, "queue.db-shm")))
        removedCount++;
    // Memories & skills
    if (clearDirectoryIfExists(getMemoriesDir()))
        removedCount++;
    if (clearDirectoryIfExists(getSkillsDir()))
        removedCount++;
    if (clearDirectoryIfExists(getUserCatalogDir()))
        removedCount++;
    if (removedCount === 0) {
        logger.system("Profile was already in a clean state. Nothing to remove.");
    }
    else {
        logger.system(`\nReset complete. ${removedCount} item(s) removed.`);
        logger.system("Run the agent again to start onboarding with a fresh owner.");
    }
}
async function resetMachine() {
    const profile = getProfile();
    const configDir = getConfigDir();
    logger.system(`\nResetting machine for profile "${profile}"...\n`);
    // Reset server-side first
    await resetOwnerServerSide();
    removeVisionClawMachineArtifacts({ removeCloudflaredLocalDir: true });
    // Delete the entire profile directory
    if (fs.existsSync(configDir)) {
        fs.rmSync(configDir, { recursive: true });
        logger.system(`  Deleted profile directory: ${configDir}`);
    }
    logger.system("\nMachine reset complete. Run 'visionclaw setup' to start fresh.");
}
export async function runResetCli(args) {
    const forceFlag = args.includes("--force") || args.includes("-f");
    const machineFlag = args.includes("--machine");
    const ownerFlag = args.includes("--owner");
    const profile = getProfile();
    // Require explicit --owner or --machine flag
    if (!machineFlag && !ownerFlag) {
        logger.system("Usage: visionclaw reset --owner [--force]");
        logger.system("       visionclaw reset --machine [--force]");
        logger.system("");
        logger.system("  --owner    Reset owner: clear owner data, memories, skills, DBs (preserves config & logs)");
        logger.system("  --machine  Reset machine: delete entire profile directory for fresh setup");
        logger.system("  --force    Skip confirmation prompt");
        process.exit(1);
    }
    if (!configExists() && !machineFlag) {
        logger.err(`No configuration found for profile "${profile}". Nothing to reset.`);
        process.exit(1);
    }
    try {
        assertVisionClawNotRunning();
    }
    catch (err) {
        logger.err(err instanceof Error ? err.message : String(err));
        process.exit(1);
    }
    if (machineFlag) {
        // Level 2: Reset Machine
        const configDir = getConfigDir();
        if (!fs.existsSync(configDir)) {
            logger.err(`Profile directory does not exist: ${configDir}. Nothing to reset.`);
            process.exit(1);
        }
        logger.system(`This will DELETE the entire profile directory for "${profile}".`);
        logger.system(`  ${configDir}`);
        logger.system("\nEverything will be removed: config, tokens, logs, memories, skills, browser profile — all of it.");
        logger.system("The machine will need 'visionclaw setup' to be usable again.\n");
        if (!forceFlag) {
            const { confirmed } = (await prompts({
                type: "confirm",
                name: "confirmed",
                message: `Are you sure you want to wipe profile "${profile}" completely?`,
                initial: false,
            }));
            if (!confirmed) {
                logger.system("Reset cancelled.");
                return;
            }
        }
        await resetMachine();
    }
    else {
        // Level 1: Reset Owner (default)
        logger.system(`This will reset profile "${profile}" to the post-setup, pre-owner state.`);
        logger.system("The following data will be removed:");
        logger.system("  - Owner configuration (requires re-onboarding)");
        logger.system("  - Agent sessions & mailbox");
        logger.system("  - Onboarding session");
        logger.system("  - Telegram access configuration");
        logger.system("  - All memories");
        logger.system("  - All installed skills & skill catalog");
        logger.system("  - Dynamic MCP server configurations");
        logger.system("  - Transcript memory & message queue databases");
        logger.system("  - All cron entries and pm2 processes");
        logger.system("  - Non-OBS cloudflared tunnel credentials (keeps ~/.cloudflared directory)");
        logger.system("");
        logger.system("Preserved: config.json, google-tokens.json, browser profile, logs/, and ~/.cloudflared/");
        logger.system("");
        if (!forceFlag) {
            const { confirmed } = (await prompts({
                type: "confirm",
                name: "confirmed",
                message: `Are you sure you want to reset profile "${profile}"?`,
                initial: false,
            }));
            if (!confirmed) {
                logger.system("Reset cancelled.");
                return;
            }
        }
        await resetOwner();
    }
}
//# sourceMappingURL=reset.js.map