/**
 * Error Handling Middleware
 *
 * Centralized error handling for the API server.
 */
import { randomBytes } from 'crypto';
import { API_ERRORS } from '../types.js';
import { logger } from '../../utils/logger.js';
/**
 * Custom API Error class
 */
export class ApiServerError extends Error {
    code;
    status;
    details;
    constructor(message, code = 'INTERNAL_ERROR', status = 500, details) {
        super(message);
        this.name = 'ApiServerError';
        this.code = code;
        this.status = status;
        this.details = details;
    }
    static badRequest(message, details) {
        return new ApiServerError(message, 'VALIDATION_ERROR', 400, details);
    }
    static unauthorized(message = 'Authentication required') {
        return new ApiServerError(message, 'UNAUTHORIZED', 401);
    }
    static forbidden(message = 'Insufficient permissions') {
        return new ApiServerError(message, 'FORBIDDEN', 403);
    }
    static notFound(resource = 'Resource') {
        return new ApiServerError(`${resource} not found`, 'NOT_FOUND', 404);
    }
    static rateLimited(retryAfter) {
        return new ApiServerError(`Rate limit exceeded. Try again in ${retryAfter}s`, 'RATE_LIMITED', 429, { retryAfter });
    }
    static internal(message = 'Internal server error') {
        return new ApiServerError(message, 'INTERNAL_ERROR', 500);
    }
    static serviceUnavailable(message = 'Service temporarily unavailable') {
        return new ApiServerError(message, 'SERVICE_UNAVAILABLE', 503);
    }
    toJSON() {
        return {
            code: this.code,
            message: this.message,
            status: this.status,
            details: this.details,
        };
    }
}
/**
 * Generate request ID
 */
export function generateRequestId() {
    return randomBytes(8).toString('hex');
}
/**
 * Request ID middleware
 */
export function requestIdMiddleware(req, res, next) {
    const requestId = req.headers['x-request-id'] || generateRequestId();
    req.headers['x-request-id'] = requestId;
    res.setHeader('X-Request-ID', requestId);
    next();
}
/**
 * Not found handler (404)
 */
export function notFoundHandler(req, res) {
    const error = {
        ...API_ERRORS.NOT_FOUND,
        message: `Endpoint not found: ${req.method} ${req.path}`,
        requestId: req.headers['x-request-id'],
    };
    res.status(404).json(error);
}
/**
 * Global error handler
 */
export const errorHandler = (err, req, res, _next) => {
    // Log error
    const requestId = req.headers['x-request-id'];
    logger.error(`[${requestId}] API Error`, err instanceof Error ? err : new Error(String(err)), { requestId });
    // Handle ApiServerError
    if (err instanceof ApiServerError) {
        const response = {
            ...err.toJSON(),
            requestId,
        };
        return res.status(err.status).json(response);
    }
    // Handle validation errors (e.g., from express-validator or Joi)
    if (err.name === 'ValidationError') {
        const response = {
            code: 'VALIDATION_ERROR',
            message: err.message,
            status: 400,
            requestId,
        };
        return res.status(400).json(response);
    }
    // Handle JSON parsing errors
    if (err instanceof SyntaxError && 'body' in err) {
        const response = {
            code: 'VALIDATION_ERROR',
            message: 'Invalid JSON in request body',
            status: 400,
            requestId,
        };
        return res.status(400).json(response);
    }
    // Handle unknown errors
    const response = {
        code: 'INTERNAL_ERROR',
        message: process.env.NODE_ENV === 'production'
            ? 'An unexpected error occurred'
            : err.message,
        status: 500,
        requestId,
        details: process.env.NODE_ENV === 'production'
            ? undefined
            : { stack: err.stack },
    };
    return res.status(500).json(response);
};
/**
 * Async handler wrapper (catches async errors)
 */
export function asyncHandler(fn) {
    return (req, res, next) => {
        Promise.resolve(fn(req, res, next)).catch(next);
    };
}
/**
 * Validate required fields in request body
 */
export function validateRequired(body, fields) {
    const missing = fields.filter((field) => {
        const key = field;
        return !(key in body) || body[key] === undefined;
    });
    if (missing.length > 0) {
        throw ApiServerError.badRequest(`Missing required field(s): ${missing.join(', ')}`, { missingFields: missing });
    }
}
/**
 * Validate field types
 */
export function validateTypes(body, schema) {
    const errors = [];
    for (const [field, expectedType] of Object.entries(schema)) {
        if (!(field in body))
            continue;
        const value = body[field];
        let actualType = typeof value;
        if (expectedType === 'array' && !Array.isArray(value)) {
            errors.push(`${field} must be an array`);
        }
        else if (expectedType !== 'array' && actualType !== expectedType) {
            errors.push(`${field} must be a ${expectedType}`);
        }
    }
    if (errors.length > 0) {
        throw ApiServerError.badRequest(errors.join('; '), { errors });
    }
}
//# sourceMappingURL=error-handler.js.map