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

/**
 * Reviews domain — the **moderated trust signal** (b14). A customer leaves **one review per completed
 * booking**; it is born `pending_moderation` and is **never public / never counted** until an admin (or the
 * AI pre-screen) publishes it; the public read only ever returns `published` reviews + the recomputed
 * aggregate. Shapes are derived from the b14 contract (`dev/contracts/domains/reviews-records.md` +
 * `dev/contracts/openapi/swagger.v1.json`), mirroring the wire exactly.
 *
 * **What the contract serves (real):** submit a review (`POST bookings/{id}/review`), the public nurse
 * reviews page (`GET nurses/{id}/reviews` — aggregate + published page), the per-nurse tag rollup, and the
 * admin moderation transition (admin-only → f15). **What it does NOT serve (client gaps, REQ-026):** a
 * **review-eligibility** read (whether *this* booking can still be reviewed) and a **my-review-for-booking**
 * read (the customer's own submitted review + its moderation state, so the CTA can show the persistent
 * "under review" state). Those two are derived/mocked behind this seam and the domain is **mock-primary**
 * (see `constants.ts`); when REQ-026 lands only `apis/clientApi.ts`'s two gap methods flip.
 *
 * Load-bearing rules (contract + phase §5):
 *  - **Never render `pending_moderation`/`hidden`/`rejected` publicly.** `getNurseReviews` returns published
 *    only (server-filtered); after a submit we show a **local** "under review" state and **never**
 *    optimistically inject the new review into any public list or aggregate.
 *  - **1:1** — one review per booking; a second submit is a `409`.
 *  - Review-eligible = the booking is **completed/closed** AND not already reviewed.
 *  - Tag chip labels are **i18n keys keyed off the code**, never a label off the wire.
 *
 * Enums cross the wire as stable string codes — mirrored here as string-literal unions.
 */

/** `moderationStatus` (contract enum). Born `pending_moderation`; only `published` is ever public/counted. */
export type ModerationStatus = 'pending_moderation' | 'published' | 'hidden' | 'rejected';

/**
 * The seeded review-tag vocabulary (contract "Review tag codes"). The chip **label** is the client's i18n
 * key (`reviews.tag_{code}`), never a label off the wire.
 */
export const REVIEW_TAG_CODES = ['punctual', 'professional', 'clean', 'kind', 'communicative'] as const;
export type ReviewTagCode = (typeof REVIEW_TAG_CODES)[number];

/** A public review row (`ReviewListItemDto`) — **never** carries moderation internals or a customer name. */
export interface ReviewListItem {
  id: number;
  /** 1–5. */
  rating: number;
  body: string | null;
  tagCodes: string[];
  /** UTC ISO-8601 — Shamsi display is the client's job. */
  createdAt: string;
}

/** The nurse rating aggregate (`NurseReviewAggregateDto`) — computed **from published reviews only**, server-side. */
export interface NurseReviewAggregate {
  /** 2-dp decimal (e.g. `4.5`). */
  averageRating: number;
  publishedCount: number;
}

/** `NurseReviewsResult` — the public reviews page for a nurse: the aggregate + a page of published reviews. */
export interface NurseReviews {
  aggregate: NurseReviewAggregate;
  reviews: Paginated<ReviewListItem>;
}

/** The submit body (`SubmitReviewBody`; the booking id comes from the route). */
export interface CreateReviewRequest {
  /** 1–5, required. */
  rating: number;
  /** ≤ 2000, optional. */
  body?: string | null;
  /** Validated against the active vocabulary; optional. */
  tagCodes?: string[];
}

/** `SubmitReviewResult`. `moderationStatus` is `pending_moderation` by default (clean text stays pending). */
export interface SubmitReviewResult {
  id: number;
  moderationStatus: ModerationStatus;
  /** Internal (rating ≤ threshold raised a support alert) — never surfaced to the user; we ignore it in the UI. */
  lowRatingAlertRaised: boolean;
}

/** Why a booking cannot be reviewed (client-derived; drives the not-eligible copy). */
export type ReviewIneligibilityReason = 'not_completed' | 'already_reviewed' | 'not_owner' | 'not_found';

/**
 * Whether *this* booking can still be reviewed (**REQ-026 — no wire endpoint**). Derived from the booking
 * status (completed/closed) + the 1:1 rule. Mocked behind the seam; the real client targets a proposed slug.
 */
export interface ReviewEligibility {
  canReview: boolean;
  reason?: ReviewIneligibilityReason;
}

/**
 * The customer's **own** review for a booking + its current moderation state (**REQ-026 — no wire
 * endpoint**). `status: 'none'` = not yet reviewed. Lets the CTA render the persistent "under review" /
 * "published" state across sessions without leaking anything public.
 */
export interface MyReviewState {
  status: ModerationStatus | 'none';
  rating: number | null;
  body: string | null;
  tagCodes: string[];
  createdAt: string | null;
}

// ── Admin moderation queue (b14 admin routes; admin/moderator-only) ─────────────────────────────────────────

/**
 * Moderation **action** (the `PATCH .../status` body). `hide`/`reject` require a non-empty `reason`;
 * `unpublish` returns a `published` review to `pending_moderation`. The human decision always overrides the AI.
 */
export type ModerationAction = 'publish' | 'hide' | 'reject' | 'unpublish';

/**
 * A row in the admin moderation worklist (`ModerationQueueItemDto`). Unlike the public `ReviewListItem` this
 * **does** carry moderation internals — the linked `lowRatingAlertId` (id only; support alerts stay internal),
 * the `moderationReason`, and the customer/nurse profile ids the moderator needs. Never rendered on a user
 * surface. Note: the wire dto does **not** carry `tagCodes` (REQ-037) — the client defaults it to `[]`.
 */
export interface ModerationQueueItem {
  id: number;
  bookingId: number;
  nurseProfileId: number;
  customerProfileId: number;
  rating: number;
  body: string | null;
  tagCodes: string[];
  moderationStatus: ModerationStatus;
  moderationReason: string | null;
  lowRatingAlertId: number | null;
  createdAt: string;
}

/** Moderation-queue filter. `status` defaults to `pending_moderation` (the worklist) when omitted. */
export interface ModerationQueueFilters {
  status?: ModerationStatus;
}

/**
 * `ModerateReviewResult` — the outcome of a transition plus the **server-recomputed-from-source** nurse
 * aggregate (`averageRating`/`totalReviews`). The client never computes these; it renders them and invalidates
 * the affected nurse's public list so the new average/count show up.
 */
export interface ModerateReviewResult {
  id: number;
  moderationStatus: ModerationStatus;
  averageRating: number;
  totalReviews: number;
}

/**
 * The reviews API seam — the real HTTP client and the in-memory mock both implement this; selection is by
 * config (`USE_REVIEWS_MOCK`), never scattered `if (mock)` checks.
 */
export interface ReviewsApi {
  /** Public — published reviews + aggregate for a nurse (server filters to published). */
  getNurseReviews(nurseProfileId: number, params: PageParams): Promise<NurseReviews>;
  /** REQ-026 — can this booking still be reviewed? */
  getReviewEligibility(bookingId: number): Promise<ReviewEligibility>;
  /** REQ-026 — the customer's own review for this booking + its moderation state. */
  getMyReviewForBooking(bookingId: number): Promise<MyReviewState>;
  /** Submit the one review for a completed booking (`409` if already reviewed). */
  createReview(bookingId: number, body: CreateReviewRequest): Promise<SubmitReviewResult>;
  /** Admin — the moderation worklist, filtered by status + paginated. */
  listModerationQueue(filters: ModerationQueueFilters, params: PageParams): Promise<Paginated<ModerationQueueItem>>;
  /** Admin — transition a review; returns the recomputed nurse aggregate. `hide`/`reject` need a `reason`. */
  moderateReview(reviewId: number, action: ModerationAction, reason?: string): Promise<ModerateReviewResult>;
}
