/**
 * VisionClaw CLI — lightweight maintenance commands that work when the
 * agent is running **or** stopped.
 *
 * All commands read from the profile's file-system state (session.json,
 * config.json, PID lock) and/or the OBS HTTP server, so they never need
 * to import the heavy agent machinery.
 */
import fs from "node:fs";
import path from "node:path";
import http from "node:http";
import { getProfile, getConfigDir, loadConfig, loadOwnerConfig, ownerConfigExists, loadSessionId, loadUsageSnapshot, getSessionFile, rotateSessionId, } from "../config/index.js";
import { getCurrentVersion } from "../utils/version-check.js";
import { DEFAULT_COMPACTION_TIMEOUT_MS } from "../agent/compaction-policy.js";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const PID_LOCK = "visionclaw.pid";
function getAgentProcessInfo() {
    const lockFile = path.join(getConfigDir(), PID_LOCK);
    if (!fs.existsSync(lockFile))
        return { running: false, pid: null };
    try {
        const pid = Number.parseInt(fs.readFileSync(lockFile, "utf-8").trim(), 10);
        if (Number.isNaN(pid))
            return { running: false, pid: null };
        process.kill(pid, 0); // existence check
        return { running: true, pid };
    }
    catch {
        return { running: false, pid: null };
    }
}
function getObsPort() {
    try {
        return loadConfig().obs.port;
    }
    catch {
        return 3101;
    }
}
function fetchObs(pathname, port) {
    return new Promise((resolve, reject) => {
        const req = http.get({ hostname: "127.0.0.1", port, path: pathname, timeout: 3000 }, (res) => {
            const chunks = [];
            res.on("data", (c) => chunks.push(c));
            res.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
        });
        req.on("error", reject);
        req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
    });
}
function fmt(n) {
    return n.toLocaleString("en-US");
}
function pct(ratio) {
    return `${(ratio * 100).toFixed(1)}%`;
}
function timeAgo(ms) {
    const seconds = Math.floor((Date.now() - ms) / 1000);
    if (seconds < 60)
        return `${seconds}s ago`;
    if (seconds < 3600)
        return `${Math.floor(seconds / 60)}m ago`;
    if (seconds < 86400)
        return `${Math.floor(seconds / 3600)}h ago`;
    return `${Math.floor(seconds / 86400)}d ago`;
}
// ---------------------------------------------------------------------------
// ctx status
// ---------------------------------------------------------------------------
export function runCtxStatus() {
    const profile = getProfile();
    console.log(`Profile: ${profile}\n`);
    const sessionFile = getSessionFile();
    if (!fs.existsSync(sessionFile)) {
        console.log("No session file found — agent has not started yet.");
        return;
    }
    let sessionData;
    try {
        sessionData = JSON.parse(fs.readFileSync(sessionFile, "utf-8"));
    }
    catch {
        console.log("Failed to read session.json");
        return;
    }
    const generalSessionId = (sessionData.generalSessionId ?? sessionData.sessionId ?? null);
    const codingSessionId = (sessionData.codingSessionId ?? null);
    console.log("Sessions:");
    console.log(`  General: ${generalSessionId ?? "(none)"}`);
    if (codingSessionId) {
        console.log(`  Coding:  ${codingSessionId}`);
    }
    const generalUsage = loadUsageSnapshot("general");
    const codingUsage = loadUsageSnapshot("coding");
    if (generalUsage) {
        console.log("\nGeneral session context:");
        console.log(`  Tokens used:    ${fmt(generalUsage.usedInputTokens)} / ${fmt(generalUsage.contextWindow)}`);
        console.log(`  Usage:          ${pct(generalUsage.usedPct)}`);
        if (generalUsage.allImageBlocks !== undefined) {
            console.log(`  Image blocks:   ${generalUsage.allImageBlocks} / 100`);
        }
        if (generalUsage.capturedAtMs) {
            console.log(`  Captured:       ${timeAgo(generalUsage.capturedAtMs)}`);
        }
    }
    else {
        console.log("\nGeneral session: no usage snapshot available");
    }
    if (codingUsage) {
        console.log("\nCoding session context:");
        console.log(`  Tokens used:    ${fmt(codingUsage.usedInputTokens)} / ${fmt(codingUsage.contextWindow)}`);
        console.log(`  Usage:          ${pct(codingUsage.usedPct)}`);
        if (codingUsage.allImageBlocks !== undefined) {
            console.log(`  Image blocks:   ${codingUsage.allImageBlocks} / 100`);
        }
        if (codingUsage.capturedAtMs) {
            console.log(`  Captured:       ${timeAgo(codingUsage.capturedAtMs)}`);
        }
    }
    if (sessionData.updatedAt) {
        console.log(`\nSession file updated: ${sessionData.updatedAt}`);
    }
}
// ---------------------------------------------------------------------------
// ctx compact
// ---------------------------------------------------------------------------
export async function runCtxCompact(args) {
    const mode = args.includes("--coding") ? "coding" : "general";
    const sessionId = loadSessionId(mode);
    if (!sessionId) {
        console.error(`No ${mode} session ID found. Cannot compact.`);
        process.exit(1);
    }
    const proc = getAgentProcessInfo();
    if (proc.running) {
        console.log(`Agent is running (PID ${proc.pid}). Use /refresh via Telegram instead,`);
        console.log(`or stop the agent first (pm2 stop visionclaw) then re-run this command.`);
        process.exit(1);
    }
    console.log(`Compacting ${mode} session: ${sessionId}`);
    console.log("Resolving Claude CLI...\n");
    // Use the SDK's query() function via dynamic import. The claude-agent-sdk
    // query() spawns the Claude CLI subprocess internally and streams JSON
    // messages back. We iterate them looking for compact_boundary.
    const sdk = await import("@anthropic-ai/claude-agent-sdk");
    const { buildAgentEnv } = await import("../agent/model-provider.js");
    const { resolveClaudeCliExecutable } = await import("../agent/engines/claude/cli-resolver.js");
    const config = loadConfig();
    const cliResolution = resolveClaudeCliExecutable();
    const env = buildAgentEnv(config);
    const options = {
        model: config.model,
        ...(cliResolution.kind !== "sdk-default"
            ? { pathToClaudeCodeExecutable: cliResolution.path }
            : {}),
        permissionMode: "bypassPermissions",
        allowDangerouslySkipPermissions: true,
        cwd: getConfigDir(),
        settingSources: ["project"],
        env,
        resume: sessionId,
        maxTurns: 1,
    };
    let preTokens;
    let compactDone = false;
    // The SDK's query() function signature: query({ prompt, options })
    // We use `as any` to bypass the type mismatch between installed SDK
    // versions — the runtime API is stable across 0.2.108–0.2.114+.
    const queryFn = sdk.query;
    const TIMEOUT_MS = DEFAULT_COMPACTION_TIMEOUT_MS;
    const abortController = new AbortController();
    const timer = setTimeout(() => {
        console.error(`\nCompaction timed out after ${Math.round(TIMEOUT_MS / 60_000)} minute(s) — aborting`);
        abortController.abort();
    }, TIMEOUT_MS);
    try {
        for await (const msg of queryFn({
            prompt: "/compact",
            options: { ...options, abortController },
        })) {
            if (msg.type === "system" && msg.subtype === "compact_boundary") {
                const meta = msg.compact_metadata;
                preTokens = meta?.pre_tokens;
                compactDone = true;
            }
        }
    }
    catch (err) {
        console.error(`\nCompaction failed: ${err instanceof Error ? err.message : String(err)}`);
        process.exit(1);
    }
    finally {
        clearTimeout(timer);
    }
    if (compactDone) {
        console.log("Compaction completed successfully.");
        if (preTokens !== undefined) {
            console.log(`  Pre-compaction tokens: ${fmt(preTokens)}`);
        }
        // Re-read the updated snapshot
        const updated = loadUsageSnapshot(mode);
        if (updated) {
            console.log(`  Post-compaction:       ${fmt(updated.usedInputTokens)} / ${fmt(updated.contextWindow)} (${pct(updated.usedPct)})`);
        }
    }
    else {
        console.log("Compaction completed (no compact_boundary event received).");
    }
}
// ---------------------------------------------------------------------------
// ctx refresh
// ---------------------------------------------------------------------------
export function runCtxRefresh() {
    const proc = getAgentProcessInfo();
    if (proc.running) {
        console.log(`Agent is running (PID ${proc.pid}). Use /refresh via Telegram instead,`);
        console.log(`or stop the agent first (pm2 stop visionclaw) then re-run this command.`);
        process.exit(1);
    }
    const sessionFile = getSessionFile();
    if (!fs.existsSync(sessionFile)) {
        console.log("No session file found — agent has not started yet.");
        return;
    }
    const generalSessionId = loadSessionId("general");
    const codingSessionId = loadSessionId("coding");
    if (!generalSessionId && !codingSessionId) {
        console.log("No active session IDs found. Next agent start will already create new sessions.");
        return;
    }
    if (generalSessionId) {
        rotateSessionId("general");
    }
    if (codingSessionId) {
        rotateSessionId("coding");
    }
    console.log("Context sessions refreshed. Next agent start will create new sessions.");
    if (generalSessionId) {
        console.log(`  General moved to previousSessionIds: ${generalSessionId}`);
    }
    if (codingSessionId) {
        console.log(`  Coding moved to previousSessionIds:  ${codingSessionId}`);
    }
}
// ---------------------------------------------------------------------------
// agent status
// ---------------------------------------------------------------------------
export async function runAgentStatus() {
    const profile = getProfile();
    const version = getCurrentVersion();
    console.log(`VisionClaw v${version}`);
    console.log(`Profile: ${profile}\n`);
    // Config
    try {
        const config = loadConfig();
        console.log(`Agent:    ${config.agentName}`);
        console.log(`Provider: ${config.provider}`);
        console.log(`Model:    ${config.model}`);
        console.log(`Gmail:    ${config.gmail}`);
    }
    catch {
        console.log("Config: not found or invalid");
    }
    // Owner
    if (ownerConfigExists()) {
        try {
            const owner = loadOwnerConfig();
            console.log(`Owner:    ${owner.ownerName} <${owner.ownerEmail}>`);
            console.log(`Dual-session: ${owner.dualSessionEnabled ? "enabled" : "disabled"}`);
        }
        catch {
            // skip
        }
    }
    // Process
    const proc = getAgentProcessInfo();
    console.log(`\nProcess:  ${proc.running ? `running (PID ${proc.pid})` : "not running"}`);
    // If running, try to get live status from OBS
    if (proc.running) {
        try {
            const port = getObsPort();
            const raw = await fetchObs("/console/status", port);
            const status = JSON.parse(raw);
            console.log(`  Busy:          ${status.busy ? "yes" : "no"}`);
            console.log(`  Stop requested: ${status.stopRequested ? "yes" : "no"}`);
            console.log(`  OBS server:    http://127.0.0.1:${port}/obs`);
        }
        catch {
            console.log("  OBS server:    unreachable");
        }
    }
    // Context summary
    const generalUsage = loadUsageSnapshot("general");
    if (generalUsage) {
        console.log(`\nContext:   ${pct(generalUsage.usedPct)} (${fmt(generalUsage.usedInputTokens)} / ${fmt(generalUsage.contextWindow)} tokens)`);
        if (generalUsage.capturedAtMs) {
            console.log(`  Updated: ${timeAgo(generalUsage.capturedAtMs)}`);
        }
    }
}
// ---------------------------------------------------------------------------
// CLI dispatch
// ---------------------------------------------------------------------------
export function printCliHelp() {
    console.log(`VisionClaw CLI — Maintenance Commands

Usage:
  visionclaw ctx status             Show context window usage and session info
  visionclaw ctx compact            Trigger manual context compaction (agent must be stopped)
  visionclaw ctx compact --coding   Compact the coding session instead
  visionclaw ctx refresh            Rotate current sessions so next start creates new sessions
  visionclaw agent status           Show agent status, process info, and config

Global options:
  --profile <name>    Use a specific profile (default: "default")
`);
}
export async function runCli(command, subcommand, args) {
    if (command === "ctx") {
        if (subcommand === "status") {
            runCtxStatus();
            return;
        }
        if (subcommand === "compact") {
            await runCtxCompact(args);
            return;
        }
        if (subcommand === "refresh") {
            runCtxRefresh();
            return;
        }
        console.error(`Unknown ctx subcommand: "${subcommand}". Try: status, compact, refresh`);
        process.exit(1);
    }
    if (command === "agent") {
        if (subcommand === "status") {
            await runAgentStatus();
            return;
        }
        console.error(`Unknown agent subcommand: "${subcommand}". Try: status`);
        process.exit(1);
    }
    console.error(`Unknown CLI command: "${command}"`);
    printCliHelp();
    process.exit(1);
}
//# sourceMappingURL=index.js.map