import { PlatformError } from './base.errors.js';
import { ErrorCategory, ErrorRecoverability, ErrorSeverity } from './error.codes.js';
import { logger } from '../logger.js';
/**
 * Global error handler configuration
 */
const config = {
    logErrors: true,
    includeStacksInProd: false,
    retry: {
        maxAttempts: 3,
        baseDelay: 1000,
        maxDelay: 30000,
        backoffFactor: 2
    }
};
/**
 * Error handler utility
 * Provides methods for consistent error handling across the platform
 */
export class ErrorHandler {
    /**
     * Current configuration
     */
    static config = config;
    /**
     * Update error handler configuration
     *
     * @param newConfig New configuration (partial)
     */
    static configure(newConfig) {
        ErrorHandler.config = {
            ...ErrorHandler.config,
            ...newConfig,
            retry: {
                ...ErrorHandler.config.retry,
                ...(newConfig.retry || {})
            }
        };
    }
    /**
     * Convert any error to a PlatformError
     *
     * @param error Error to convert
     * @param defaultCode Default error code if not a PlatformError
     * @param context Additional context
     * @returns PlatformError instance
     */
    static toPlatformError(error, defaultCode, context = {}) {
        // If already a PlatformError, just add context
        if (error instanceof PlatformError) {
            // Add context if provided
            if (Object.keys(context).length > 0) {
                return new error.constructor(error.message, error.code, error.severity, error.category, error.recoverability, {
                    ...error.context,
                    ...context,
                    data: {
                        ...(error.context.data || {}),
                        ...(context.data || {})
                    }
                });
            }
            return error;
        }
        // Convert standard Error to PlatformError
        if (error instanceof Error) {
            return new PlatformError(error.message, defaultCode, ErrorSeverity.MEDIUM, ErrorCategory.OPERATION, ErrorRecoverability.NON_RECOVERABLE, {
                ...context,
                data: {
                    ...(context.data || {}),
                    originalError: {
                        name: error.name,
                        message: error.message,
                        stack: error.stack
                    }
                }
            });
        }
        // Not an Error instance
        return new PlatformError(typeof error === 'string' ? error : 'Unknown error', defaultCode, ErrorSeverity.MEDIUM, ErrorCategory.OPERATION, ErrorRecoverability.NON_RECOVERABLE, context);
    }
    /**
     * Format an error for API responses
     * Sanitizes errors for safe external exposure
     *
     * @param error Error to format
     * @param includeDetails Whether to include detailed information
     * @returns Formatted error object
     */
    static formatErrorForResponse(error, includeDetails = false) {
        const platformError = ErrorHandler.toPlatformError(error, 'PLATFORM_OPERATION_ERROR');
        // Basic error information
        const responseError = {
            code: platformError.code,
            message: platformError.getUserMessage(),
            requestId: platformError.context.requestId
        };
        // Include more details if requested
        if (includeDetails) {
            responseError.details = {
                severity: platformError.severity,
                category: platformError.category,
                rawMessage: platformError.message,
                data: platformError.context.data
            };
            // Only include stack trace in non-production or if explicitly enabled
            if (process.env.NODE_ENV !== 'production' || ErrorHandler.config.includeStacksInProd) {
                responseError.details.stack = platformError.stack;
            }
        }
        return responseError;
    }
    /**
     * Handle an error with consistent logging and formatting
     *
     * @param error Error to handle
     * @param defaultCode Default error code if not a PlatformError
     * @param context Additional context
     * @returns Formatted error for response
     */
    static handleError(error, defaultCode, context = {}) {
        const platformError = ErrorHandler.toPlatformError(error, defaultCode, context);
        // Log the error if enabled
        if (ErrorHandler.config.logErrors) {
            logger.error(platformError.message, {
                error_code: platformError.code,
                error_name: platformError.name,
                error_severity: platformError.severity,
                error_category: platformError.category,
                error_recoverability: platformError.recoverability,
                ...platformError.context,
                stack: platformError.stack
            });
        }
        // Return formatted error for response
        const isDetailedMode = process.env.NODE_ENV !== 'production';
        return ErrorHandler.formatErrorForResponse(platformError, isDetailedMode);
    }
    /**
     * Execute a function with error handling
     *
     * @param fn Function to execute
     * @param defaultCode Default error code if the function throws
     * @param context Additional context
     * @returns Function result or error
     */
    static async execute(fn, defaultCode, context = {}) {
        try {
            return await fn();
        }
        catch (error) {
            throw ErrorHandler.toPlatformError(error, defaultCode, context);
        }
    }
    /**
     * Execute a function with retries and exponential backoff
     *
     * @param fn Function to execute
     * @param defaultCode Default error code if the function throws
     * @param options Retry options
     * @param context Additional context
     * @returns Function result or error after max retries
     */
    static async executeWithRetry(fn, defaultCode, options = {}, context = {}) {
        const { maxAttempts = ErrorHandler.config.retry.maxAttempts, baseDelay = ErrorHandler.config.retry.baseDelay, maxDelay = ErrorHandler.config.retry.maxDelay, backoffFactor = ErrorHandler.config.retry.backoffFactor, retryableErrorCodes = [], retryableErrorPatterns = [], onRetry = () => { } } = options;
        let lastError;
        for (let attempt = 0; attempt < maxAttempts; attempt++) {
            try {
                return await fn();
            }
            catch (error) {
                // Convert to PlatformError
                const platformError = ErrorHandler.toPlatformError(error, defaultCode, {
                    ...context,
                    retry: {
                        currentRetry: attempt,
                        maxRetries: maxAttempts,
                        nextRetryAt: 0 // Will be set below if retrying
                    }
                });
                lastError = platformError;
                // Check if we should retry
                const isLastAttempt = attempt >= maxAttempts - 1;
                if (isLastAttempt) {
                    // No more retries
                    throw platformError;
                }
                // Check if error is retryable
                const isRetryable = 
                // Built-in recoverability
                platformError.recoverability === ErrorRecoverability.RECOVERABLE ||
                    platformError.recoverability === ErrorRecoverability.MAYBE_RECOVERABLE ||
                    platformError.recoverability === ErrorRecoverability.TRANSIENT ||
                    // Specifically included error codes
                    retryableErrorCodes.includes(platformError.code) ||
                    // Matches error message patterns
                    retryableErrorPatterns.some(pattern => pattern.test(platformError.message));
                if (!isRetryable) {
                    throw platformError;
                }
                // Calculate delay with exponential backoff
                const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt), maxDelay);
                // Add jitter to prevent thundering herd problem (±20%)
                const jitter = 0.8 + Math.random() * 0.4;
                const actualDelay = Math.floor(delay * jitter);
                // Update nextRetryAt in error context
                const nextRetryAt = Date.now() + actualDelay;
                platformError.context.retry.nextRetryAt = nextRetryAt;
                // Log retry attempt
                logger.warn(`Retrying operation after error (attempt ${attempt + 1}/${maxAttempts}): ${platformError.message}`, {
                    error_code: platformError.code,
                    retry_attempt: attempt + 1,
                    retry_max_attempts: maxAttempts,
                    retry_delay_ms: actualDelay,
                    retry_next_at: new Date(nextRetryAt).toISOString()
                });
                // Call onRetry callback
                onRetry(platformError, attempt + 1, actualDelay);
                // Wait before next retry
                await new Promise(resolve => setTimeout(resolve, actualDelay));
            }
        }
        // This should never happen, but TypeScript needs it
        throw lastError;
    }
}
/**
 * Create a middleware for handling errors in HTTP requests
 *
 * @returns Middleware function
 */
export function createErrorHandlerMiddleware() {
    return (error, req, res, next) => {
        // Add request context
        const context = {
            requestId: req.headers['x-request-id'] || req.headers['x-correlation-id'],
            component: 'HttpServer',
            operation: `${req.method} ${req.url}`,
            data: {
                method: req.method,
                url: req.url,
                query: req.query,
                params: req.params,
                ip: req.ip || req.connection.remoteAddress,
                userAgent: req.headers['user-agent']
            }
        };
        // Handle the error
        const formattedError = ErrorHandler.handleError(error, 'PLATFORM_OPERATION_ERROR', context);
        // Set status code based on error type
        let statusCode = 500;
        if (error instanceof PlatformError) {
            // Map error categories to HTTP status codes
            switch (error.category) {
                case ErrorCategory.VALIDATION:
                    statusCode = 400;
                    break;
                case ErrorCategory.AUTHENTICATION:
                    statusCode = 401;
                    break;
                case ErrorCategory.RESOURCE:
                    statusCode = 429;
                    break;
                case ErrorCategory.OPERATION:
                    statusCode = 400;
                    break;
                default:
                    statusCode = 500;
            }
        }
        else if (error.statusCode) {
            // Use provided status code if available
            statusCode = error.statusCode;
        }
        // Send error response
        res.status(statusCode).json({
            success: false,
            error: formattedError
        });
    };
}
//# sourceMappingURL=data:application/json;base64, 