/**
 * The one error type controllers and services throw. `errorHandler` is the only
 * code that turns one of these into a response body (PLANNING.md §5).
 *
 * `fields` carries the per-field validation map the enquiry contract requires
 * (`{ ok: false, error, fields }`, keyed to EnquiryInput field names — §9).
 * `expose` marks whether `message` is safe to send to the client.
 */
/**
 * Which response envelope `errorHandler` writes:
 *   'plain'   -> { error, fields?, requestId }        (default, every route)
 *   'ok-false' -> { ok: false, error, fields? }        (POST /enquiries — the
 *                 discriminated union the frontend's enquiryResponseSchema wants)
 */
export type ErrorEnvelope = 'plain' | 'ok-false';

export class AppError extends Error {
  readonly status: number;
  readonly expose: boolean;
  readonly fields?: Record<string, string>;
  readonly envelope: ErrorEnvelope;

  constructor(
    status: number,
    message: string,
    opts: {
      expose?: boolean;
      fields?: Record<string, string>;
      envelope?: ErrorEnvelope;
      cause?: unknown;
    } = {},
  ) {
    super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
    this.name = 'AppError';
    this.status = status;
    // 4xx messages are shown to the client by default; 5xx are not.
    this.expose = opts.expose ?? status < 500;
    this.fields = opts.fields;
    this.envelope = opts.envelope ?? 'plain';
    Error.captureStackTrace?.(this, AppError);
  }

  static badRequest(message = 'Bad request', fields?: Record<string, string>): AppError {
    return new AppError(400, message, { fields });
  }

  static unauthorized(message = 'Unauthorized'): AppError {
    return new AppError(401, message);
  }

  static forbidden(message = 'Forbidden'): AppError {
    return new AppError(403, message);
  }

  static notFound(message = 'Not found'): AppError {
    return new AppError(404, message);
  }

  /** Contract validation failure — HTTP 422 with a field map (PLANNING.md §1a M1). */
  static validation(fields: Record<string, string>, message = 'Validation failed'): AppError {
    return new AppError(422, message, { fields });
  }

  /**
   * `POST /enquiries` validation failure — HTTP 422, but rendered as the
   * `{ ok: false, error, fields }` discriminated-union body the frontend parses
   * (PLANNING.md §1a M1). Keeps errorHandler the one place a body is written.
   */
  static enquiryValidation(
    fields: Record<string, string>,
    message = 'Validation failed',
  ): AppError {
    return new AppError(422, message, { fields, envelope: 'ok-false' });
  }
}
