/**
 * error-envelope.ts — single `respond()` helper that builds the standard
 * `{ ok, error, code }` JSON envelope used across all API routes.
 *
 * Spec 01 — defineApi route adapter.
 *
 * ALL error responses MUST pass through here to guarantee envelope consistency.
 * Success responses with arbitrary data shapes bypass this helper (they are
 * built by the adapter's result translator).
 *
 * Default HTTP status per error code (handler can override via `status` field):
 *   AUTH_REQUIRED    → 401
 *   FORBIDDEN        → 403
 *   NOT_FOUND        → 404
 *   CONFLICT         → 409
 *   VALIDATION_ERROR → 422  (contact uses 422; personal-deals uses 400 — handler override)
 *   INVALID_BODY     → 400
 *   BODY_TOO_LARGE   → 413
 *   CSRF_INVALID     → 403
 *   CONFIG_ERROR     → 503
 *   INTERNAL_ERROR   → 500
 *   (anything else)  → 400
 */

import type { BuiltInErrorCode } from './types.js';

/** Default HTTP status map for well-known error codes. */
const STATUS_FROM_CODE: Record<string, number> = {
  AUTH_REQUIRED: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  CONFLICT: 409,
  VALIDATION_ERROR: 422,
  INVALID_BODY: 400,
  BODY_TOO_LARGE: 413,
  CSRF_INVALID: 403,
  CONFIG_ERROR: 503,
  INTERNAL_ERROR: 500,
  // Aliases used by some handlers
  DEAL_NOT_FOUND: 404,
  DEAL_NOT_SOLD_OUT: 409,
  RATE_LIMITED: 429,
};

/**
 * Returns the default HTTP status for a given error code.
 * Falls back to 400 for unknown handler-specific codes.
 */
export function statusFromCode(code: string): number {
  return STATUS_FROM_CODE[code] ?? 400;
}

/**
 * Build an error Response with the standard `{ ok: false, error, code }` envelope.
 *
 * @param code  — Error code string (BuiltInErrorCode or handler-specific).
 * @param error — Human-readable error message.
 * @param status — HTTP status override. Defaults to `statusFromCode(code)`.
 */
export function respondError(
  code: BuiltInErrorCode | string,
  error: string,
  status?: number,
): Response {
  return Response.json({ ok: false, error, code }, { status: status ?? statusFromCode(code) });
}

/**
 * Build a success Response.
 *
 * When `data` is provided, the response is `{ ok: true, ...data }` (flat merge),
 * so handlers can include top-level fields like `{ ok: true, requestId: '...' }`.
 *
 * When `data` is absent/undefined, the response is `{ ok: true }`.
 *
 * @param data   — Payload to merge into the response. Should be a plain object.
 * @param status — HTTP status. Defaults to 200.
 */
export function respondOk(
  data?: unknown,
  status?: number,
  headers?: Record<string, string> | Headers,
): Response {
  const body =
    data != null && typeof data === 'object' && !Array.isArray(data)
      ? { ok: true, ...data }
      : data !== undefined
        ? { ok: true, data }
        : { ok: true };

  const resolvedStatus = status ?? 200;
  // 204/205/304 are null-body statuses — constructing a Response with a body throws
  // a TypeError in the Fetch/Workers runtime. Return bodyless response instead.
  if (resolvedStatus === 204 || resolvedStatus === 205 || resolvedStatus === 304) {
    return new Response(null, { status: resolvedStatus, headers: headers ?? undefined });
  }

  const responseHeaders = new Headers(headers ?? undefined);
  responseHeaders.set('Content-Type', 'application/json');

  return Response.json(body, { status: resolvedStatus, headers: responseHeaders });
}
