import { execFile, execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
const STATUS_KEY = "nerf";
const lastSidebarSnapshots = new Map();
let cmuxPromptedThisSession = false;
let cachedCliAvailability = null;
export function detectCmuxEnvironment(env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
    const socketPath = env.CMUX_SOCKET_PATH ?? DEFAULT_SOCKET_PATH;
    const workspaceId = env.CMUX_WORKSPACE_ID?.trim() || undefined;
    const surfaceId = env.CMUX_SURFACE_ID?.trim() || undefined;
    const available = Boolean(workspaceId && surfaceId && socketExists(socketPath));
    return {
        available,
        cliAvailable: cliAvailable(),
        socketPath,
        workspaceId,
        surfaceId,
    };
}
export function resolveCmuxConfig(preferences, env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
    const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
    const cmux = preferences?.cmux ?? {};
    const enabled = detected.available && cmux.enabled === true;
    return {
        ...detected,
        enabled,
        notifications: enabled && cmux.notifications !== false,
        sidebar: enabled && cmux.sidebar !== false,
        splits: enabled && cmux.splits === true,
        browser: enabled && cmux.browser === true,
    };
}
export function shouldPromptToEnableCmux(preferences, env = process.env, socketExists = existsSync, cliAvailable = isCmuxCliAvailable) {
    if (cmuxPromptedThisSession)
        return false;
    const detected = detectCmuxEnvironment(env, socketExists, cliAvailable);
    if (!detected.available)
        return false;
    return preferences?.cmux?.enabled === undefined;
}
export function markCmuxPromptShown() {
    cmuxPromptedThisSession = true;
}
export function resetCmuxPromptState() {
    cmuxPromptedThisSession = false;
}
export function isCmuxCliAvailable() {
    if (cachedCliAvailability !== null)
        return cachedCliAvailability;
    try {
        execFileSync("cmux", ["--help"], { stdio: "ignore", timeout: 1000 });
        cachedCliAvailability = true;
    }
    catch {
        cachedCliAvailability = false;
    }
    return cachedCliAvailability;
}
export function supportsOsc777Notifications(env = process.env) {
    const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
    return termProgram === "ghostty" || termProgram === "wezterm" || termProgram === "iterm.app";
}
export function emitOsc777Notification(title, body) {
    if (!supportsOsc777Notifications())
        return;
    const safeTitle = normalizeNotificationText(title).replace(/;/g, ",");
    const safeBody = normalizeNotificationText(body).replace(/;/g, ",");
    process.stdout.write(`\x1b]777;notify;${safeTitle};${safeBody}\x07`);
}
export function buildCmuxStatusLabel(state) {
    const parts = [];
    if (state.activeMilestone)
        parts.push(state.activeMilestone.id);
    if (state.activeSlice)
        parts.push(state.activeSlice.id);
    if (state.activeTask) {
        const prev = parts.pop();
        parts.push(prev ? `${prev}/${state.activeTask.id}` : state.activeTask.id);
    }
    if (parts.length === 0)
        return state.phase;
    return `${parts.join(" ")} · ${state.phase}`;
}
export function buildCmuxProgress(state) {
    const progress = state.progress;
    if (!progress)
        return null;
    const choose = (done, total, label) => {
        if (total <= 0)
            return null;
        return { value: Math.max(0, Math.min(1, done / total)), label: `${done}/${total} ${label}` };
    };
    return choose(progress.tasks?.done ?? 0, progress.tasks?.total ?? 0, "tasks")
        ?? choose(progress.slices?.done ?? 0, progress.slices?.total ?? 0, "slices")
        ?? choose(progress.milestones.done, progress.milestones.total, "milestones");
}
function phaseVisuals(phase) {
    switch (phase) {
        case "blocked":
            return { icon: "triangle-alert", color: "#ef4444" };
        case "paused":
            return { icon: "pause", color: "#f59e0b" };
        case "complete":
        case "completing-milestone":
            return { icon: "check", color: "#22c55e" };
        case "planning":
        case "researching":
        case "replanning-slice":
            return { icon: "compass", color: "#3b82f6" };
        case "validating-milestone":
        case "verifying":
            return { icon: "shield-check", color: "#06b6d4" };
        default:
            return { icon: "rocket", color: "#4ade80" };
    }
}
function sidebarSnapshotKey(config) {
    return config.workspaceId ?? "default";
}
export class CmuxClient {
    config;
    constructor(config) {
        this.config = config;
    }
    static fromPreferences(preferences) {
        return new CmuxClient(resolveCmuxConfig(preferences));
    }
    getConfig() {
        return this.config;
    }
    canRun() {
        return this.config.available && this.config.cliAvailable;
    }
    appendWorkspace(args) {
        return this.config.workspaceId ? [...args, "--workspace", this.config.workspaceId] : args;
    }
    appendSurface(args, surfaceId) {
        return surfaceId ? [...args, "--surface", surfaceId] : args;
    }
    runSync(args) {
        if (!this.canRun())
            return null;
        try {
            return execFileSync("cmux", args, {
                encoding: "utf-8",
                timeout: 3000,
                env: process.env,
            });
        }
        catch {
            return null;
        }
    }
    async runAsync(args) {
        if (!this.canRun())
            return null;
        try {
            const result = await execFileAsync("cmux", args, {
                encoding: "utf-8",
                timeout: 5000,
                env: process.env,
            });
            return result.stdout;
        }
        catch {
            return null;
        }
    }
    getCapabilities() {
        const stdout = this.runSync(["capabilities", "--json"]);
        return stdout ? parseJson(stdout) : null;
    }
    identify() {
        const stdout = this.runSync(["identify", "--json"]);
        return stdout ? parseJson(stdout) : null;
    }
    setStatus(label, phase) {
        if (!this.config.sidebar)
            return;
        const visuals = phaseVisuals(phase);
        this.runSync(this.appendWorkspace([
            "set-status",
            STATUS_KEY,
            label,
            "--icon",
            visuals.icon,
            "--color",
            visuals.color,
        ]));
    }
    clearStatus() {
        if (!this.config.sidebar)
            return;
        this.runSync(this.appendWorkspace(["clear-status", STATUS_KEY]));
    }
    setProgress(progress) {
        if (!this.config.sidebar)
            return;
        if (!progress) {
            this.runSync(this.appendWorkspace(["clear-progress"]));
            return;
        }
        this.runSync(this.appendWorkspace([
            "set-progress",
            progress.value.toFixed(3),
            "--label",
            progress.label,
        ]));
    }
    log(message, level = "info", source = "nerf") {
        if (!this.config.sidebar)
            return;
        this.runSync(this.appendWorkspace([
            "log",
            "--level",
            level,
            "--source",
            source,
            "--",
            message,
        ]));
    }
    notify(title, body, subtitle) {
        if (!this.config.notifications)
            return false;
        const args = ["notify", "--title", title, "--body", body];
        if (subtitle)
            args.push("--subtitle", subtitle);
        return this.runSync(args) !== null;
    }
    async listSurfaceIds() {
        const stdout = await this.runAsync(this.appendWorkspace(["list-surfaces", "--json", "--id-format", "both"]));
        const parsed = stdout ? parseJson(stdout) : null;
        return extractSurfaceIds(parsed);
    }
    async createSplit(direction) {
        return this.createSplitFrom(this.config.surfaceId, direction);
    }
    async createSplitFrom(sourceSurfaceId, direction) {
        if (!this.config.splits)
            return null;
        const before = new Set(await this.listSurfaceIds());
        const args = ["new-split", direction];
        const scopedArgs = this.appendSurface(this.appendWorkspace(args), sourceSurfaceId);
        await this.runAsync(scopedArgs);
        const after = await this.listSurfaceIds();
        for (const id of after) {
            if (!before.has(id))
                return id;
        }
        return null;
    }
    /**
     * Create a grid of surfaces for parallel agent execution.
     *
     * Layout strategy (nerf stays in the original surface):
     *   1 agent:  [nerf | A]
     *   2 agents: [nerf | A]
     *             [    | B]
     *   3 agents: [nerf | A]
     *             [ C  | B]
     *   4 agents: [nerf | A]
     *             [ C  | B]  (D splits from B downward)
     *             [    | D]
     *
     * Returns surface IDs in order, or empty array on failure.
     */
    async createGridLayout(count) {
        if (!this.config.splits || count <= 0)
            return [];
        const surfaces = [];
        // First split: create right column from the nerf surface
        const rightCol = await this.createSplitFrom(this.config.surfaceId, "right");
        if (!rightCol)
            return [];
        surfaces.push(rightCol);
        if (count === 1)
            return surfaces;
        // Second split: split right column down → bottom-right
        const bottomRight = await this.createSplitFrom(rightCol, "down");
        if (!bottomRight)
            return surfaces;
        surfaces.push(bottomRight);
        if (count === 2)
            return surfaces;
        // Third split: split nerf surface down → bottom-left
        const bottomLeft = await this.createSplitFrom(this.config.surfaceId, "down");
        if (!bottomLeft)
            return surfaces;
        surfaces.push(bottomLeft);
        if (count === 3)
            return surfaces;
        // Fourth+: split subsequent surfaces down from the last created
        let lastSurface = bottomRight;
        for (let i = 3; i < count; i++) {
            const next = await this.createSplitFrom(lastSurface, "down");
            if (!next)
                break;
            surfaces.push(next);
            lastSurface = next;
        }
        return surfaces;
    }
    async sendSurface(surfaceId, text) {
        const payload = text.endsWith("\n") ? text : `${text}\n`;
        const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
        return stdout !== null;
    }
}
export function syncCmuxSidebar(preferences, state) {
    const client = CmuxClient.fromPreferences(preferences);
    const config = client.getConfig();
    if (!config.sidebar)
        return;
    const label = buildCmuxStatusLabel(state);
    const progress = buildCmuxProgress(state);
    const snapshot = JSON.stringify({ label, progress, phase: state.phase });
    const key = sidebarSnapshotKey(config);
    if (lastSidebarSnapshots.get(key) === snapshot)
        return;
    client.setStatus(label, state.phase);
    client.setProgress(progress);
    lastSidebarSnapshots.set(key, snapshot);
}
export function clearCmuxSidebar(preferences) {
    const config = resolveCmuxConfig(preferences);
    if (!config.available || !config.cliAvailable)
        return;
    const client = new CmuxClient({ ...config, enabled: true, sidebar: true });
    const key = sidebarSnapshotKey(config);
    client.clearStatus();
    client.setProgress(null);
    lastSidebarSnapshots.delete(key);
}
export function logCmuxEvent(preferences, message, level = "info") {
    CmuxClient.fromPreferences(preferences).log(message, level);
}
export function shellEscape(value) {
    return `'${value.replace(/'/g, `'\\''`)}'`;
}
function normalizeNotificationText(value) {
    return value.replace(/\r?\n/g, " ").trim();
}
function parseJson(text) {
    try {
        return JSON.parse(text);
    }
    catch {
        return null;
    }
}
function extractSurfaceIds(value) {
    const found = new Set();
    const visit = (node) => {
        if (Array.isArray(node)) {
            for (const item of node)
                visit(item);
            return;
        }
        if (!node || typeof node !== "object")
            return;
        for (const [key, child] of Object.entries(node)) {
            if (typeof child === "string"
                && (key === "surface_id" || key === "surface" || (key === "id" && child.includes("surface")))) {
                found.add(child);
            }
            visit(child);
        }
    };
    visit(value);
    return Array.from(found);
}
