import { z } from 'zod';
import { Writable } from 'node:stream';
import { createCommand } from '../../../types';
import * as tui from '../../../tui';
import { createSandboxClient } from './util';
import { getCommand } from '../../../command-prefix';
import { sandboxExecute, executionGet, writeAndDrain, sandboxResolve } from '@agentuity/server';
// Server-side long-poll wait duration (max 5 minutes supported by server)
const EXECUTION_WAIT_DURATION = '5m';
const SandboxExecResponseSchema = z.object({
    executionId: z.string().describe('Unique execution identifier'),
    status: z.string().describe('Execution status'),
    exitCode: z.number().optional().describe('Exit code (if completed)'),
    durationMs: z.number().optional().describe('Duration in milliseconds (if completed)'),
    output: z.string().optional().describe('Combined stdout/stderr output'),
});
export const execSubcommand = createCommand({
    name: 'exec',
    aliases: ['execute'],
    description: 'Execute a command in a running sandbox',
    tags: ['slow', 'requires-auth'],
    requires: { auth: true, apiClient: true },
    examples: [
        {
            command: getCommand('cloud sandbox exec abc123 -- echo "hello"'),
            description: 'Execute a command in a sandbox',
        },
        {
            command: getCommand('cloud sandbox exec abc123 --timeout 5m -- bun run build'),
            description: 'Execute with timeout',
        },
    ],
    schema: {
        args: z.object({
            sandboxId: z.string().describe('Sandbox ID'),
            command: z.array(z.string()).describe('Command and arguments to execute'),
        }),
        options: z.object({
            timeout: z.string().optional().describe('Execution timeout (e.g., "5m", "1h")'),
            timestamps: z
                .boolean()
                .default(false)
                .optional()
                .describe('Include timestamps in output (default: false)'),
        }),
        response: SandboxExecResponseSchema,
    },
    async handler(ctx) {
        const { args, opts, options, auth, logger, apiClient } = ctx;
        // Resolve sandbox to get region and orgId using CLI API
        const sandboxInfo = await sandboxResolve(apiClient, args.sandboxId);
        const { region, orgId } = sandboxInfo;
        const client = createSandboxClient(logger, auth, region);
        const started = Date.now();
        const abortController = new AbortController();
        const handleSignal = () => {
            abortController.abort();
        };
        process.on('SIGINT', handleSignal);
        process.on('SIGTERM', handleSignal);
        const outputChunks = [];
        // For JSON output, capture to buffer; otherwise stream to process
        const stdout = options.json
            ? createCaptureStream((chunk) => outputChunks.push(chunk))
            : process.stdout;
        const stderr = options.json
            ? createCaptureStream((chunk) => outputChunks.push(chunk))
            : process.stderr;
        try {
            const execution = await sandboxExecute(client, {
                sandboxId: args.sandboxId,
                options: {
                    command: args.command,
                    timeout: opts.timeout,
                    stream: opts.timestamps !== undefined ? { timestamps: opts.timestamps } : undefined,
                },
                orgId,
            });
            const stdoutStreamUrl = execution.stdoutStreamUrl;
            const stderrStreamUrl = execution.stderrStreamUrl;
            const streamAbortController = new AbortController();
            const streamPromises = [];
            // Check if stdout and stderr are the same stream (combined output)
            const isCombinedOutput = stdoutStreamUrl && stderrStreamUrl && stdoutStreamUrl === stderrStreamUrl;
            if (isCombinedOutput) {
                // Stream combined output to stdout only to avoid duplicates
                logger.debug('using combined output stream (stdout === stderr): %s', stdoutStreamUrl);
                streamPromises.push(streamUrlToWritable(stdoutStreamUrl, stdout, streamAbortController.signal, logger));
            }
            else {
                if (stdoutStreamUrl) {
                    logger.debug('starting stdout stream from: %s', stdoutStreamUrl);
                    streamPromises.push(streamUrlToWritable(stdoutStreamUrl, stdout, streamAbortController.signal, logger));
                }
                if (stderrStreamUrl) {
                    logger.debug('starting stderr stream from: %s', stderrStreamUrl);
                    streamPromises.push(streamUrlToWritable(stderrStreamUrl, stderr, streamAbortController.signal, logger));
                }
            }
            // Use server-side long-polling to wait for execution completion
            // This is more efficient than client-side polling and provides immediate
            // error detection if the sandbox is terminated
            const finalExecution = await executionGet(client, {
                executionId: execution.executionId,
                orgId,
                wait: EXECUTION_WAIT_DURATION,
            });
            // Wait for all streams to reach EOF (Pulse blocks until true EOF)
            await Promise.all(streamPromises);
            // Ensure stdout is fully flushed before continuing
            if (!options.json && process.stdout.writable) {
                await new Promise((resolve) => {
                    if (process.stdout.writableNeedDrain) {
                        process.stdout.once('drain', () => resolve());
                    }
                    else {
                        resolve();
                    }
                });
            }
            const duration = Date.now() - started;
            const output = outputChunks.join('');
            if (!options.json) {
                if (finalExecution.exitCode === 0) {
                    // no op
                }
                else if (finalExecution.exitCode !== undefined) {
                    tui.error(`failed with exit code ${finalExecution.exitCode} in ${duration}ms`);
                }
                else {
                    tui.info(`Execution ${tui.bold(finalExecution.executionId)} - Status: ${finalExecution.status}`);
                }
            }
            return {
                executionId: finalExecution.executionId,
                status: finalExecution.status,
                exitCode: finalExecution.exitCode,
                durationMs: finalExecution.durationMs,
                output: options.json ? output : undefined,
            };
        }
        finally {
            process.off('SIGINT', handleSignal);
            process.off('SIGTERM', handleSignal);
        }
    },
});
async function streamUrlToWritable(url, writable, signal, logger) {
    try {
        logger.debug('fetching stream: %s', url);
        const response = await fetch(url, { signal });
        logger.debug('stream response status: %d', response.status);
        if (!response.ok || !response.body) {
            logger.debug('stream response not ok or no body');
            return;
        }
        const reader = response.body.getReader();
        // Read until EOF - Pulse will block until data is available
        while (true) {
            const { done, value } = await reader.read();
            if (done) {
                logger.debug('stream EOF');
                break;
            }
            if (value) {
                logger.debug('stream chunk: %d bytes', value.length);
                await writeAndDrain(writable, value);
            }
        }
    }
    catch (err) {
        if (err instanceof Error && err.name === 'AbortError') {
            logger.debug('stream aborted');
            return;
        }
        logger.debug('stream error: %s', err);
    }
}
function createCaptureStream(onChunk) {
    return new Writable({
        write(chunk, _encoding, callback) {
            const text = typeof chunk === 'string' ? chunk : chunk.toString('utf-8');
            onChunk(text);
            callback();
        },
    });
}
export default execSubcommand;
//# sourceMappingURL=exec.js.map