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

/**
 * Booking-requests domain — the **pre-payment intent** layer of the engagement lifecycle (b8). A
 * customer requests a nurse for a patient/variant/address/date; the nurse accepts (opening a
 * config-driven 30-minute payment window) or rejects; both sides read a role-scoped inbox and a single
 * request. **There is no money and no `bookings` row here** — that conversion is b9/b10. Shapes mirror
 * the b8 contract (`dev/contracts/domains/booking-requests.md`); the wire is **camelCase** and
 * `clientFetch` unwraps the `ApiEnvelope<T>`, so these are the post-`unwrap()` payloads.
 *
 * Load-bearing semantics (contract "Key semantics" + phase §5):
 *  - **Deadlines are server-frozen absolute UTC instants.** The client renders countdowns from
 *    `nurseResponseDeadlineAt` / `paymentDeadlineAt` against `Date.now()` — it never computes a deadline.
 *    `paymentDeadlineAt` is `null` until the nurse accepts.
 *  - **`requiredCaregiverGender` is first-class and required on create** (`male`/`female`/`any`) — never
 *    silently defaulted. `male`/`female` must match the nurse's gender (a mismatch is a `400`).
 *  - **Two-stage clinical disclosure.** The nurse sees **only** `customerNotes` (stage-1 plaintext) and a
 *    **masked** address (`addressLine`/`postalCode`/recipient are `null` in the nurse view). Full
 *    clinical/care instructions do not exist until b9 and must never appear in this UI.
 *  - **Forward-only status machine.** Terminal states have no outgoing edges; a stale accept/reject/cancel
 *    returns `409`.
 */

/** The same-gender matching facet carried from search into the request (`any` = فرقی ندارد). */
export type RequiredCaregiverGender = 'male' | 'female' | 'any';

/** The full `booking_request_status` enum (contract). */
export type BookingRequestStatus =
  | 'pending_nurse_response'
  | 'accepted_awaiting_payment'
  | 'converted'
  | 'rejected_by_nurse'
  | 'expired_no_response'
  | 'payment_deadline_expired'
  | 'cancelled_by_customer';

/** Which inbox to read — disambiguates a user who holds both roles (contract `list?role=`). */
export type RequestRole = 'customer' | 'nurse';

/** Terminal states: no outgoing edges, so polling stops and no accept/reject/cancel is offered. */
export const TERMINAL_BOOKING_REQUEST_STATUSES: readonly BookingRequestStatus[] = [
  'converted',
  'rejected_by_nurse',
  'expired_no_response',
  'payment_deadline_expired',
  'cancelled_by_customer',
] as const;

export function isTerminalBookingRequestStatus(status: BookingRequestStatus): boolean {
  return TERMINAL_BOOKING_REQUEST_STATUSES.includes(status);
}

/**
 * `BookingRequestDto` — the full single-request view. The customer/admin view carries the full address;
 * the **nurse view masks it** (`addressLine`/`postalCode`/`recipientName`/`recipientPhone` are `null`),
 * leaving only the coarse city/district.
 *
 * `variantPrice` is **client-augmented**: the contract DTO returns `variantLabel` + `variantPriceUnit`
 * but no price (filed as REQ-013). The mock supplies it so the summary card can price the service; the
 * real client leaves it `null` (the summary then hides the amount) until the field lands.
 *
 * `bookingId` is likewise **client-augmented** (filed as REQ-017): once a paid request converts, the b8
 * DTO gives no way to reach the booking it became. The mock stamps it at capture so C5's `converted`
 * card and the checkout confirmation can deep-link the booking; the real client leaves it `null` (those
 * surfaces then fall back to the bookings list) until the field lands.
 */
export interface BookingRequestDto {
  id: number;
  status: BookingRequestStatus;
  /** Client-augmented (REQ-017): the converted booking's id, or `null` until converted / on the real path. */
  bookingId: number | null;
  nurseId: number;
  nurseName: string;
  nurseRating: number;
  nurseTotalReviews: number;
  patientId: number;
  patientName: string;
  variantId: number;
  variantLabel: string;
  variantPriceUnit: PriceUnit;
  /** Client-augmented (REQ-013): IRR digit-string, or `null` on the real path until the DTO carries it. */
  variantPrice: string | null;
  customerAddressId: number;
  addressTitle: string;
  cityId: number;
  cityNameFa: string;
  cityNameEn: string;
  districtId: number | null;
  districtNameFa: string | null;
  districtNameEn: string | null;
  /** Full-address PII — present in the customer/admin view, **`null` in the nurse view** (masked). */
  addressLine: string | null;
  postalCode: string | null;
  recipientName: string | null;
  recipientPhone: string | null;
  requiredCaregiverGender: RequiredCaregiverGender | null;
  /** ISO date `YYYY-MM-DD`. */
  requestedDate: string;
  /** `HH:mm:ss`. */
  requestedTimeStart: string;
  requestedTimeEnd: string;
  /** Stage-1 plaintext — the ONLY clinical text the nurse sees before accepting. */
  customerNotes: string | null;
  /** Server-frozen absolute UTC instant. */
  nurseResponseDeadlineAt: string;
  /** Server-frozen UTC; `null` until the nurse accepts. */
  paymentDeadlineAt: string | null;
  nurseRejectionReason: string | null;
  createdAt: string;
}

/**
 * `BookingRequestListItemDto` — an inbox row. The **customer** inbox sets `counterpartyName` = nurse name
 * (+ `nurseRating`); the **nurse** inbox sets `counterpartyName` = patient name (+ `customerNotes`,
 * stage-1 only). Actionable rows sort first server-side.
 */
export interface BookingRequestListItem {
  id: number;
  status: BookingRequestStatus;
  counterpartyName: string;
  /** Customer view only; `null` in the nurse inbox. */
  nurseRating: number | null;
  requiredCaregiverGender: RequiredCaregiverGender | null;
  requestedDate: string;
  requestedTimeStart: string;
  requestedTimeEnd: string;
  nurseResponseDeadlineAt: string;
  paymentDeadlineAt: string | null;
  /** Nurse view only (stage-1 plaintext); `null` in the customer inbox. */
  customerNotes: string | null;
}

/** The `booking_requests/create` body (contract). Money-free; ids come from search/patients/addresses. */
export interface CreateBookingRequestPayload {
  nurseId: number;
  variantId: number;
  patientId: number;
  customerAddressId: number;
  /** `YYYY-MM-DD`. */
  requestedDate: string;
  /** `HH:mm:ss`. */
  requestedTimeStart: string;
  requestedTimeEnd: string;
  /** Required, never silently defaulted. */
  requiredCaregiverGender: RequiredCaregiverGender;
  /** ≤ 1000 chars; the only text the nurse sees pre-accept. */
  customerNotes?: string | null;
}

/** The `booking_requests/reject` body. */
export interface RejectBookingRequestPayload {
  /** Required, ≤ 500 chars. */
  reason: string;
}

/** `booking_requests/list` query params (role-scoped, paginated, optional status filter). */
export interface BookingRequestListParams extends PageParams {
  role: RequestRole;
  status?: BookingRequestStatus;
}

/**
 * Display fields the mock needs to build a faithful DTO, resolved by the C4 form from the already-loaded
 * nurse profile / patient / address / variant queries. **The real `clientApi` ignores this** — the
 * server returns the joined DTO from the ids alone; only the mock (which cannot read the other domains'
 * in-memory stores) uses it. This is the b8 analogue of the search/patients/addresses "client-augmented"
 * display fields, kept off the wire `CreateBookingRequestPayload`.
 */
export interface BookingRequestDisplayContext {
  nurseName: string;
  nurseRating: number;
  nurseTotalReviews: number;
  patientName: string;
  variantLabel: string;
  variantPriceUnit: PriceUnit;
  variantPrice: string | null;
  addressTitle: string;
  cityId: number;
  cityNameFa: string;
  cityNameEn: string;
  districtId: number | null;
  districtNameFa: string | null;
  districtNameEn: string | null;
  addressLine: string | null;
  postalCode: string | null;
  recipientName: string | null;
  recipientPhone: string | null;
}

/**
 * The booking-requests API seam — the real HTTP client and the in-memory mock both implement this
 * interface; selection is by config (`USE_BOOKING_REQUESTS_MOCK`), never scattered `if (mock)` checks.
 *
 * `get`/`create` take an optional `role`/`context` that only the mock uses (to mask the nurse view and
 * to build a faithful DTO respectively); the real client infers the view from auth and ignores them.
 */
export interface BookingRequestsApi {
  create(payload: CreateBookingRequestPayload, context?: BookingRequestDisplayContext): Promise<BookingRequestDto>;
  get(id: number, role?: RequestRole): Promise<BookingRequestDto>;
  list(params: BookingRequestListParams): Promise<Paginated<BookingRequestListItem>>;
  accept(id: number): Promise<BookingRequestDto>;
  reject(id: number, payload: RejectBookingRequestPayload): Promise<BookingRequestDto>;
  cancel(id: number): Promise<BookingRequestDto>;
}
