import type { Freight } from "@edr/types";
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import { URL_CONSTANTS } from "@/constants/URLS";
import type {
  BatchBoardFilters,
  BatchBoardListResponse,
  BatchBoardScheduleDetail,
  BookableSchedule,
  BookingWindow,
  AssignBookingsPayload,
  BookingLoadResult,
  BookingUnloadResult,
  CompositionRemovalEntry,
  DocReviewAlert,
  UnassignedBookingsResponse,
  CreateTrainSchedulePayload,
  EligibleContainerBookingsResponse,
  FreightType,
  ImportDjiboutiActionPayload,
  ImportDjiboutiLoadList,
  ImportDjiboutiOperation,
  ImportLoadingBookingsResponse,
  IntercityAcceptResult,
  IntercityCandidatesResult,
  LoadingStatus,
  LocomotiveRecord,
  PinWagonsPayload,
  RecordCheckpointPayload,
  StaffBookingWindow,
  TrainScheduleDetail,
  UpdateScheduleWindowRulePayload,
  TrainScheduleFilters,
  TrainScheduleListFilters,
  TrainScheduleListResponse,
  TrainSchedulePreviewPayload,
  TrainSchedulePreviewResponse,
  TrainSchedulingGlobalRules,
  TrainTrackResponse,
  UploadImportDjiboutiDocumentPayload,
  WagonAllocationAttemptResult,
  YardOption,
  YardWorkResult,
} from "@/types/trainScheduling";

interface BookingReferenceDataResponse {
  yard?: Array<YardOption & { label?: string }>;
}

const pathsFor = (freightType?: FreightType) =>
  freightType === "BULK"
    ? URL_CONSTANTS.TRAIN_SCHEDULING.BULK
    : URL_CONSTANTS.TRAIN_SCHEDULING.CONTAINER;

export const trainSchedulingService = {
  getEligibleBookings: async (
    filters?: TrainScheduleFilters,
    freightType?: FreightType,
  ): Promise<EligibleContainerBookingsResponse> => {
    const useUnified = !freightType || freightType === "MIXED";
    // The container/bulk endpoints already encode freight type in the path, and their
    // query DTOs reject an extra `freightType` param — so only pass the station filters.
    const response = await client.get<EligibleContainerBookingsResponse>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.ELIGIBLE_BOOKINGS
        : pathsFor(freightType).ELIGIBLE_BOOKINGS,
      { params: { ...filters } },
    );
    return unwrap(response.data);
  },

  preview: async (
    payload: TrainSchedulePreviewPayload,
    freightType?: FreightType,
  ): Promise<TrainSchedulePreviewResponse> => {
    const useUnified = !freightType || freightType === "MIXED";
    const response = await client.post<TrainSchedulePreviewResponse>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW
        : pathsFor(freightType).PREVIEW,
      payload,
    );
    return unwrap(response.data);
  },

  createSchedule: async (
    payload: CreateTrainSchedulePayload,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      pathsFor(freightType).SCHEDULES,
      payload,
    );
    return unwrap(response.data);
  },

  listSchedules: async (
    freightType: FreightType = "CONTAINER",
    filters: TrainScheduleListFilters = {},
  ): Promise<TrainScheduleListResponse> => {
    const params: Record<string, string | number> = {};
    if (filters.page) params.page = filters.page;
    if (filters.pageSize) params.pageSize = filters.pageSize;
    if (filters.search?.trim()) params.search = filters.search.trim();
    if (filters.status) params.status = filters.status;
    if (filters.freightType) params.freightType = filters.freightType;
    if (filters.originStationId) params.originStationId = filters.originStationId;
    if (filters.destinationStationId)
      params.destinationStationId = filters.destinationStationId;
    if (filters.sortBy) params.sortBy = filters.sortBy;
    if (filters.sortOrder) params.sortOrder = filters.sortOrder;
    const response = await client.get<TrainScheduleListResponse>(
      pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULES,
      { params },
    );
    return unwrap(response.data);
  },

  getBatchBoard: async (
    filters: BatchBoardFilters = {},
  ): Promise<BatchBoardListResponse> => {
    const params: Record<string, string | number> = {};
    if (filters.page) params.page = filters.page;
    if (filters.pageSize) params.pageSize = filters.pageSize;
    if (filters.statuses?.length) params.statuses = filters.statuses.join(",");
    if (filters.bookingWindowStatus)
      params.bookingWindowStatus = filters.bookingWindowStatus;
    if (filters.search?.trim()) params.search = filters.search.trim();
    if (filters.departureFrom) params.departureFrom = filters.departureFrom;
    if (filters.departureTo) params.departureTo = filters.departureTo;
    if (filters.createdFrom) params.createdFrom = filters.createdFrom;
    if (filters.createdTo) params.createdTo = filters.createdTo;
    if (filters.sortBy) params.sortBy = filters.sortBy;
    if (filters.sortOrder) params.sortOrder = filters.sortOrder;
    const response = await client.get<BatchBoardListResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD,
      { params },
    );
    return unwrap(response.data);
  },

  getBatchBoardDetail: async (
    scheduleId: string,
  ): Promise<BatchBoardScheduleDetail> => {
    const response = await client.get<BatchBoardScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD_DETAIL(scheduleId),
    );
    return unwrap(response.data);
  },

  /**
   * Booking windows for every route/schedule of a contract. A window with
   * `isOpenNow === true` means GL may create a booking right now for that route.
   */
  getContractBookingWindows: async (
    contractId: string,
  ): Promise<BookingWindow[]> => {
    const response = await client.get<BookingWindow[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CONTRACT_BOOKING_WINDOWS(contractId),
    );
    return unwrap(response.data);
  },

  getBookableSchedules: async (
    originYardId?: string,
    destinationYardId?: string,
  ): Promise<BookableSchedule[]> => {
    const response = await client.get<BookableSchedule[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKABLE_SCHEDULES,
      { params: { originYardId, destinationYardId } },
    );
    return unwrap(response.data);
  },

  /**
   * Day-level pool: the days that have an OPEN departure on the route. Staff pick
   * a day; the batch engine assigns the train. No capacity is returned.
   */
  getAvailableDays: async (
    originYardId?: string,
    destinationYardId?: string,
  ): Promise<string[]> => {
    const response = await client.get<{ days: string[] }>(
      URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS,
      { params: { originYardId, destinationYardId } },
    );
    return unwrap(response.data).days;
  },

  // Cargo-aware day pool (matching wagons + open train capacity). `containers`
  // is serialized as a JSON string param (the server parses it).
  getAvailableDaysForCargo: async (
    query: Freight.AvailableDaysForCargoQuery,
  ): Promise<string[]> => {
    const { containers, ...rest } = query;
    const response = await client.get<{ days: string[] }>(
      URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS_FOR_CARGO,
      {
        params: {
          ...rest,
          ...(containers ? { containers: JSON.stringify(containers) } : {}),
        },
      },
    );
    return unwrap(response.data).days;
  },

  runBatch: async (scheduleId: string): Promise<BatchBoardScheduleDetail> => {
    const response = await client.post<BatchBoardScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.RUN_BATCH(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  /**
   * Staff finished reviewing documents early — runs the batch immediately
   * for the schedule's whole route-day group.
   */
  completeDocReview: async (
    scheduleId: string,
  ): Promise<BatchBoardScheduleDetail> => {
    const response = await client.post<BatchBoardScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.DOC_REVIEW_COMPLETE(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  runAllocation: async (
    scheduleId: string,
  ): Promise<WagonAllocationAttemptResult> => {
    const response = await client.post<WagonAllocationAttemptResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.RUN_ALLOCATION(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  setBookingWindow: async (
    scheduleId: string,
    status: "OPEN" | "CLOSED",
  ): Promise<TrainScheduleDetail> => {
    const response = await client.patch<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOW(scheduleId),
      { status },
    );
    return unwrap(response.data);
  },

  updateScheduleWindowRule: async (
    scheduleId: string,
    payload: UpdateScheduleWindowRulePayload,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.patch<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.WINDOW_RULE(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  updateScheduleDate: async (
    scheduleId: string,
    scheduleDate: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.patch<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.SCHEDULE_DATE(scheduleId),
      { scheduleDate },
    );
    return unwrap(response.data);
  },

  markBookingPaid: async (bookingId: string): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId),
      {},
    );
  },

  expireBooking: async (bookingId: string): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId),
      {},
    );
  },

  moveBookingSchedule: async (
    bookingId: string,
    trainScheduleId: string,
  ): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId),
      {
        trainScheduleId,
      },
    );
  },

  getScheduleById: async (
    id: string,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.get<TrainScheduleDetail>(
      pathsFor(
        freightType === "MIXED" ? undefined : freightType,
      ).SCHEDULE_BY_ID(id),
    );
    return unwrap(response.data);
  },

  assignUnassignedBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_UNASSIGNED_BOOKING(scheduleId),
      { bookingId },
    );
    return unwrap(response.data);
  },

  assignBookings: async (
    scheduleId: string,
    payload: AssignBookingsPayload,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const useUnified = !freightType || freightType === "MIXED";
    const response = await client.post<TrainScheduleDetail>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_BOOKINGS(scheduleId)
        : pathsFor(freightType).ASSIGN_BOOKINGS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  unassignBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.delete<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGN_BOOKING(scheduleId, bookingId),
    );
    return unwrap(response.data);
  },

  pinWagons: async (
    scheduleId: string,
    payload: PinWagonsPayload,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.PIN_WAGONS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  finalizeSchedule: async (
    scheduleId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.FINALIZE(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  getYardWork: async (scheduleId: string): Promise<YardWorkResult> => {
    const response = await client.get<YardWorkResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.YARD_WORK(scheduleId),
    );
    return unwrap(response.data);
  },

  loadScheduleBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<BookingLoadResult> => {
    const response = await client.post<BookingLoadResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_LOAD(scheduleId, bookingId),
      {},
    );
    return unwrap(response.data);
  },

  unloadScheduleBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<BookingUnloadResult> => {
    const response = await client.post<BookingUnloadResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_UNLOAD(scheduleId, bookingId),
      {},
    );
    return unwrap(response.data);
  },

  listIntercityBookings: async (): Promise<
    import("@/types/trainScheduling").IntercityRideAlongRow[]
  > => {
    const response = await client.get<
      import("@/types/trainScheduling").IntercityRideAlongRow[]
    >(URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_BOOKINGS);
    return response.data ?? [];
  },

  getIntercityCandidates: async (
    scheduleId: string,
  ): Promise<IntercityCandidatesResult> => {
    const response = await client.get<IntercityCandidatesResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_CANDIDATES(scheduleId),
    );
    return unwrap(response.data);
  },

  acceptIntercityBookings: async (
    scheduleId: string,
    bookingIds: string[],
  ): Promise<IntercityAcceptResult> => {
    const response = await client.post<IntercityAcceptResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_ACCEPT(scheduleId),
      { bookingIds },
    );
    return unwrap(response.data);
  },

  loadIntercityBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_LOAD(scheduleId, bookingId),
      {},
    );
  },

  unloadIntercityBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_UNLOAD(scheduleId, bookingId),
      {},
    );
  },

  dispatchSchedule: async (
    scheduleId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.DISPATCH(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  getImportLoadingBookings: async (
    scheduleId: string,
  ): Promise<ImportLoadingBookingsResponse> => {
    const response = await client.get<ImportLoadingBookingsResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_LOADING_BOOKINGS(scheduleId),
    );
    return unwrap(response.data);
  },

  updateImportLoadingStatus: async (
    scheduleId: string,
    payload: { bookingIds: string[]; loadingStatus: LoadingStatus },
  ): Promise<ImportLoadingBookingsResponse> => {
    const response = await client.patch<ImportLoadingBookingsResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_LOADING_STATUS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  setLoadingStatus: async (
    scheduleId: string,
    payload: { bookingIds: string[]; loadingStatus: LoadingStatus },
  ): Promise<TrainScheduleDetail> => {
    const response = await client.patch<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.LOADING_STATUS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  confirmLoading: async (
    scheduleId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CONFIRM_LOADING(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  getImportDjiboutiOperation: async (
    scheduleId: string,
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.get<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI(scheduleId),
    );
    return unwrap(response.data);
  },

  uploadImportDjiboutiDocument: async (
    scheduleId: string,
    payload: UploadImportDjiboutiDocumentPayload,
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.post<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_DOCUMENTS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  grantImportDjiboutiGatepass: async (
    scheduleId: string,
    payload: ImportDjiboutiActionPayload = {},
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.post<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_GATEPASS_GRANTED(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  markImportDjiboutiReadyForLoading: async (
    scheduleId: string,
    payload: ImportDjiboutiActionPayload = {},
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.post<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_READY_FOR_LOADING(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  confirmImportDjiboutiLoadedOnTrain: async (
    scheduleId: string,
    payload: ImportDjiboutiActionPayload = {},
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.post<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOADED_ON_TRAIN(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  departImportFromDjibouti: async (
    scheduleId: string,
    payload: ImportDjiboutiActionPayload = {},
  ): Promise<ImportDjiboutiOperation> => {
    const response = await client.post<ImportDjiboutiOperation>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_DEPART(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  generateImportDjiboutiLoadList: async (
    scheduleId: string,
    payload: ImportDjiboutiActionPayload = {},
  ): Promise<ImportDjiboutiLoadList> => {
    const response = await client.post<ImportDjiboutiLoadList>(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOAD_LIST(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  downloadImportDjiboutiLoadListDocument: async (
    scheduleId: string,
  ): Promise<Blob> => {
    const response = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOAD_LIST_DOCUMENT(scheduleId),
      { responseType: "blob" },
    );
    return response.data;
  },

  downloadExportLoadListDocument: async (
    scheduleId: string,
  ): Promise<Blob> => {
    const response = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.EXPORT_LOAD_LIST_DOCUMENT(scheduleId),
      { responseType: "blob" },
    );
    return response.data;
  },

  getTrack: async (scheduleId: string): Promise<TrainTrackResponse> => {
    const response = await client.get<TrainTrackResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
    );
    return unwrap(response.data);
  },

  recordCheckpoint: async (
    scheduleId: string,
    payload: RecordCheckpointPayload,
  ): Promise<TrainTrackResponse> => {
    const response = await client.post<TrainTrackResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  arriveSchedule: async (scheduleId: string): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.ARRIVE(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  cancelSchedule: async (
    id: string,
    freightType: FreightType = "CONTAINER",
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      pathsFor(
        freightType === "MIXED" ? undefined : freightType,
      ).CANCEL_SCHEDULE(id),
      {},
    );
    return unwrap(response.data);
  },

  getAvailableLocomotives: async (
    routeId?: string,
  ): Promise<LocomotiveRecord[]> => {
    if (routeId) {
      const response = await client.get<LocomotiveRecord[]>(
        URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_LOCOMOTIVES,
        { params: { routeId } },
      );
      return unwrap(response.data);
    }
    const response = await client.get<LocomotiveRecord[]>(
      URL_CONSTANTS.LOCOMOTIVES.BASE,
      {
        params: { status: "AVAILABLE" },
      },
    );
    return unwrap(response.data);
  },

  previewReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      trigger: string;
      reason?: string;
      newDepartureDate?: string;
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_PREVIEW(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  executeReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      trigger: string;
      reason?: string;
      newDepartureDate?: string;
      finalBookingIds: string[];
      displacedBookingIds: string[];
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_EXECUTE(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  maintenanceReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      newDepartureDate: string;
      reason?: string;
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MAINTENANCE(scheduleId),
      { ...payload, trigger: "TRAIN_MAINTENANCE" },
    );
    return unwrap(response.data);
  },

  getGlobalRules: async (): Promise<TrainSchedulingGlobalRules> => {
    const response = await client.get<TrainSchedulingGlobalRules>(
      URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
    );
    return unwrap(response.data);
  },

  getAllBookingWindows: async (): Promise<StaffBookingWindow[]> => {
    const response = await client.get<StaffBookingWindow[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOWS,
    );
    return unwrap(response.data);
  },

  getDocReviewAlert: async (): Promise<DocReviewAlert | null> => {
    const response = await client.get<DocReviewAlert | null>(
      URL_CONSTANTS.TRAIN_SCHEDULING.DOC_REVIEW_ALERT,
    );
    // "No alert" comes back as null — which Nest sends as an empty body, so
    // coerce anything falsy to null (react-query rejects undefined).
    return unwrap(response.data) || null;
  },

  updateGlobalRules: async (
    payload: Partial<Omit<TrainSchedulingGlobalRules, "id">>,
  ): Promise<TrainSchedulingGlobalRules> => {
    const response = await client.patch<TrainSchedulingGlobalRules>(
      URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
      payload,
    );
    return unwrap(response.data);
  },

  getStations: async (): Promise<YardOption[]> => {
    const response = await client.get<BookingReferenceDataResponse>(
      URL_CONSTANTS.BOOKINGS.REFERENCE_DATA,
    );
    const data = unwrap(response.data);
    return (data.yard ?? []).map((yard) => ({
      id: yard.id,
      name: yard.name ?? yard.label ?? yard.code,
      code: yard.code,
      country: yard.country,
    }));
  },

  removeWagonSlot: async (
    scheduleId: string,
    wagonId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.delete<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.REMOVE_WAGON_SLOT(scheduleId, wagonId),
    );
    return unwrap(response.data);
  },

  updateContainerItem: async (
    scheduleId: string,
    itemId: string,
    payload: { containerNumber: string | null },
  ): Promise<{ id: string; containerNumber: string | null }> => {
    const response = await client.patch<{
      id: string;
      containerNumber: string | null;
    }>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UPDATE_CONTAINER_ITEM(scheduleId, itemId),
      payload,
    );
    return unwrap(response.data);
  },

  moveWagonLoad: async (
    scheduleId: string,
    wagonId: string,
    payload: { targetWagonId: string },
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_WAGON_LOAD(scheduleId, wagonId),
      payload,
    );
    return unwrap(response.data);
  },

  getUnassignedBookings: async (
    scheduleId: string,
  ): Promise<UnassignedBookingsResponse> => {
    const response = await client.get<UnassignedBookingsResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGNED_BOOKINGS(scheduleId),
    );
    return unwrap(response.data);
  },

  getCompositionRemovals: async (
    scheduleId: string,
  ): Promise<CompositionRemovalEntry[]> => {
    const response = await client.get<CompositionRemovalEntry[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.COMPOSITION_REMOVALS(scheduleId),
    );
    return unwrap(response.data);
  },
};
