import { StructuredError } from "../../error.js";
import { z } from 'zod';
/**
 * Machine-readable error codes for sandbox operations.
 * These codes allow programmatic error handling without fragile string matching.
 */
export const SandboxErrorCodeSchema = z.enum([
    'SANDBOX_NOT_FOUND',
    'SANDBOX_TERMINATED',
    'SANDBOX_BUSY',
    'EXECUTION_NOT_FOUND',
    'EXECUTION_TIMEOUT',
    'EXECUTION_CANCELLED',
    'SNAPSHOT_NOT_FOUND',
]);
/**
 * Error thrown when a sandbox API request fails.
 *
 * Includes optional context about which sandbox or execution caused the error.
 */
export const SandboxResponseError = StructuredError('SandboxResponseError')();
/**
 * Error thrown when a sandbox is not found.
 *
 * @example
 * ```typescript
 * try {
 *   await sandboxGet(client, { sandboxId: 'non-existent' });
 * } catch (error) {
 *   if (error._tag === 'SandboxNotFoundError') {
 *     console.error(`Sandbox not found: ${error.sandboxId}`);
 *   }
 * }
 * ```
 */
export const SandboxNotFoundError = StructuredError('SandboxNotFoundError')();
/**
 * Error thrown when a sandbox has already terminated.
 *
 * @example
 * ```typescript
 * try {
 *   await sandboxExecute(client, { sandboxId: 'terminated-sandbox', command: ['ls'] });
 * } catch (error) {
 *   if (error._tag === 'SandboxTerminatedError') {
 *     console.error(`Sandbox terminated: ${error.sandboxId}`);
 *   }
 * }
 * ```
 */
export const SandboxTerminatedError = StructuredError('SandboxTerminatedError')();
/**
 * Error thrown when a sandbox is currently busy executing another command.
 *
 * This typically occurs when a second execute request is sent before the
 * previous execution has completed. Sandbox executions are serialized -
 * wait for the current execution to complete before sending a new one.
 *
 * @example
 * ```typescript
 * try {
 *   await sandbox.execute({ command: ['ls'] });
 * } catch (error) {
 *   if (error._tag === 'SandboxBusyError') {
 *     console.error('Sandbox is busy, waiting for current execution to finish');
 *     // Wait and retry, or use executionGet with long-polling to wait for completion
 *   }
 * }
 * ```
 */
export const SandboxBusyError = StructuredError('SandboxBusyError')();
/**
 * Error thrown when an execution is not found.
 *
 * @example
 * ```typescript
 * try {
 *   await executionGet(client, { executionId: 'non-existent' });
 * } catch (error) {
 *   if (error._tag === 'ExecutionNotFoundError') {
 *     console.error(`Execution not found: ${error.executionId}`);
 *   }
 * }
 * ```
 */
export const ExecutionNotFoundError = StructuredError('ExecutionNotFoundError')();
/**
 * Error thrown when an execution times out.
 *
 * @example
 * ```typescript
 * try {
 *   await sandboxExecute(client, { sandboxId, command: ['long-running'], timeout: '30s' });
 * } catch (error) {
 *   if (error._tag === 'ExecutionTimeoutError') {
 *     console.error('Execution timed out');
 *   }
 * }
 * ```
 */
export const ExecutionTimeoutError = StructuredError('ExecutionTimeoutError')();
/**
 * Error thrown when an execution is cancelled.
 *
 * @example
 * ```typescript
 * try {
 *   await sandboxRun(client, params, { signal: controller.signal });
 * } catch (error) {
 *   if (error._tag === 'ExecutionCancelledError') {
 *     console.error('Execution was cancelled');
 *   }
 * }
 * ```
 */
export const ExecutionCancelledError = StructuredError('ExecutionCancelledError')();
/**
 * Error thrown when a snapshot is not found.
 *
 * @example
 * ```typescript
 * try {
 *   await snapshotGet(client, { snapshotId: 'non-existent' });
 * } catch (error) {
 *   if (error._tag === 'SnapshotNotFoundError') {
 *     console.error(`Snapshot not found: ${error.snapshotId}`);
 *   }
 * }
 * ```
 */
export const SnapshotNotFoundError = StructuredError('SnapshotNotFoundError')();
/**
 * Context for throwing sandbox errors.
 */
export const SandboxErrorContextSchema = z.object({
    sandboxId: z.string().optional().describe('sandbox id'),
    executionId: z.string().optional().describe('execution id'),
    jobId: z.string().optional().describe('job id'),
    sessionId: z.string().nullish().describe('session id'),
    snapshotId: z.string().optional().describe('snapshot id'),
});
/**
 * Throws the appropriate sandbox error based on the response code.
 *
 * This helper centralizes error mapping logic, throwing specific error types
 * when the backend returns a known error code, and falling back to
 * SandboxResponseError for unknown codes.
 *
 * Note: Pause and resume operations use standard error codes. The backend
 * returns SANDBOX_NOT_FOUND when the sandbox doesn't exist, and HTTP 409
 * Conflict (handled by APIClient retries) for invalid state transitions
 * (e.g. pausing a non-running sandbox, resuming a non-suspended sandbox).
 * No additional error codes are needed for pause/resume.
 *
 * @param resp - The API response containing message and optional code
 * @param context - Context about the operation (sandbox ID, execution ID, etc.)
 * @throws {SandboxNotFoundError} When code is 'SANDBOX_NOT_FOUND'
 * @throws {SandboxTerminatedError} When code is 'SANDBOX_TERMINATED'
 * @throws {SandboxBusyError} When code is 'SANDBOX_BUSY'
 * @throws {ExecutionNotFoundError} When code is 'EXECUTION_NOT_FOUND'
 * @throws {ExecutionTimeoutError} When code is 'EXECUTION_TIMEOUT'
 * @throws {ExecutionCancelledError} When code is 'EXECUTION_CANCELLED'
 * @throws {SnapshotNotFoundError} When code is 'SNAPSHOT_NOT_FOUND'
 * @throws {SandboxResponseError} For unknown codes or when no code is provided
 */
export function throwSandboxError(resp, context) {
    const { sandboxId, executionId, sessionId, snapshotId } = context;
    const code = resp.code;
    switch (code) {
        case 'SANDBOX_NOT_FOUND':
            throw new SandboxNotFoundError({ message: resp.message, sandboxId: sandboxId ?? '' });
        case 'SANDBOX_TERMINATED':
            throw new SandboxTerminatedError({ message: resp.message, sandboxId: sandboxId ?? '' });
        case 'SANDBOX_BUSY':
            throw new SandboxBusyError({ message: resp.message, sandboxId });
        case 'EXECUTION_NOT_FOUND':
            throw new ExecutionNotFoundError({
                message: resp.message,
                executionId: executionId ?? '',
                sandboxId,
            });
        case 'EXECUTION_TIMEOUT':
            throw new ExecutionTimeoutError({ message: resp.message, executionId, sandboxId });
        case 'EXECUTION_CANCELLED':
            throw new ExecutionCancelledError({ message: resp.message, sandboxId });
        case 'SNAPSHOT_NOT_FOUND':
            throw new SnapshotNotFoundError({ message: resp.message, snapshotId });
        default:
            throw new SandboxResponseError({
                message: resp.message,
                sandboxId,
                executionId,
                sessionId,
                code,
            });
    }
}
/** Current sandbox API version */
/**
 * Write a chunk to a writable stream and wait for it to drain if necessary.
 * Properly cleans up event listeners to avoid memory leaks.
 */
export function writeAndDrain(writable, chunk) {
    return new Promise((resolve, reject) => {
        let needsDrain;
        try {
            needsDrain = !writable.write(chunk);
        }
        catch (err) {
            reject(err);
            return;
        }
        if (needsDrain) {
            const cleanup = () => {
                writable.removeListener('drain', onDrain);
                writable.removeListener('error', onError);
            };
            const onDrain = () => {
                cleanup();
                resolve();
            };
            const onError = (err) => {
                cleanup();
                reject(err);
            };
            writable.once('drain', onDrain);
            writable.once('error', onError);
        }
        else {
            resolve();
        }
    });
}
//# sourceMappingURL=util.js.map