/**
 * Custom error classes for Code Buddy
 */
/**
 * Base error class for all Code Buddy errors
 */
export class CodeBuddyError extends Error {
    code;
    details;
    constructor(message, code, details) {
        super(message);
        this.code = code;
        this.details = details;
        this.name = this.constructor.name;
        Error.captureStackTrace(this, this.constructor);
    }
    toJSON() {
        return {
            name: this.name,
            message: this.message,
            code: this.code,
            details: this.details,
        };
    }
}
/**
 * Error thrown when API key is missing or invalid
 */
export class APIKeyError extends CodeBuddyError {
    constructor(message = 'No API key found') {
        super(message, 'API_KEY_ERROR');
    }
}
/**
 * Error thrown when API request fails
 */
export class APIError extends CodeBuddyError {
    statusCode;
    response;
    constructor(message, statusCode, response) {
        super(message, 'API_ERROR', { statusCode, response });
        this.statusCode = statusCode;
        this.response = response;
    }
}
/**
 * Error thrown when network request fails
 */
export class NetworkError extends CodeBuddyError {
    originalError;
    constructor(message, originalError) {
        super(message, 'NETWORK_ERROR', originalError);
        this.originalError = originalError;
    }
}
/**
 * Error thrown when operation times out
 */
export class TimeoutError extends CodeBuddyError {
    timeoutMs;
    constructor(message, timeoutMs) {
        super(message, 'TIMEOUT_ERROR', { timeoutMs });
        this.timeoutMs = timeoutMs;
    }
}
/**
 * Error thrown when file operation fails
 */
export class FileError extends CodeBuddyError {
    filePath;
    operation;
    constructor(message, filePath, operation) {
        super(message, 'FILE_ERROR', { filePath, operation });
        this.filePath = filePath;
        this.operation = operation;
    }
}
/**
 * Error thrown when file is not found
 */
export class FileNotFoundError extends FileError {
    constructor(filePath) {
        super(`File not found: ${filePath}`, filePath, 'read');
        this.code = 'FILE_NOT_FOUND';
    }
}
/**
 * Error thrown when tool execution fails
 */
export class ToolExecutionError extends CodeBuddyError {
    toolName;
    toolArgs;
    constructor(message, toolName, toolArgs) {
        super(message, 'TOOL_EXECUTION_ERROR', { toolName, toolArgs });
        this.toolName = toolName;
        this.toolArgs = toolArgs;
    }
}
/**
 * Error thrown when bash command is invalid or dangerous
 */
export class InvalidCommandError extends CodeBuddyError {
    command;
    constructor(message, command) {
        super(message, 'INVALID_COMMAND', { command });
        this.command = command;
    }
}
/**
 * Error thrown when bash command execution fails
 */
export class CommandExecutionError extends CodeBuddyError {
    command;
    exitCode;
    stderr;
    constructor(message, command, exitCode, stderr) {
        super(message, 'COMMAND_EXECUTION_ERROR', { command, exitCode, stderr });
        this.command = command;
        this.exitCode = exitCode;
        this.stderr = stderr;
    }
}
/**
 * Error thrown when validation fails
 */
export class ValidationError extends CodeBuddyError {
    field;
    value;
    constructor(message, field, value) {
        super(message, 'VALIDATION_ERROR', { field, value });
        this.field = field;
        this.value = value;
    }
}
/**
 * Error thrown when configuration is invalid
 */
export class ConfigurationError extends CodeBuddyError {
    configKey;
    constructor(message, configKey) {
        super(message, 'CONFIGURATION_ERROR', { configKey });
        this.configKey = configKey;
    }
}
/**
 * Error thrown when MCP server communication fails
 */
export class MCPError extends CodeBuddyError {
    serverName;
    operation;
    constructor(message, serverName, operation) {
        super(message, 'MCP_ERROR', { serverName, operation });
        this.serverName = serverName;
        this.operation = operation;
    }
}
/**
 * Error thrown when search operation fails
 */
export class SearchError extends CodeBuddyError {
    query;
    searchType;
    constructor(message, query, searchType) {
        super(message, 'SEARCH_ERROR', { query, searchType });
        this.query = query;
        this.searchType = searchType;
    }
}
/**
 * Error thrown when code parsing fails
 */
export class ParserError extends CodeBuddyError {
    filePath;
    language;
    constructor(message, filePath, language) {
        super(message, 'PARSER_ERROR', { filePath, language });
        this.filePath = filePath;
        this.language = language;
    }
}
/**
 * Error thrown when path traversal is attempted
 */
export class PathTraversalError extends CodeBuddyError {
    attemptedPath;
    basePath;
    constructor(message, attemptedPath, basePath) {
        super(message, 'PATH_TRAVERSAL_ERROR', { attemptedPath, basePath });
        this.attemptedPath = attemptedPath;
        this.basePath = basePath;
    }
}
/**
 * Error thrown when JSON parsing fails
 */
export class JSONParseError extends CodeBuddyError {
    input;
    constructor(message, input) {
        super(message, 'JSON_PARSE_ERROR', { input: input?.substring(0, 200) });
        this.input = input;
    }
}
/**
 * User-friendly error with actionable suggestions
 *
 * Provides clear, actionable error messages with:
 * - Human-readable message explaining what went wrong
 * - Suggested command or action to fix the issue
 * - Link to relevant documentation
 * - Additional context for debugging
 *
 * @example
 * ```typescript
 * throw new UserFriendlyError({
 *   message: 'Model "llama3" not found',
 *   suggestion: 'Run: ollama pull llama3',
 *   docs: 'https://docs.codebuddy.dev/models',
 *   context: { availableModels: ['llama2', 'mistral'] }
 * });
 * ```
 */
export class UserFriendlyError extends CodeBuddyError {
    /** Suggested action to fix the error */
    suggestion;
    /** Link to documentation */
    docs;
    /** Original error that caused this */
    cause;
    constructor(options) {
        super(options.message, options.code || 'USER_FRIENDLY_ERROR', options.context);
        this.suggestion = options.suggestion;
        this.docs = options.docs;
        this.cause = options.cause;
    }
    /**
     * Format the error for display to the user
     */
    format() {
        const lines = [this.message];
        if (this.suggestion) {
            lines.push('');
            lines.push(`Suggestion: ${this.suggestion}`);
        }
        if (this.docs) {
            lines.push('');
            lines.push(`Documentation: ${this.docs}`);
        }
        if (this.details && typeof this.details === 'object') {
            const context = this.details;
            // Format available models if present
            if (Array.isArray(context.availableModels) && context.availableModels.length > 0) {
                lines.push('');
                lines.push('Available models:');
                for (const model of context.availableModels.slice(0, 10)) {
                    lines.push(`  - ${model}`);
                }
                if (context.availableModels.length > 10) {
                    lines.push(`  ... and ${context.availableModels.length - 10} more`);
                }
            }
            // Format other context items
            for (const [key, value] of Object.entries(context)) {
                if (key === 'availableModels')
                    continue; // Already handled
                if (value !== undefined && value !== null) {
                    lines.push(`${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
                }
            }
        }
        return lines.join('\n');
    }
    toJSON() {
        return {
            ...super.toJSON(),
            suggestion: this.suggestion,
            docs: this.docs,
            cause: this.cause ? { message: this.cause.message, name: this.cause.name } : undefined,
        };
    }
}
/**
 * Error thrown when a model is not found
 */
export class ModelNotFoundError extends UserFriendlyError {
    constructor(options) {
        const suggestions = [];
        if (options.downloadCommand) {
            suggestions.push(options.downloadCommand);
        }
        else if (options.provider === 'ollama') {
            suggestions.push(`ollama pull ${options.model}`);
        }
        else if (options.provider === 'local-llama') {
            suggestions.push(`Download a GGUF model from https://huggingface.co/models?search=gguf`);
        }
        const suggestionText = suggestions.length > 0
            ? `Run: ${suggestions[0]}`
            : 'Check the model name and try again';
        super({
            message: `Model "${options.model}" not found for ${options.provider}`,
            suggestion: suggestionText,
            docs: 'https://docs.codebuddy.dev/local-llm/models',
            context: { availableModels: options.availableModels },
            code: 'MODEL_NOT_FOUND',
        });
    }
}
/**
 * Error thrown when tool argument parsing fails
 */
export class ToolArgumentParseError extends UserFriendlyError {
    constructor(options) {
        const truncatedArgs = options.rawArguments.length > 500
            ? options.rawArguments.substring(0, 500) + '...'
            : options.rawArguments;
        super({
            message: `Failed to parse arguments for tool "${options.toolName}"`,
            suggestion: 'The AI model may have provided malformed JSON. Try rephrasing your request.',
            docs: 'https://docs.codebuddy.dev/tools/troubleshooting',
            context: {
                toolName: options.toolName,
                parseError: options.parseError,
                rawArguments: truncatedArgs,
                expectedSchema: options.expectedSchema,
            },
            code: 'TOOL_ARGUMENT_PARSE_ERROR',
        });
    }
}
/**
 * Error thrown when a provider is not available
 */
export class ProviderNotAvailableError extends UserFriendlyError {
    constructor(options) {
        const suggestions = [];
        if (options.startCommand) {
            suggestions.push(`Start the service: ${options.startCommand}`);
        }
        if (options.installCommand) {
            suggestions.push(`Install: ${options.installCommand}`);
        }
        super({
            message: `${options.provider} is not available${options.reason ? `: ${options.reason}` : ''}`,
            suggestion: suggestions.join('\n'),
            docs: `https://docs.codebuddy.dev/providers/${options.provider.toLowerCase()}`,
            context: { provider: options.provider, reason: options.reason },
            code: 'PROVIDER_NOT_AVAILABLE',
        });
    }
}
/**
 * Error thrown when a loop exceeds maximum iterations
 */
export class LoopTimeoutError extends CodeBuddyError {
    maxIterations;
    context;
    constructor(message, maxIterations, context) {
        super(message, 'LOOP_TIMEOUT_ERROR', { maxIterations, context });
        this.maxIterations = maxIterations;
        this.context = context;
    }
}
/**
 * Checks if an error is a CodeBuddyError or subclass
 */
export function isCodeBuddyError(error) {
    return error instanceof CodeBuddyError;
}
/**
 * Safely extracts error message from any error type
 */
export function getErrorMessage(error) {
    if (error instanceof Error) {
        return error.message;
    }
    if (typeof error === 'string') {
        return error;
    }
    return 'An unknown error occurred';
}
/**
 * Wraps a promise with timeout
 */
export async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
    let timeoutHandle;
    const timeoutPromise = new Promise((_, reject) => {
        timeoutHandle = setTimeout(() => {
            reject(new TimeoutError(errorMessage, timeoutMs));
        }, timeoutMs);
    });
    try {
        return await Promise.race([promise, timeoutPromise]);
    }
    finally {
        clearTimeout(timeoutHandle);
    }
}
/**
 * Retries a function with exponential backoff
 */
export async function withRetry(fn, options = {}) {
    const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000, shouldRetry = () => true, } = options;
    let lastError;
    let delay = initialDelay;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        }
        catch (error) {
            lastError = error;
            if (attempt === maxRetries || !shouldRetry(error)) {
                throw error;
            }
            // Wait before retrying with exponential backoff
            await new Promise((resolve) => setTimeout(resolve, delay));
            delay = Math.min(delay * 2, maxDelay);
        }
    }
    throw lastError;
}
/**
 * Loop guard to prevent infinite loops
 * Returns a function that should be called at each iteration.
 * Throws LoopTimeoutError when max iterations exceeded.
 *
 * @example
 * ```typescript
 * const guard = createLoopGuard({ maxIterations: 1000, context: 'parsing postfix' });
 * while (true) {
 *   guard(); // Throws if iterations exceed limit
 *   // ... loop body
 * }
 * ```
 */
export function createLoopGuard(options = {}) {
    const { maxIterations = 10000, context = 'loop', warnAt = Math.floor(maxIterations * 0.8), onWarn, } = options;
    let iterations = 0;
    let warned = false;
    return () => {
        iterations++;
        if (!warned && iterations >= warnAt && onWarn) {
            warned = true;
            onWarn(`Warning: ${context} approaching iteration limit (${iterations}/${maxIterations})`);
        }
        if (iterations > maxIterations) {
            throw new LoopTimeoutError(`${context} exceeded maximum iterations (${maxIterations}). ` +
                'This may indicate an infinite loop or corrupted input.', maxIterations, context);
        }
    };
}
//# sourceMappingURL=errors.js.map