import { type ApiErrorResponse, ErrorCode } from "@batch-cooking/shared";
import { ZodError } from "zod";
import { HttpError } from "./http-error.js";

/** Return value of {@link ErrorHandlerService.handle}: everything a caller needs to send an HTTP response. */
export interface ErrorHandlingResult {
  /** HTTP status code to respond with. */
  status: number;
  /** JSON body to respond with — matches the shared {@link ApiErrorResponse} contract. */
  body: ApiErrorResponse;
}

/**
 * Centralizes every "how do we turn a thrown error into an HTTP response"
 * decision in one place, so route handlers and framework-specific
 * middleware (e.g. `createErrorMiddleware` in `@batch-cooking/express-tools`)
 * never duplicate this logic. Framework-agnostic on purpose — it only maps
 * an error to `{ status, body }` and has zero dependency on Express or any
 * other HTTP framework.
 *
 * Recognizes three error shapes today (zod validation failures, our own
 * `HttpError`, and anything else) and always falls back to a safe, generic
 * 500 for the unknown case — a caller of `handle()` never needs its own
 * fallback branch.
 */
export class ErrorHandlerService {
  /**
   * Maps any thrown value into a status + body pair ready to send to the
   * client. Always succeeds — an error that doesn't match a known shape
   * becomes a generic {@link ErrorCode.INTERNAL_ERROR} and is logged.
   */
  public handle(error: unknown): ErrorHandlingResult {
    if (error instanceof ZodError) {
      return this._fromZodError(error);
    }
    if (error instanceof HttpError) {
      return this._fromHttpError(error);
    }
    return this._fromUnknownError(error);
  }

  /** Request body/query failed schema validation — always a 400. */
  private _fromZodError(error: ZodError): ErrorHandlingResult {
    return {
      status: 400,
      body: {
        code: ErrorCode.VALIDATION_ERROR,
        message: "Validation error",
        details: error.flatten().fieldErrors,
      },
    };
  }

  /** Our own typed error — status/code were decided by whoever threw it. */
  private _fromHttpError(error: HttpError): ErrorHandlingResult {
    return {
      status: error.status,
      body: { code: error.code, message: error.message },
    };
  }

  /**
   * Anything unrecognized: never leaks internal details to the client —
   * always a generic 500. Doesn't log the error itself — this class only
   * maps an error to `{ status, body }` (see the class doc comment); a
   * caller wanting this logged server-side does so on its own before/
   * around calling `handle()` (see `apps/api`'s `errorLogger` middleware,
   * which sees every error — including this exact "unrecognized" case —
   * before it ever reaches here).
   */
  private _fromUnknownError(_error: unknown): ErrorHandlingResult {
    return {
      status: 500,
      body: { code: ErrorCode.INTERNAL_ERROR, message: "Internal server error" },
    };
  }
}

/** Single shared instance — this service is stateless, no need for one per request. */
export const errorHandlerService = new ErrorHandlerService();
