import * as readline from 'readline';
import { readFile, writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { fileExists, getMcpcHome } from '../lib/utils.js';
import { formatError, logTarget } from './output.js';
import chalk from 'chalk';
import * as tools from './commands/tools.js';
import * as resources from './commands/resources.js';
import * as prompts from './commands/prompts.js';
import * as logging from './commands/logging.js';
import { ping } from './commands/utilities.js';
import { createSessionClient } from '../lib/session-client.js';
import { parseShellCommand } from './shell-parser.js';
const HISTORY_MAX_COMMANDS = 1000;
const HISTORY_FILE = 'shell-history';
async function loadHistory() {
    const historyPath = join(getMcpcHome(), HISTORY_FILE);
    if (!(await fileExists(historyPath))) {
        return [];
    }
    try {
        const content = await readFile(historyPath, 'utf-8');
        return content.split('\n').filter((line) => line.trim().length > 0);
    }
    catch {
        return [];
    }
}
async function saveHistory(history) {
    const historyPath = join(getMcpcHome(), HISTORY_FILE);
    await mkdir(getMcpcHome(), { recursive: true });
    const toSave = history.slice(-HISTORY_MAX_COMMANDS);
    try {
        await writeFile(historyPath, toSave.join('\n') + '\n', 'utf-8');
    }
    catch {
    }
}
function addToHistory(ctx, line) {
    const trimmed = line.trim();
    if (!trimmed || trimmed === ctx.history[ctx.history.length - 1]) {
        return;
    }
    ctx.history.push(trimmed);
}
function displayNotification(notification) {
    const timestamp = new Date().toLocaleTimeString();
    let message = '';
    switch (notification.method) {
        case 'tools/list_changed':
            message = chalk.yellow(`[${timestamp}] Server tools list changed`);
            break;
        case 'resources/list_changed':
            message = chalk.yellow(`[${timestamp}] Server resources list changed`);
            break;
        case 'prompts/list_changed':
            message = chalk.yellow(`[${timestamp}] Server prompts list changed`);
            break;
        case 'resources/updated':
            message = chalk.yellow(`[${timestamp}] Resource updated`);
            break;
        case 'progress':
            message = chalk.cyan(`[${timestamp}] Progress: ${JSON.stringify(notification.params)}`);
            break;
        case 'logging/message':
            message = chalk.gray(`[${timestamp}] Server log: ${JSON.stringify(notification.params)}`);
            break;
        default:
            message = chalk.dim(`[${timestamp}] Notification: ${String(notification.method)}`);
    }
    console.log(message);
}
async function setupNotificationListener(ctx) {
    try {
        if (!ctx.target.startsWith('@')) {
            return;
        }
        ctx.notificationClient = await createSessionClient(ctx.target);
        ctx.notificationClient.on('notification', (notification) => {
            displayNotification(notification);
        });
    }
    catch {
    }
}
function showShellHelp() {
    console.log(chalk.bold('\nAvailable commands:'));
    console.log('');
    console.log(chalk.cyan('  MCP commands:'));
    console.log('    tools-list');
    console.log('    tools-get <name>');
    console.log('    tools-call <name> [key:=value ...]');
    console.log('    resources-list');
    console.log('    resources-read <uri>');
    console.log('    resources-templates-list');
    console.log('    prompts-list');
    console.log('    prompts-get <name> [key:=value ...]');
    console.log('    logging-set-level <level>');
    console.log('    ping');
    console.log('');
    console.log(chalk.cyan('  Shell commands:'));
    console.log('    help              Show this help message');
    console.log('    exit, quit        Exit the shell');
    console.log('    clear             Clear the screen');
    console.log('');
}
async function executeCommand(ctx, line) {
    const { command, args } = parseShellCommand(line);
    if (!command) {
        return;
    }
    if (command === 'help') {
        showShellHelp();
        return;
    }
    if (command === 'exit' || command === 'quit') {
        ctx.running = false;
        return;
    }
    if (command === 'clear') {
        console.clear();
        return;
    }
    const options = {
        outputMode: 'human',
        hideTarget: true,
    };
    try {
        switch (command) {
            case 'ping':
                await ping(ctx.target, options);
                break;
            case 'tools':
            case 'tools-list':
                await tools.listTools(ctx.target, options);
                break;
            case 'tools-get': {
                if (args.length === 0) {
                    console.log(chalk.red('Error: tools-get requires a tool name'));
                    console.log('Usage: tools-get <name>');
                    return;
                }
                await tools.getTool(ctx.target, args[0], options);
                break;
            }
            case 'tools-call': {
                if (args.length === 0) {
                    console.log(chalk.red('Error: tools-call requires a tool name'));
                    console.log('Usage: tools-call <name> [key:=value ...]');
                    return;
                }
                const toolName = args[0];
                const toolArgs = args.slice(1);
                await tools.callTool(ctx.target, toolName, {
                    ...options,
                    ...(toolArgs.length > 0 ? { args: toolArgs } : {}),
                });
                break;
            }
            case 'resources':
            case 'resources-list':
                await resources.listResources(ctx.target, options);
                break;
            case 'resources-read': {
                if (args.length === 0) {
                    console.log(chalk.red('Error: resources-read requires a URI'));
                    console.log('Usage: resources-read <uri>');
                    return;
                }
                await resources.getResource(ctx.target, args[0], options);
                break;
            }
            case 'resources-templates-list':
                await resources.listResourceTemplates(ctx.target, options);
                break;
            case 'prompts':
            case 'prompts-list':
                await prompts.listPrompts(ctx.target, options);
                break;
            case 'prompts-get': {
                if (args.length === 0) {
                    console.log(chalk.red('Error: prompts-get requires a prompt name'));
                    console.log('Usage: prompts-get <name> [key:=value ...]');
                    return;
                }
                const promptName = args[0];
                const promptArgs = args.slice(1);
                await prompts.getPrompt(ctx.target, promptName, {
                    ...options,
                    ...(promptArgs.length > 0 ? { args: promptArgs } : {}),
                });
                break;
            }
            case 'logging-set-level': {
                if (args.length === 0) {
                    console.log(chalk.red('Error: logging-set-level requires a level'));
                    console.log('Usage: logging-set-level <level>');
                    return;
                }
                await logging.setLogLevel(ctx.target, args[0], options);
                break;
            }
            case 'shell': {
                const shellMessages = [
                    '🐚 Shell-ception! You\'re already in a shell.',
                    '🪆 It\'s shells all the way down...',
                    '🎭 Ha, nice try!',
                    '🔄 Yo dawg, I heard you like shells...',
                    '🌀 Recursion limit reached. Just kidding.',
                    '🐢 A shell inside a shell? How very turtles-all-the-way-down of you.',
                    '🎪 Welcome to shell² — same great shell, same great location!',
                    '🪞 *shell stares back at you*',
                ];
                const message = shellMessages[Math.floor(Math.random() * shellMessages.length)];
                console.log(chalk.yellow(message));
                break;
            }
            default:
                console.log(chalk.red(`Unknown command: ${command}`));
                console.log(chalk.dim('Type "help" for available commands'));
        }
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        console.error(formatError(errorMessage));
    }
}
async function shellLoop(ctx) {
    const historyReversed = ctx.history.slice(-HISTORY_MAX_COMMANDS).reverse();
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        history: historyReversed,
        historySize: HISTORY_MAX_COMMANDS,
        prompt: chalk.cyan(`mcpc(${ctx.target})> `),
        terminal: true,
    });
    rl.on('line', (line) => {
        addToHistory(ctx, line);
        void executeCommand(ctx, line).then(() => {
            if (ctx.running) {
                rl.prompt();
            }
            else {
                rl.close();
            }
        });
    });
    rl.on('SIGINT', () => {
        console.log('');
        rl.prompt();
    });
    rl.on('close', () => {
        ctx.running = false;
        console.log(chalk.dim('Goodbye!'));
    });
    rl.prompt();
    await new Promise((resolve) => {
        rl.on('close', resolve);
    });
}
export async function startShell(target) {
    const ctx = {
        target,
        history: await loadHistory(),
        running: true,
    };
    await logTarget(target, { outputMode: 'human' });
    console.log(chalk.bold(`Welcome to mcpc shell`));
    console.log(chalk.dim('Type "help" for available commands, "exit" to quit\n'));
    await setupNotificationListener(ctx);
    const cleanup = async () => {
        await saveHistory(ctx.history);
        if (ctx.notificationClient) {
            await ctx.notificationClient.close();
        }
    };
    process.on('SIGINT', () => {
        ctx.running = false;
    });
    process.on('SIGTERM', () => {
        ctx.running = false;
    });
    try {
        await shellLoop(ctx);
    }
    finally {
        await cleanup();
    }
}
//# sourceMappingURL=shell.js.map