/**
 * Slash command dispatch and handler implementations extracted from InteractiveMode.
 *
 * The `dispatchSlashCommand` function contains the dispatch logic (routing text
 * to handlers), and individual handler functions implement each command.
 *
 * Handlers that are also invoked from keybindings or other subsystems remain on
 * InteractiveMode and are called through the `SlashCommandContext` interface.
 */
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Markdown, Spacer, Text, } from "@nerf/pi-tui";
import { spawn, spawnSync } from "child_process";
import { getShareViewerUrl, } from "../../config.js";
import { copyToClipboard } from "../../utils/clipboard.js";
import { getChangelogPath, parseChangelog } from "../../utils/changelog.js";
import { ArminComponent } from "./components/armin.js";
import { BorderedLoader } from "./components/bordered-loader.js";
import { DynamicBorder } from "./components/dynamic-border.js";
import { appKey, editorKey, formatKeyForDisplay } from "./components/keybinding-hints.js";
import { SelectSubmenu, THINKING_DESCRIPTIONS } from "./components/settings-selector.js";
import { theme } from "./theme/theme.js";
// ---------------------------------------------------------------------------
// Dispatch
// ---------------------------------------------------------------------------
/**
 * Routes a slash command string to the appropriate handler.
 *
 * @returns `true` if the text was handled as a slash command (caller should
 *          not process it further), `false` otherwise.
 */
export async function dispatchSlashCommand(text, ctx) {
    if (text === "/settings") {
        ctx.showSettingsSelector();
        return true;
    }
    if (text === "/scoped-models") {
        await ctx.showModelsSelector();
        return true;
    }
    if (text === "/model" || text.startsWith("/model ")) {
        const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
        await ctx.handleModelCommand(searchTerm);
        return true;
    }
    if (text.startsWith("/export")) {
        await handleExportCommand(text, ctx);
        return true;
    }
    if (text === "/share") {
        await handleShareCommand(ctx);
        return true;
    }
    if (text === "/copy") {
        handleCopyCommand(ctx);
        return true;
    }
    if (text === "/name" || text.startsWith("/name ")) {
        handleNameCommand(text, ctx);
        return true;
    }
    if (text === "/session") {
        handleSessionCommand(ctx);
        return true;
    }
    if (text === "/changelog") {
        handleChangelogCommand(ctx);
        return true;
    }
    if (text === "/hotkeys") {
        handleHotkeysCommand(ctx);
        return true;
    }
    if (text === "/fork") {
        ctx.showUserMessageSelector();
        return true;
    }
    if (text === "/tree") {
        ctx.showTreeSelector();
        return true;
    }
    if (text === "/provider") {
        ctx.showProviderManager();
        return true;
    }
    if (text === "/login") {
        await ctx.showOAuthSelector("login");
        return true;
    }
    if (text === "/logout") {
        await ctx.showOAuthSelector("logout");
        return true;
    }
    if (text === "/new") {
        await ctx.handleClearCommand();
        return true;
    }
    if (text === "/compact" || text.startsWith("/compact ")) {
        const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
        await handleCompactCommand(customInstructions, ctx);
        return true;
    }
    if (text === "/reload") {
        await ctx.handleReloadCommand();
        return true;
    }
    if (text === "/thinking" || text.startsWith("/thinking ")) {
        const arg = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined;
        handleThinkingCommand(arg, ctx);
        return true;
    }
    if (text === "/edit-mode" || text.startsWith("/edit-mode ")) {
        const arg = text.startsWith("/edit-mode ") ? text.slice(11).trim() : undefined;
        handleEditModeCommand(arg, ctx);
        return true;
    }
    if (text === "/debug") {
        ctx.handleDebugCommand();
        return true;
    }
    if (text === "/arminsayshi") {
        handleArminSaysHi(ctx);
        return true;
    }
    if (text === "/resume") {
        ctx.showSessionSelector();
        return true;
    }
    if (text === "/quit") {
        await ctx.shutdown();
        return true;
    }
    if (text === "/terminal" || text.startsWith("/terminal ")) {
        const command = text.startsWith("/terminal ") ? text.slice(10).trim() : "";
        if (!command) {
            ctx.showWarning("Usage: /terminal <command>  (e.g. /terminal ping -c3 1.1.1.1)");
            return true;
        }
        // Run in the user's login shell ($SHELL -l -c) so PATH additions
        // and env vars from shell profiles (.zprofile/.profile) are available.
        // Note: shell aliases are not loaded (requires -i which has side effects).
        await ctx.handleBashCommand(command, { loginShell: true });
        return true;
    }
    return false;
}
// ---------------------------------------------------------------------------
// Individual command handlers
// ---------------------------------------------------------------------------
async function handleExportCommand(text, ctx) {
    const parts = text.split(/\s+/);
    const outputPath = parts.length > 1 ? parts[1] : undefined;
    try {
        const filePath = await ctx.session.exportToHtml(outputPath);
        ctx.showStatus(`Session exported to: ${filePath}`);
    }
    catch (error) {
        ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
    }
}
async function handleShareCommand(ctx) {
    // Check if gh is available and logged in
    try {
        const authResult = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
        if (authResult.status !== 0) {
            ctx.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
            return;
        }
    }
    catch {
        ctx.showError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/");
        return;
    }
    // Export to a temp file
    const tmpFile = path.join(os.tmpdir(), "session.html");
    try {
        await ctx.session.exportToHtml(tmpFile);
    }
    catch (error) {
        ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
        return;
    }
    // Show cancellable loader, replacing the editor
    const loader = new BorderedLoader(ctx.ui, theme, "Creating gist...");
    ctx.editorContainer.clear();
    ctx.editorContainer.addChild(loader);
    ctx.ui.setFocus(loader);
    ctx.requestRender();
    const restoreEditor = () => {
        loader.dispose();
        ctx.editorContainer.clear();
        ctx.editorContainer.addChild(ctx.editor);
        ctx.ui.setFocus(ctx.editor);
        try {
            fs.unlinkSync(tmpFile);
        }
        catch {
            // Ignore cleanup errors
        }
    };
    // Create a secret gist asynchronously
    let proc = null;
    loader.onAbort = () => {
        proc?.kill();
        restoreEditor();
        ctx.showStatus("Share cancelled");
    };
    try {
        const result = await new Promise((resolve) => {
            proc = spawn("gh", ["gist", "create", "--public=false", tmpFile]);
            let stdout = "";
            let stderr = "";
            proc.stdout?.on("data", (data) => {
                stdout += data.toString();
            });
            proc.stderr?.on("data", (data) => {
                stderr += data.toString();
            });
            proc.on("close", (code) => resolve({ stdout, stderr, code }));
        });
        if (loader.signal.aborted)
            return;
        restoreEditor();
        if (result.code !== 0) {
            const errorMsg = result.stderr?.trim() || "Unknown error";
            ctx.showError(`Failed to create gist: ${errorMsg}`);
            return;
        }
        // Extract gist ID from the URL returned by gh
        // gh returns something like: https://gist.github.com/username/GIST_ID
        const gistUrl = result.stdout?.trim();
        const gistId = gistUrl?.split("/").pop();
        if (!gistId) {
            ctx.showError("Failed to parse gist ID from gh output");
            return;
        }
        // Create the preview URL
        const previewUrl = getShareViewerUrl(gistId);
        ctx.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
    }
    catch (error) {
        if (!loader.signal.aborted) {
            restoreEditor();
            ctx.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
        }
    }
}
function handleCopyCommand(ctx) {
    const text = ctx.session.getLastAssistantText();
    if (!text) {
        ctx.showError("No agent messages to copy yet.");
        return;
    }
    try {
        copyToClipboard(text);
        ctx.showStatus("Copied last agent message to clipboard");
    }
    catch (error) {
        ctx.showError(error instanceof Error ? error.message : String(error));
    }
}
function handleNameCommand(text, ctx) {
    const name = text.replace(/^\/name\s*/, "").trim();
    if (!name) {
        const currentName = ctx.sessionManager.getSessionName();
        if (currentName) {
            ctx.chatContainer.addChild(new Spacer(1));
            ctx.chatContainer.addChild(new Text(theme.fg("dim", `Session name: ${currentName}`), 1, 0));
        }
        else {
            ctx.showWarning("Usage: /name <name>");
        }
        ctx.requestRender();
        return;
    }
    ctx.sessionManager.appendSessionInfo(name);
    ctx.updateTerminalTitle();
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
    ctx.requestRender();
}
function handleSessionCommand(ctx) {
    const stats = ctx.session.getSessionStats();
    const sessionName = ctx.sessionManager.getSessionName();
    let info = `${theme.bold("Session Info")}\n\n`;
    if (sessionName) {
        info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
    }
    info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
    info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
    info += `${theme.bold("Messages")}\n`;
    info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
    info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
    info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
    info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
    info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
    info += `${theme.bold("Tokens")}\n`;
    info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
    info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
    if (stats.tokens.cacheRead > 0) {
        info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
    }
    if (stats.tokens.cacheWrite > 0) {
        info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
    }
    info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
    if (stats.cost > 0) {
        info += `\n${theme.bold("Cost")}\n`;
        info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
    }
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new Text(info, 1, 0));
    ctx.requestRender();
}
function handleChangelogCommand(ctx) {
    const changelogPath = getChangelogPath();
    const allEntries = parseChangelog(changelogPath);
    const changelogMarkdown = allEntries.length > 0
        ? allEntries
            .reverse()
            .map((e) => e.content)
            .join("\n\n")
        : "No changelog entries found.";
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new DynamicBorder());
    ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, ctx.getMarkdownThemeWithSettings()));
    ctx.chatContainer.addChild(new DynamicBorder());
    ctx.requestRender();
}
// ---------------------------------------------------------------------------
// /hotkeys helpers
// ---------------------------------------------------------------------------
export function capitalizeKey(key) {
    return key
        .split("/")
        .map((k) => k
        .split("+")
        .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
        .join("+"))
        .join("/");
}
export function getAppKeyDisplay(keybindings, action) {
    return capitalizeKey(appKey(keybindings, action));
}
function getEditorKeyDisplay(action) {
    return capitalizeKey(editorKey(action));
}
function handleHotkeysCommand(ctx) {
    // Navigation keybindings
    const cursorWordLeft = getEditorKeyDisplay("cursorWordLeft");
    const cursorWordRight = getEditorKeyDisplay("cursorWordRight");
    const cursorLineStart = getEditorKeyDisplay("cursorLineStart");
    const cursorLineEnd = getEditorKeyDisplay("cursorLineEnd");
    const jumpForward = getEditorKeyDisplay("jumpForward");
    const jumpBackward = getEditorKeyDisplay("jumpBackward");
    const pageUp = getEditorKeyDisplay("pageUp");
    const pageDown = getEditorKeyDisplay("pageDown");
    // Editing keybindings
    const submit = getEditorKeyDisplay("submit");
    const newLine = getEditorKeyDisplay("newLine");
    const deleteWordBackward = getEditorKeyDisplay("deleteWordBackward");
    const deleteWordForward = getEditorKeyDisplay("deleteWordForward");
    const deleteToLineStart = getEditorKeyDisplay("deleteToLineStart");
    const deleteToLineEnd = getEditorKeyDisplay("deleteToLineEnd");
    const yank = getEditorKeyDisplay("yank");
    const yankPop = getEditorKeyDisplay("yankPop");
    const undo = getEditorKeyDisplay("undo");
    const tab = getEditorKeyDisplay("tab");
    // App keybindings
    const interrupt = getAppKeyDisplay(ctx.keybindings, "interrupt");
    const clear = getAppKeyDisplay(ctx.keybindings, "clear");
    const exit = getAppKeyDisplay(ctx.keybindings, "exit");
    const suspend = getAppKeyDisplay(ctx.keybindings, "suspend");
    const cycleThinkingLevel = getAppKeyDisplay(ctx.keybindings, "cycleThinkingLevel");
    const cycleModelForward = getAppKeyDisplay(ctx.keybindings, "cycleModelForward");
    const selectModel = getAppKeyDisplay(ctx.keybindings, "selectModel");
    const expandTools = getAppKeyDisplay(ctx.keybindings, "expandTools");
    const toggleThinking = getAppKeyDisplay(ctx.keybindings, "toggleThinking");
    const externalEditor = getAppKeyDisplay(ctx.keybindings, "externalEditor");
    const followUp = getAppKeyDisplay(ctx.keybindings, "followUp");
    const dequeue = getAppKeyDisplay(ctx.keybindings, "dequeue");
    let hotkeys = `
**Navigation**
| Key | Action |
|-----|--------|
| \`Arrow keys\` | Move cursor / browse history (Up when empty) |
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
| \`${cursorLineStart}\` | Start of line |
| \`${cursorLineEnd}\` | End of line |
| \`${jumpForward}\` | Jump forward to character |
| \`${jumpBackward}\` | Jump backward to character |
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |

**Editing**
| Key | Action |
|-----|--------|
| \`${submit}\` | Send message |
| \`${newLine}\` | New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |
| \`${deleteWordBackward}\` | Delete word backwards |
| \`${deleteWordForward}\` | Delete word forwards |
| \`${deleteToLineStart}\` | Delete to start of line |
| \`${deleteToLineEnd}\` | Delete to end of line |
| \`${yank}\` | Paste the most-recently-deleted text |
| \`${yankPop}\` | Cycle through the deleted text after pasting |
| \`${undo}\` | Undo |

**Other**
| Key | Action |
|-----|--------|
| \`${tab}\` | Path completion / accept autocomplete |
| \`${interrupt}\` | Cancel autocomplete / abort streaming |
| \`${clear}\` | Clear editor (first) / exit (second) |
| \`${exit}\` | Exit (when editor is empty) |
| \`${suspend}\` | Suspend to background |
| \`${cycleThinkingLevel}\` | Cycle thinking level |
| \`${cycleModelForward}\` | Cycle models |
| \`${selectModel}\` | Open model selector |
| \`${expandTools}\` | Toggle tool output expansion |
| \`${toggleThinking}\` | Toggle thinking block visibility |
| \`${externalEditor}\` | Edit message in external editor |
| \`${followUp}\` | Queue follow-up message |
| \`${dequeue}\` | Restore queued messages |
| \`Ctrl+V\` | Paste image from clipboard |
| \`/\` | Slash commands |
| \`!\` | Run bash command |
| \`!!\` | Run bash command (excluded from context) |
`;
    // Add extension-registered shortcuts
    const extensionRunner = ctx.session.extensionRunner;
    if (extensionRunner) {
        const shortcuts = extensionRunner.getShortcuts(ctx.keybindings.getEffectiveConfig());
        if (shortcuts.size > 0) {
            hotkeys += `
**Extensions**
| Key | Action |
|-----|--------|
`;
            for (const [key, shortcut] of shortcuts) {
                const description = shortcut.description ?? shortcut.extensionPath;
                const keyDisplay = formatKeyForDisplay(key).replace(/\b\w/g, (c) => c.toUpperCase());
                hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
            }
        }
    }
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new DynamicBorder());
    ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, ctx.getMarkdownThemeWithSettings()));
    ctx.chatContainer.addChild(new DynamicBorder());
    ctx.requestRender();
}
async function handleCompactCommand(customInstructions, ctx) {
    const entries = ctx.sessionManager.getEntries();
    const messageCount = entries.filter((e) => e.type === "message").length;
    if (messageCount < 2) {
        ctx.showWarning("Nothing to compact (no messages yet)");
        return;
    }
    await ctx.executeCompaction(customInstructions, false);
}
function handleThinkingCommand(arg, ctx) {
    if (!ctx.session.supportsThinking()) {
        ctx.showStatus("Current model does not support thinking");
        return;
    }
    const availableLevels = ctx.session.getAvailableThinkingLevels();
    if (arg) {
        const level = arg.toLowerCase();
        if (!availableLevels.includes(level)) {
            ctx.showStatus(`Invalid thinking level "${arg}". Available: ${availableLevels.join(", ")}`);
            return;
        }
        ctx.session.setThinkingLevel(level);
        ctx.invalidateFooter();
        ctx.updateEditorBorderColor();
        ctx.showStatus(`Thinking level: ${level}`);
        return;
    }
    showThinkingSelector(ctx, availableLevels);
}
function showThinkingSelector(ctx, availableLevels) {
    ctx.showSelector((done) => {
        const selector = new SelectSubmenu("Thinking Level", "Select reasoning depth for thinking-capable models", availableLevels.map((level) => ({
            value: level,
            label: level,
            description: THINKING_DESCRIPTIONS[level],
        })), ctx.session.thinkingLevel, (value) => {
            ctx.session.setThinkingLevel(value);
            ctx.invalidateFooter();
            ctx.updateEditorBorderColor();
            done();
            ctx.showStatus(`Thinking level: ${value}`);
        }, () => {
            done();
        });
        return { component: selector, focus: selector };
    });
}
function handleEditModeCommand(arg, ctx) {
    const modes = ["standard", "hashline"];
    if (arg) {
        const mode = arg.toLowerCase();
        if (!modes.includes(mode)) {
            ctx.showStatus(`Invalid edit mode "${arg}". Available: standard, hashline`);
            return;
        }
        ctx.session.setEditMode(mode);
        ctx.showStatus(`Edit mode: ${mode}${mode === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`);
        return;
    }
    // Toggle
    const current = ctx.session.editMode;
    const next = current === "standard" ? "hashline" : "standard";
    ctx.session.setEditMode(next);
    ctx.showStatus(`Edit mode: ${next}${next === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`);
}
function handleArminSaysHi(ctx) {
    ctx.chatContainer.addChild(new Spacer(1));
    ctx.chatContainer.addChild(new ArminComponent(ctx.ui));
    ctx.requestRender();
}
//# sourceMappingURL=slash-command-handlers.js.map