/**
 * Enumeration of every business/domain error code the API can return.
 *
 * This is the single source of truth for error identification across the
 * whole monorepo: `apps/api` throws errors carrying one of these codes,
 * and `apps/web` maps each code to a localized, user-facing label (see
 * `apps/web/src/services/error-message.service.ts`, backed by i18next
 * locale files under `apps/web/src/locales/`). Neither side should ever
 * hardcode a raw error value that the other side has to guess at — always
 * reference `ErrorCode.XXX`, never a bare number/string.
 *
 * Numeric values (not string codes): grouped by category so the number
 * itself hints at the kind of failure, similar in spirit to HTTP status
 * code families —
 *   - `4000`–`4099`: request validation
 *   - `4010`–`4019`: authentication
 *   - `4020`–`4029`: conflicting/invalid state transition
 *   - `4030`–`4039`: authorization (caller authenticated, but not allowed to)
 *   - `4040`–`4049`: not found
 *   - `5000`–`5099`: internal/unexpected
 *
 * When adding a new failure case in the API:
 *   1. Add a new member here, in the right range, with the next free number.
 *   2. Throw it via `HttpError` (`@batch-cooking/express-tools`).
 *   3. Add its translation key to every locale file under
 *      `apps/web/src/locales` (one `translation.json` per language).
 */
export enum ErrorCode {
  /** Request body/query failed zod schema validation. */
  VALIDATION_ERROR = 4000,
  /** Signup attempted with an email that already has a profile. */
  EMAIL_ALREADY_IN_USE = 4001,
  /** Login failed — wrong email or wrong password (never say which). */
  INVALID_CREDENTIALS = 4010,
  /** Request required a session cookie/JWT that is missing, invalid, or stale. */
  NOT_AUTHENTICATED = 4011,
  /** `POST /house` or `POST /house/join` attempted while the profile already belongs to a household. */
  ALREADY_HAS_HOUSE = 4020,
  /** `DELETE /recipes/:id` attempted on a recipe still referenced by at least one `PlanningItem`. */
  RECIPE_IN_USE = 4021,
  /** `POST /sources/:sourceKey/import/:externalId` attempted on an item already imported (a `Recipe` already exists for that `sourceId`/`externalId` pair). */
  RECIPE_ALREADY_IMPORTED = 4022,
  /** `POST /admin/tech-steps/retrain` attempted while a previous retrain (F1 gate + backfill) is still running — it holds a process-wide lock so two can't overlap. */
  RETRAIN_ALREADY_RUNNING = 4023,
  /** A household action reserved to its admin (delete the household, remove a member) attempted by a non-admin member. */
  NOT_HOUSE_ADMIN = 4030,
  /** `PATCH /recipes/:id` or `DELETE /recipes/:id` attempted by someone other than the recipe's author — visibility controls reading, not writing. */
  NOT_RECIPE_AUTHOR = 4031,
  /** No route/resource matches the request. */
  NOT_FOUND = 4040,
  /** The profile making the request has no household yet (`houseId` is `null`). */
  HOUSE_NOT_FOUND = 4041,
  /** A `dietId` was given that doesn't match any reference `Diet` row. */
  DIET_NOT_FOUND = 4042,
  /** One or more `allergyIds` don't match any reference `Allergy` row. */
  ALLERGY_NOT_FOUND = 4043,
  /** `POST /house/join`'s `inviteCode` doesn't match any household. */
  INVITE_CODE_NOT_FOUND = 4044,
  /** `GET /recipes/:id`, `PATCH /recipes/:id` or `DELETE /recipes/:id` given an id that doesn't match any recipe. */
  RECIPE_NOT_FOUND = 4045,
  /** A recipe payload's `ingredientId` doesn't match any reference `Ingredient` row. */
  INGREDIENT_NOT_FOUND = 4046,
  /** `DELETE /planning/items/:id` given an id that doesn't match any planning item visible to the caller's household. */
  PLANNING_ITEM_NOT_FOUND = 4047,
  /** A recipe payload's `unitId` doesn't match any reference `Unit` row. */
  UNIT_NOT_FOUND = 4048,
  /** `PATCH /house/current/sources`'s `sourceIds` contains one that doesn't match any reference `Source` row. */
  SOURCE_NOT_FOUND = 4049,
  /** `POST /recipes/:id/steps/:stepId/corrections` given a `stepId` that doesn't belong to a recipe visible to the caller. */
  STEP_NOT_FOUND = 4050,
  /** A tech-step correction's `previousTechStepId`/`correctedTechStepId` doesn't match any reference `TechStep` row. */
  TECH_STEP_NOT_FOUND = 4051,
  /** A tech-step correction's manually-attached `utensils[].utensilId` doesn't match any reference `Utensil` row. */
  UTENSIL_NOT_FOUND = 4052,
  /** A tech-step correction's `start`/`end` span falls outside the target step's `description`, or `start >= end`. */
  INVALID_CORRECTION_SPAN = 4002,
  /** Unexpected/unhandled failure — the catch-all, always logged server-side. */
  INTERNAL_ERROR = 5000,
}

/**
 * Shape of every JSON error body the API returns, whatever the failure.
 * Kept intentionally small and stable: `code` is what clients should
 * branch on, `message` is a human-readable (English, developer-facing)
 * description useful for logs/debugging — never shown to end users as-is,
 * since end-user-facing text is localized client-side from `code`.
 */
export interface ApiErrorResponse {
  /** Machine-readable error identifier — see {@link ErrorCode}. */
  code: ErrorCode;
  /** Developer-facing description (English). Not localized, not for UI display. */
  message: string;
  /** Present only for VALIDATION_ERROR: per-field error messages from zod. */
  details?: Record<string, string[] | undefined>;
}
