import { HttpError, VextValidationError } from "../../types/errors.js";
/**
 * createErrorHandler — 全局错误处理中间件工厂
 *
 * 内置中间件 #6（通过 adapter.registerErrorHandler() 注册），职责：
 *   捕获中间件链中抛出的所有错误，按类型格式化为统一的 JSON 错误响应。
 *
 * 三层错误匹配规则（按优先级）：
 *
 *   1. VextValidationError（422 Validation Failed）
 *      由 validate 中间件在 schema 校验失败时抛出。
 *      响应：{ code: 422, message: 'Validation failed', errors: [...], requestId }
 *      errors 数组包含每个字段的错误描述：[{ field: 'email', message: '...' }]
 *
 *   2. HttpError（4xx / 5xx 业务错误）
 *      由 app.throw() 或用户代码 throw new HttpError(...) 抛出。
 *      响应：{ code, message, requestId }
 *      code 取值优先级：err.code（业务错误码）> err.status（HTTP 状态码）
 *      这意味着如果用户传入了业务码（如 10001），响应的 code 就是 10001；
 *      否则 code 等于 HTTP 状态码（如 404）。
 *
 *   3. 未知错误（500 Internal Server Error）
 *      所有不属于上述两类的错误（运行时异常、第三方库异常等）。
 *      响应：{ code: 500, message: 'Internal Server Error', requestId }
 *      生产环境（hideInternalErrors = true）隐藏 stack trace，
 *      开发环境（hideInternalErrors = false）附带 stack 供调试。
 *
 * Dev 错误覆盖层（可选 devOverlay 参数）：
 *   当 devOverlay 已注入且请求 Accept 包含 'text/html' 时，
 *   返回 HTML 错误覆盖层而非 JSON。适用于浏览器直接访问出错路由的场景。
 *   devOverlay 渲染失败时，自动降级到 JSON 处理。
 *
 * 工厂函数设计（Q21）：
 *   使用工厂模式 createErrorHandler(config) 而非直接引用 app.config，
 *   原因是错误处理器需要持有 config 引用但不应直接依赖 app 对象。
 *   工厂函数在 bootstrap 时调用一次，返回的闭包通过闭包变量持有 config，
 *   解决了作用域和生命周期问题。
 *
 * 注册方式（在 bootstrap 中）：
 *   app.adapter.registerErrorHandler(
 *     createErrorHandler(config.response ?? {}, undefined, app.logger)
 *   )
 *
 * 与 rawJson 的关系：
 *   错误响应始终使用 res.rawJson()，绕过出口包装（response-wrapper）。
 *   这确保错误响应格式为 { code, message, requestId } 而非被二次包装为
 *   { code: 0, data: { code, message, ... }, requestId }。
 *
 * 日志记录（可选 logger 参数）：
 *   当 logger 传入时，error-handler 负责在响应格式决策之前记录日志：
 *     - 未知错误（500）：logger.error({ err }, '[uncaught] ...')（含 stack trace）
 *     - HttpError 5xx：logger.error('[http-error] status message')
 *     - HttpError 4xx：logger.warn(...)（需 logErrors.http4xx = true 开启）
 *     - VextValidationError：不记录（客户端输入问题）
 *   日志记录先于 devOverlay 判断，确保无论响应格式如何日志都可见。
 *   不传 logger 时行为与原来完全一致（向后兼容）。
 *
 * @param responseConfig 响应配置（从 VextConfig.response 提取）
 * @param devOverlay     可选：dev 模式错误覆盖层渲染函数（生产模式不传）
 * @param logger         可选：VextLogger 实例，传入后自动记录错误日志
 * @returns VextErrorMiddleware
 */
export function createErrorHandler(responseConfig, devOverlay, logger) {
    // 是否隐藏内部错误详情（生产环境默认 true）
    // 当 hideInternalErrors = true 时，500 错误不暴露 stack trace，
    // 防止敏感信息泄漏（如文件路径、依赖版本、SQL 语句等）
    const hideInternalErrors = responseConfig.hideInternalErrors ?? true;
    const logErrors = responseConfig.logErrors;
    return (err, req, res) => {
        // ── Step 0: 错误归一化（统一为 Error 对象，消除后续重复转换）──
        //
        // 非 Error 对象（如 throw "string" / throw null）统一包装为 Error。
        // 后续所有步骤直接使用 errObj，避免重复 `err instanceof Error ? err : new Error(...)`.
        //
        const errObj = err instanceof Error ? err : new Error(String(err));
        // ── Step 1: 日志记录（先于响应格式决策，确保任何路径都能记录）──
        //
        // 关键设计：日志前置于 devOverlay 判断。
        // 原因：devOverlay 触发时会提前 return，若日志在其后则 browser 访问场景日志丢失。
        // 日志记录（side effect）与响应格式（HTML/JSON）正交，互不影响。
        //
        // try-catch 保护：logger 可能是用户通过 setLogger() 注入的自定义实现，
        // 若 logger 自身抛出异常，不能让 error-handler（最后防线）崩溃，否则请求会挂起。
        //
        // 使用 .name 字符串检测替代 instanceof：
        // 防止 CJS/ESM 双包场景下 instanceof 失败（与 Step 2 devOverlay 保持一致）。
        //
        if (logger) {
            try {
                if (errObj.name === "VextValidationError") {
                    // 校验错误属于客户端输入问题，不记录日志
                }
                else if (errObj.name === "HttpError" &&
                    typeof errObj.status ===
                        "number") {
                    const status = errObj
                        .status;
                    if (status >= 500 && logErrors?.http5xx !== false) {
                        logger.error(`[http-error] ${status} ${errObj.message}`);
                    }
                    else if (status < 500 && logErrors?.http4xx === true) {
                        logger.warn(`[http-error] ${status} ${errObj.message}`);
                    }
                }
                else {
                    // 未知错误（运行时异常、第三方库异常等）
                    if (logErrors?.unknownErrors !== false) {
                        logger.error({ err: errObj }, `[uncaught] ${errObj.message}`);
                    }
                }
            }
            catch {
                // logger 自身异常，静默忽略（error-handler 是最后防线，不能崩溃）
            }
        }
        // ── Step 2: Dev 错误覆盖层：浏览器请求 + dev overlay 已注入 ────
        //
        // 触发条件：
        //   1. devOverlay 函数已注入（仅 dev 模式）
        //   2. 请求 Accept 包含 'text/html'（浏览器特征）
        // API 客户端（curl/axios/fetch）的 Accept 不含 text/html，不受影响。
        //
        if (devOverlay) {
            const accept = req.headers["accept"] ?? "";
            if (accept.includes("text/html")) {
                // 计算正确的 HTTP 状态码
                // 使用 error.name 字符串检测替代 instanceof，防止 CJS/ESM 双包 instanceof 失败
                let statusCode = 500;
                if (errObj.name === "VextValidationError") {
                    statusCode = 422;
                }
                else if (errObj.name === "HttpError" &&
                    typeof errObj.status ===
                        "number") {
                    statusCode = errObj
                        .status;
                }
                try {
                    const html = devOverlay(err);
                    res.setHeader("Content-Type", "text/html; charset=utf-8");
                    res.text(html, statusCode);
                    return;
                }
                catch {
                    // overlay 渲染失败 → fallback 到 JSON（继续执行下方逻辑）
                }
            }
        }
        const requestId = req.requestId;
        // ── 层 1：校验错误（422）────────────────────────────
        //
        // VextValidationError 由 validate 中间件在 schema 校验失败时抛出。
        // 携带 errors 数组，每个元素描述一个字段的校验错误。
        // HTTP 状态码固定为 422 Unprocessable Entity。
        //
        if (err instanceof VextValidationError) {
            res.rawJson({
                code: 422,
                message: err.message,
                errors: err.errors, // [{ field: 'email', message: '邮箱格式不正确' }]
                requestId,
            }, 422);
            return;
        }
        // ── 层 2：HTTP 业务错误（app.throw 抛出）─────────────
        //
        // HttpError 由 app.throw() 或 createDefaultThrow() 创建。
        // status: HTTP 状态码（4xx / 5xx）
        // message: 错误描述（可能是 i18n 翻译后的文本）
        // code: 业务错误码（可选，不传则 code = status）
        //
        // 响应的 code 字段优先使用业务错误码（err.code），
        // 没有业务码时降级使用 HTTP 状态码（err.status）。
        // 这样 API 消费方可以通过 code 区分具体的业务错误类型。
        //
        if (err instanceof HttpError) {
            res.rawJson({
                code: err.code ?? err.status, // 有业务码用业务码，否则用 HTTP 状态码
                message: err.message,
                requestId,
            }, err.status);
            return;
        }
        // ── 层 3：未知错误（500）─────────────────────────────
        //
        // 所有非 VextValidationError / HttpError 的错误走此分支。
        // 包括：运行时 TypeError / ReferenceError、第三方库异常、
        // 数据库连接失败等。
        //
        // 生产环境隐藏内部细节（安全性）：
        //   - message 固定为 'Internal Server Error'
        //   - 不暴露 stack trace、错误详情、文件路径等
        //
        // 开发环境显示 stack（调试便利性）：
        //   - 附带 stack 字段，开发者可快速定位错误位置
        //
        const body = {
            code: 500,
            message: "Internal Server Error",
            requestId,
        };
        // 开发环境：附带 stack trace 供调试
        if (!hideInternalErrors) {
            body.stack = errObj.stack;
        }
        res.rawJson(body, 500);
    };
}
