/**
 * Framework-agnostic error mapping utilities for @beignet/core/server
 */

import {
  createErrorResponseBody,
  type ErrorResponseBody,
} from "../../errors/index.js";
import type { AppEnvironment } from "../health.js";
import { getRequestIdFromContext } from "./utils.js";

/**
 * Re-export `AppEnvironment` for convenience.
 */
export type { AppEnvironment } from "../health.js";

/**
 * Framework-neutral response produced by an error mapper.
 */
export interface ErrorMappingResult {
  /**
   * HTTP status code.
   */
  status: number;
  /**
   * Response body.
   */
  body: unknown;
  /**
   * Response headers.
   */
  headers?: Record<string, string>;
}

/**
 * Error mapping configuration.
 */
export interface ErrorMappingConfig<Ctx> {
  /** Custom error mapper function. */
  mapErrorToResponse?: (err: unknown, ctx: Ctx) => ErrorMappingResult;

  /** Include stack traces in error responses. Defaults to true in dev/test. */
  includeStackInResponse?: boolean;

  /** Application environment. */
  env?: AppEnvironment;
}

/**
 * Create default error response body
 */
function createDefaultErrorBody(
  err: unknown,
  includeStack: boolean,
  requestId?: string,
): ErrorResponseBody {
  return createErrorResponseBody({
    code: "INTERNAL_SERVER_ERROR",
    message: "Internal server error",
    requestId,
    details:
      includeStack && err instanceof Error
        ? {
            error: {
              message: err.message,
              stack: err.stack,
            },
          }
        : undefined,
  });
}

/**
 * Default error mapping function that handles unknown errors and converts them
 * to a standard error response format.
 *
 * **Important:** This function does NOT handle AppError instances from @beignet/core/errors.
 * AppError is handled separately in the router's error handling flow before reaching
 * this function. This function is only called for truly unexpected errors that bypass normal
 * error handling (e.g., unhandled exceptions, infrastructure errors).
 *
 * This function:
 * 1. First tries the custom mapErrorToResponse if provided
 * 2. Falls back to a default 500 error response
 * 3. Optionally includes stack traces in development/test environments
 *
 * @param err - The error that was thrown (excluding AppError instances)
 * @param ctx - The request context
 * @param config - Error mapping configuration
 * @returns An error mapping result with status, body, and optional headers
 *
 * @example
 * ```ts
 * const errorConfig = {
 *   mapErrorToResponse: (err, ctx) => ({
 *     status: 500,
 *     body: {
 *       code: "INTERNAL_SERVER_ERROR",
 *       message: "Custom error",
 *       requestId: ctx.requestId,
 *     },
 *   }),
 *   includeStackInResponse: true,
 *   env: "development",
 * };
 *
 * const result = defaultMapErrorToResponse(error, ctx, errorConfig);
 * ```
 */
export function defaultMapErrorToResponse<Ctx>(
  err: unknown,
  ctx: Ctx,
  config: ErrorMappingConfig<Ctx>,
): ErrorMappingResult {
  // First, try the user's custom error handler
  if (config.mapErrorToResponse) {
    try {
      return config.mapErrorToResponse(err, ctx);
    } catch {
      // Fall through to default error response below
    }
  }

  // Determine if stack traces should be included
  const includeStack =
    config.includeStackInResponse ??
    (config.env === "development" || config.env === "test");

  // Default error response
  const requestId = getRequestIdFromContext(ctx);
  const body = createDefaultErrorBody(err, includeStack, requestId);

  return {
    status: 500,
    body,
    headers: { "Content-Type": "application/json" },
  };
}
