/**
 * Service Catalog types (`/catalog`).
 *
 * These mirror the backend Prisma schema and the three catalog controllers
 * exactly — the backend is the source of truth. Notable constraints baked in
 * here so callers can't get them wrong:
 *
 * - The Admin owns the catalog: it creates `WashPackage` rows and the price
 *   range each one may be sold at. A Provider does NOT create services — it
 *   opts into an admin package and picks its own price inside that range
 *   (`ProviderService`). See `Notes/requitements/01_changes.txt`.
 * - The backend runs `ValidationPipe({ forbidNonWhitelisted: true })`, so any
 *   unknown query/body key returns 400. Request types are therefore closed —
 *   don't add convenience fields the API doesn't declare.
 * - Neither list endpoint has a sort parameter (both hardcode
 *   `orderBy: { name: 'asc' }`), which is why the params types have no sort
 *   field and the list columns aren't marked `sortable`.
 * - `isActive` is settable only at the DB level: `UpdateWashPackageDto` has no
 *   `isActive` field and there is no activate/deactivate endpoint, so the UI
 *   reads and filters it but never writes it.
 * - There is no `nameAr`/`descriptionAr` — a package has ONE name and ONE
 *   description, rendered as typed in both languages.
 */

// ─── Shared ──────────────────────────────────────────────────────────────────

/**
 * A catalog wash package, as returned raw by the admin and customer endpoints.
 *
 * `icon` is a free-text identifier chosen by the frontend (the backend's own
 * DTO examples are `exterior_wash` / `internal_external_wash`), NOT a URL —
 * see `lib/constants/package-icons.ts` for the identifier → glyph map.
 *
 * Soft delete mangles `name` with a `[DELETED-<timestamp>]` suffix to free the
 * unique constraint, so a row with `isDeleted: true` must never be shown by
 * name. The list endpoints already filter them out.
 */
export interface WashPackage {
  id: string;
  name: string;
  description: string | null;
  /** Minutes */
  estimatedDuration: number;
  features: string[];
  icon: string | null;
  /** Lowest price a provider may charge for this package, in SAR */
  minPrice: number;
  /** Highest price a provider may charge for this package, in SAR */
  maxPrice: number;
  isActive: boolean;
  isDeleted: boolean;
  createdAt: string;
  updatedAt: string;
}

/** Envelope shared by every catalog list endpoint */
interface CatalogPage<TRow> {
  data: TRow[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

// ─── Admin list ──────────────────────────────────────────────────────────────

/** Query accepted by `GET /catalog/admin/packages` — every field optional */
export interface AdminPackageListParams {
  /** Matches `name` only (contains, case-insensitive) */
  search?: string;
  /**
   * Backend reads this as the STRING "true"/"false" (`@IsBooleanString`) — the
   * api layer stringifies it, callers pass a real boolean.
   */
  isActive?: boolean;
  /** 1-based */
  page?: number;
  limit?: number;
}

export type AdminPackageListResponse = CatalogPage<WashPackage>;

// ─── Admin mutations ─────────────────────────────────────────────────────────

/**
 * Body for `POST /catalog/admin/packages`.
 *
 * `maxPrice` must be >= `minPrice` (400 otherwise) and `name` must be globally
 * unique across non-deleted packages (409 otherwise).
 */
export interface CreateWashPackageRequest {
  name: string;
  description?: string;
  /** Minutes, integer >= 1 */
  estimatedDuration: number;
  features: string[];
  icon?: string;
  minPrice: number;
  maxPrice: number;
}

/**
 * Body for `PUT /catalog/admin/packages/:id` — every field optional, omitted
 * fields keep their stored value. Deliberately no `isActive`: the backend DTO
 * doesn't declare it and would reject it.
 */
export type UpdateWashPackageRequest = Partial<CreateWashPackageRequest>;

// ─── Provider list ───────────────────────────────────────────────────────────

/** Query accepted by `GET /catalog/provider/packages` */
export interface ProviderPackageListParams {
  search?: string;
  /** Stringified by the api layer, same as the admin `isActive` filter */
  isOffered?: boolean;
  page?: number;
  limit?: number;
}

/**
 * A catalog package as seen by the provider that may offer it.
 *
 * Flattened by the backend rather than nested, and it deliberately omits
 * `isDeleted` — the endpoint only ever returns active, non-deleted packages.
 */
export interface ProviderPackage {
  id: string;
  name: string;
  description: string | null;
  estimatedDuration: number;
  features: string[];
  icon: string | null;
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
  /** Whether this provider currently offers the package */
  isOffered: boolean;
  /** Admin-defined floor, in SAR */
  minPrice: number;
  /** Admin-defined ceiling, in SAR */
  maxPrice: number;
  /** This provider's own price — `null` while not offered */
  price: number | null;
}

export type ProviderPackageListResponse = CatalogPage<ProviderPackage>;

// ─── Provider mutations ──────────────────────────────────────────────────────

/**
 * Body for `POST /catalog/provider/packages/:id/toggle` — the ONLY write path
 * for a provider's services. There is no separate price endpoint: changing a
 * price means re-sending `optIn: true` with the new value.
 *
 * `optIn: false` HARD-deletes the ProviderService row, so the stored price is
 * lost and re-opting in always requires a fresh price. It also fails with 400
 * if past bookings reference the row.
 */
export interface ToggleProviderPackageRequest {
  optIn: boolean;
  /** Required when `optIn` is true; must fall within [minPrice, maxPrice] */
  price?: number;
}

/** Note: this endpoint answers 201, not 200, and echoes back no price */
export interface ToggleProviderPackageResponse {
  success: boolean;
  packageId: string;
  isOffered: boolean;
}
