import type { PageParams, Paginated } from '@/lib/api/types';

/**
 * Catalog domain — the admin-seeded skeleton (categories → option groups → option values) and
 * the nurse pricing layer (variants — the atomic bookable unit). Shapes mirror the b5 contract
 * (`dev/contracts/domains/catalog.md`) exactly. The wire is **camelCase** (`nameFa`/`serviceCategoryId`/
 * `priceUnit`), not the snake_case the routing convention implies — the swagger snapshot is the source
 * of truth.
 *
 * Load-bearing semantics (see the contract "Key semantics"):
 *  - The bookable unit is the **variant**, never the nurse or the category.
 *  - `price` is **IRR Rials, integer, a string of digits** (e.g. `"2800000"`) — never a float, never Toman.
 *    The engagement total is `price` + `priceUnit` + `sessionCount`, never `price` alone.
 *  - A `serviceCategoryId = null` option group is **cross-category** — it applies to every category.
 *  - Categories/option groups/values/variants are **reference data + soft-deactivated**, never hard-deleted.
 */

/** The five price units a variant's `price` may be quoted in (closed enum; the only hardcoded catalog set). */
export type PriceUnit = 'per_hour' | 'per_session' | 'per_half_day' | 'per_day' | 'per_24h';

/** Ordered for the price-unit select; labels are i18n keys off the code (never derived from the code). */
export const PRICE_UNITS: readonly PriceUnit[] = [
  'per_hour',
  'per_session',
  'per_half_day',
  'per_day',
  'per_24h',
] as const;

/** Fields every localisable catalog row carries — `nameFa` is primary, the client picks by locale. */
export interface CatalogName {
  nameFa: string;
  nameEn: string;
}

/** `ServiceCategoryDto` — a top-level service category (Elderly Care, Post-Surgery Recovery, …). */
export interface ServiceCategory extends CatalogName {
  id: number;
  descriptionFa: string | null;
  descriptionEn: string | null;
  /** Optional icon hint; the tile maps known keys to a registered icon and falls back gracefully. */
  iconKey: string | null;
  sortOrder: number;
  isActive: boolean;
}

/** `OptionValueDto` — a concrete answer within an option group (e.g. شبانه‌روزی / Live-in). */
export interface ServiceOptionValue extends CatalogName {
  id: number;
  sortOrder: number;
  isActive: boolean;
}

/**
 * `OptionGroupDto` — a configurable dimension for a category (e.g. نوع شیفت / Shift type).
 * `serviceCategoryId = null` ⇒ **cross-category** (applies to every category). `isRequired` groups
 * must be answered (one value) before a variant can be created.
 */
export interface ServiceOptionGroup extends CatalogName {
  id: number;
  serviceCategoryId: number | null;
  isRequired: boolean;
  sortOrder: number;
  isActive: boolean;
  values: ServiceOptionValue[];
}

/** `VariantOptionDto` — one answered dimension on a variant (denormalised group + value labels). */
export interface VariantOption {
  optionGroupId: number;
  groupNameFa: string;
  groupNameEn: string;
  optionValueId: number;
  valueNameFa: string;
  valueNameEn: string;
}

/** `VariantDto` — a nurse's priced offering: category + chosen option values + own price + unit. */
export interface NurseServiceVariant {
  id: number;
  serviceCategoryId: number;
  categoryNameFa: string;
  categoryNameEn: string;
  /** IRR Rials as a string of digits (money-and-types.md). Rendered only via the money util. */
  price: string;
  priceUnit: PriceUnit;
  sessionCount: number | null;
  displayName: string;
  isActive: boolean;
  options: VariantOption[];
}

/** One answered dimension in a create request — one value per group (mirrors the server UNIQUE). */
export interface VariantOptionSelection {
  optionGroupId: number;
  optionValueId: number;
}

/**
 * `nurse_variants/create` body. `price` is the IRR digit-string (converted from Toman at the field
 * boundary). Omit `displayName` to let the server auto-generate it from the category + value labels.
 */
export interface CreateVariantInput {
  serviceCategoryId: number;
  options: VariantOptionSelection[];
  price: string;
  priceUnit: PriceUnit;
  sessionCount?: number | null;
  displayName?: string | null;
}

/**
 * `nurse_variants/update/{id}` body — edits price/unit/session/display only. The option-set is
 * **immutable** on update (changing dimensions = create-new + deactivate-old). A blank `displayName`
 * leaves the current one unchanged.
 */
export interface UpdateVariantInput {
  price: string;
  priceUnit: PriceUnit;
  sessionCount?: number | null;
  displayName?: string | null;
}

/**
 * The catalog domain's API seam — the real HTTP client and the in-memory mock both implement this
 * interface; selection is by config (`USE_CATALOG_MOCK`), never scattered `if (mock)` checks.
 */
export interface CatalogApi {
  /** Public reference data — active categories, ordered by `sortOrder`, paginated + cached. */
  listCategories(params?: PageParams): Promise<Paginated<ServiceCategory>>;
  /** A category's applicable option groups (its own + every cross-category group), with values. Cached. */
  getCategoryOptionGroups(categoryId: number): Promise<ServiceOptionGroup[]>;
  /** The signed-in nurse's own offerings — active and inactive, active-first, paginated. */
  listMyVariants(params?: PageParams): Promise<Paginated<NurseServiceVariant>>;
  getVariant(id: number): Promise<NurseServiceVariant>;
  createVariant(input: CreateVariantInput): Promise<NurseServiceVariant>;
  updateVariant(id: number, input: UpdateVariantInput): Promise<NurseServiceVariant>;
  /** Soft deactivate/reactivate — never a hard delete. */
  setVariantActive(id: number, isActive: boolean): Promise<void>;
}

/**
 * The applicable option groups for a category = its own groups **plus** every cross-category
 * (`serviceCategoryId === null`) group. The server already returns exactly this set from
 * `getCategoryOptionGroups`; this predicate documents the rule and is used by the mock.
 */
export function isGroupApplicable(group: ServiceOptionGroup, categoryId: number): boolean {
  return group.serviceCategoryId === null || group.serviceCategoryId === categoryId;
}

/**
 * A stable, order-independent signature of a variant's answered option-set, used for the
 * duplicate-listing guard (same nurse + same category + identical option-set → 409). The server's
 * uniqueness is the source of truth; this lets the mock reproduce the 409 and the builder pre-warn.
 */
export function optionSetSignature(categoryId: number, valueIds: number[]): string {
  return `${categoryId}:${[...valueIds].sort((a, b) => a - b).join(',')}`;
}
