import { dirname } from "node:path";
import { RiskLevelSchema } from "../types.js";
import { formatPathForDisplay } from "../utils/format.js";
const DEFAULT_RISK_LEVELS = ["read-only", "read-write"];
const RISK_LEVEL_HELP_DESCRIPTIONS = {
    "read-only": "allow read-only tool calls",
    "read-write": "allow all tools",
};
export function getRiskLevelDescription(level) {
    switch (level) {
        case "read-only":
            return "read-only tools";
        case "read-write":
            return "all tools";
    }
}
export function getRiskLevelAutocompleteOptions(allowed) {
    return allowed.map((level) => ({
        id: level,
        description: RISK_LEVEL_HELP_DESCRIPTIONS[level],
    }));
}
function formatSkillPath(fullPath) {
    return formatPathForDisplay(dirname(dirname(fullPath)));
}
function splitCommandInput(raw) {
    const trimmed = raw.trim();
    const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/s);
    const command = match?.[1] ?? trimmed;
    const extra = match?.[2]?.trim();
    return extra ? { command, extra } : { command };
}
export class CommandRegistry {
    commands = [];
    byId = new Map();
    register(definition) {
        const def = definition;
        this.commands.push(def);
        this.byId.set(definition.id, def);
    }
    parse(raw) {
        const trimmed = raw.trim();
        for (const command of this.commands) {
            if (command.hidden)
                continue;
            const match = command.parse(trimmed);
            if (match)
                return match;
        }
        return { type: "unknown", raw: trimmed };
    }
    async dispatch(command, ctx) {
        const handler = this.byId.get(command.type);
        if (!handler)
            return;
        await handler.run(ctx, command);
    }
    list() {
        return this.commands
            .filter((command) => !command.hidden)
            .map(({ parse, run, hidden, ...info }) => info);
    }
    allowsDuringStreaming(command) {
        const handler = this.byId.get(command.type);
        return Boolean(handler?.allowDuringStreaming);
    }
    buildHelpText(options = {}) {
        const lines = [];
        const { agentsFiles, skills, riskLevels, themes } = options;
        if (agentsFiles && agentsFiles.length > 0) {
            lines.push("context:");
            agentsFiles.forEach((agentsFile) => {
                lines.push(`  ${formatPathForDisplay(agentsFile)}`);
            });
        }
        if (skills && skills.length > 0) {
            if (lines.length > 0) {
                lines.push("");
            }
            lines.push("skills:");
            skills.forEach((skill) => {
                lines.push(`  ${skill.name} (${formatSkillPath(skill.path)})`);
            });
        }
        if (lines.length > 0) {
            lines.push("");
        }
        const hasThemes = (themes?.length ?? 0) > 0;
        const commands = this.list().filter((command) => command.argument !== "theme" || hasThemes);
        const baseCommands = commands.filter((command) => command.section === "base");
        const trailingCommands = commands.filter((command) => command.section === "trailing");
        const riskCommand = commands.find((command) => command.section === "risk");
        const commandEntries = [];
        baseCommands.forEach((command) => {
            commandEntries.push([command.usage, command.description]);
        });
        if (riskCommand) {
            const allowed = riskLevels ?? DEFAULT_RISK_LEVELS;
            allowed.forEach((level) => {
                const description = RISK_LEVEL_HELP_DESCRIPTIONS[level];
                commandEntries.push([`/risk:${level}`, description]);
            });
        }
        trailingCommands.forEach((command) => {
            commandEntries.push([command.usage, command.description]);
        });
        const keyEntries = [
            ["shift+tab", "cycle reasoning effort"],
            ["ctrl+r", "cycle risk level"],
            ["ctrl+p", "cycle personality"],
            ["ctrl+t", "toggle thoughts visibility"],
            ["ctrl+o", "toggle compact tool UI"],
            ["ctrl+f", "expand @<file> and @@skill:<name> mentions"],
            ["ctrl+s", "stash input to clipboard"],
            ["ctrl+y", "toggle voice recording"],
            ["enter x2", "retry last response"],
            ["esc x2", "clear current prompt"],
            ["esc", "interrupt active task"],
        ];
        const commandPad = Math.max(...commandEntries.map(([cmd]) => cmd.length));
        const keyPad = Math.max(...keyEntries.map(([key]) => key.length));
        lines.push("commands:");
        for (const [cmd, desc] of commandEntries) {
            lines.push(`  ${cmd.padEnd(commandPad)}  ${desc}`);
        }
        lines.push("", "keys:");
        for (const [key, desc] of keyEntries) {
            lines.push(`  ${key.padEnd(keyPad)}  ${desc}`);
        }
        return lines.join("\n");
    }
}
export function createCommandRegistry() {
    const registry = new CommandRegistry();
    registry.register({
        id: "help",
        usage: "/help",
        description: "show this help",
        autocompleteDescription: "show help",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/help")
                return null;
            return { type: "help", extra };
        },
        run: (ctx) => ctx.help(),
    });
    registry.register({
        id: "new",
        usage: "/new",
        description: "new session",
        autocompleteDescription: "new session",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/new")
                return null;
            return { type: "new", extra };
        },
        run: (ctx) => ctx.newSession(),
    });
    registry.register({
        id: "rewind",
        usage: "/rewind",
        description: "rewind context to an earlier user message",
        autocompleteDescription: "rewind context to a selected user message",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/rewind")
                return null;
            return { type: "rewind", extra };
        },
        run: (ctx) => ctx.rewind(),
    });
    registry.register({
        id: "cd",
        usage: "/cd",
        description: "change working directory",
        autocompleteDescription: "change working directory",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/cd")
                return null;
            return { type: "cd", path: extra ?? "", extra };
        },
        run: (ctx, command) => ctx.cd(command.path),
    });
    registry.register({
        id: "diff",
        usage: "/diff [git diff args...]",
        description: "review a git diff with the external diff tool",
        autocompleteDescription: "review a git diff with the external diff tool",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/diff")
                return null;
            return { type: "diff", argsText: extra ?? "", extra };
        },
        run: (ctx, command) => ctx.diff(command.argsText),
    });
    registry.register({
        id: "compactSummaryOnly",
        usage: "/compact:summary-only",
        description: "summarize and start new session",
        autocompleteDescription: "compact history to a summary",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/compact:summary-only")
                return null;
            return { type: "compactSummaryOnly", extra };
        },
        run: (ctx, command) => ctx.compactSummaryOnly(command.extra),
    });
    registry.register({
        id: "compactSummaryAndLast",
        usage: "/compact:summary-and-last",
        description: "summarize and include previous last assistant message",
        autocompleteDescription: "compact history, keep last assistant message",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/compact:summary-and-last")
                return null;
            return { type: "compactSummaryAndLast", extra };
        },
        run: (ctx, command) => ctx.compactSummaryAndLast(command.extra),
    });
    registry.register({
        id: "pruneEarliest",
        usage: "/prune:earliest",
        description: "prune earliest tool results from context",
        autocompleteDescription: "prune earliest tool results",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/prune:earliest")
                return null;
            return { type: "pruneEarliest", extra };
        },
        run: (ctx, command) => ctx.pruneEarliest(command.extra),
    });
    registry.register({
        id: "pruneLargest",
        usage: "/prune:largest",
        description: "prune largest tool results from context",
        autocompleteDescription: "prune largest tool results",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/prune:largest")
                return null;
            return { type: "pruneLargest", extra };
        },
        run: (ctx, command) => ctx.pruneLargest(command.extra),
    });
    registry.register({
        id: "pruneSmart",
        usage: "/prune:smart",
        description: "prune tool results using model selection",
        autocompleteDescription: "prune tool results with model selection",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/prune:smart")
                return null;
            return { type: "pruneSmart", extra };
        },
        run: (ctx, command) => ctx.pruneSmart(command.extra),
    });
    registry.register({
        id: "reload",
        usage: "/reload",
        description: "reload prompts, skills, themes, bash commands, and AGENTS.md",
        autocompleteDescription: "reload prompts, skills, themes, bash commands, and AGENTS.md",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/reload")
                return null;
            return { type: "reload", extra };
        },
        run: (ctx) => ctx.reload(),
    });
    registry.register({
        id: "listen",
        usage: "/listen",
        description: "start voice recording and transcription",
        autocompleteDescription: "start voice recording",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/listen")
                return null;
            return { type: "listen", extra };
        },
        run: (ctx) => ctx.listen(),
    });
    registry.register({
        id: "speak",
        usage: "/speak",
        description: "speak the last assistant message aloud",
        autocompleteDescription: "speak the last assistant message",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/speak")
                return null;
            return { type: "speak", extra };
        },
        run: (ctx) => ctx.speak(),
    });
    registry.register({
        id: "copyText",
        usage: "/copy:text",
        description: "copy last assistant message",
        autocompleteDescription: "copy last assistant message",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/copy:text")
                return null;
            return { type: "copyText", extra };
        },
        run: (ctx) => ctx.copyText(),
    });
    registry.register({
        id: "copyCode",
        usage: "/copy:code",
        description: "copy code blocks from last assistant message",
        autocompleteDescription: "copy code blocks from last assistant message",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/copy:code")
                return null;
            return { type: "copyCode", extra };
        },
        run: (ctx) => ctx.copyCode(),
    });
    registry.register({
        id: "checkpoint",
        usage: "/checkpoint",
        description: "save a checkpoint file",
        autocompleteDescription: "save a checkpoint file",
        argument: "none",
        section: "base",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            if (command !== "/checkpoint")
                return null;
            return { type: "checkpoint", extra };
        },
        run: (ctx) => ctx.checkpoint(),
    });
    registry.register({
        id: "risk",
        usage: "/risk:<level>",
        description: "set risk level",
        argument: "risk",
        section: "risk",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            const match = command.match(/^\/risk:(read-only|read-write)$/i);
            if (!match)
                return null;
            const parsed = RiskLevelSchema.safeParse(match[1]?.toLowerCase());
            if (!parsed.success)
                return null;
            return { type: "risk", level: parsed.data, extra };
        },
        run: (ctx, command) => ctx.risk(command.level),
    });
    registry.register({
        id: "bash",
        usage: "/bash:<id>",
        description: "run saved bash command",
        argument: "bash",
        section: "trailing",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            const match = command.match(/^\/bash:(.+)$/i);
            const id = match?.[1]?.trim() ?? "";
            if (!id)
                return null;
            return { type: "bash", id, extra };
        },
        run: (ctx, command) => ctx.bash(command.id),
    });
    registry.register({
        id: "persona",
        usage: "/persona:<id>",
        description: "switch persona",
        argument: "persona",
        section: "trailing",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            const match = command.match(/^\/persona:(.+)$/i);
            const id = match?.[1]?.trim() ?? "";
            if (!id)
                return null;
            return { type: "persona", id, extra };
        },
        run: (ctx, command) => ctx.persona(command.id),
    });
    registry.register({
        id: "prompt",
        usage: "/prompt:<id>",
        description: "insert prompt template",
        argument: "prompt",
        section: "trailing",
        allowDuringStreaming: true,
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            const match = command.match(/^\/prompt:(.+)$/i);
            const id = match?.[1]?.trim() ?? "";
            if (!id)
                return null;
            return { type: "prompt", id, extra };
        },
        run: (ctx, command) => ctx.prompt(command.id),
    });
    registry.register({
        id: "theme",
        usage: "/theme:<id>",
        description: "switch theme",
        argument: "theme",
        section: "trailing",
        parse: (raw) => {
            const { command, extra } = splitCommandInput(raw);
            const match = command.match(/^\/theme:(.+)$/i);
            const id = match?.[1]?.trim() ?? "";
            if (!id)
                return null;
            return { type: "theme", id, extra };
        },
        run: (ctx, command) => ctx.theme(command.id),
    });
    registry.register({
        id: "unknown",
        usage: "",
        description: "",
        argument: "none",
        section: "base",
        hidden: true,
        parse: () => null,
        run: (ctx, command) => ctx.unknown(command.raw),
    });
    return registry;
}
//# sourceMappingURL=registry.js.map