import type { Freight } from "@edr/types";

/**
 * Booking statuses that no longer occupy the single active-booking slot of a
 * ONE_TIME contract. Mirrors the API source of truth in
 * `contract-booking.service.ts`.
 */
export const TERMINAL_BOOKING_STATUSES = [
  "EXPIRED",
  "CANCELLED",
  "COMPLETED",
  "REJECTED",
];

/** Path A statuses where a customer (no customs) may book against the contract. */
const PATH_A_BOOKABLE = ["FULLY_EXECUTED", "CONTRACT_ACTIVE"];

/** Split bookings in these statuses released their quantity — no remainder to book. */
const RELEASING_BOOKING_STATUSES = ["CANCELLED", "REJECTED", "EXPIRED"];

export type ContractBookingActionKind =
  | "book"
  | "rebook"
  | "request"
  | "initiate"
  | "none";

export interface ContractBookingAction {
  kind: ContractBookingActionKind;
  to: string;
  /**
   * ONE_TIME contract whose booking was split (partial batch payment): the
   * customer may book again, but ONLY the whole outstanding remainder — the
   * exact quantities come from the contract capacity endpoint.
   */
  remainderOnly?: boolean;
}

/**
 * The single source of truth for whether a contract row should show a
 * Book / Re-book shipment button, and where it should navigate.
 *
 * - ONE_TIME: book only while there is no active booking. If the previous
 *   booking expired, offer Re-book. Once a live booking exists, no button.
 * - GENERAL: bookable while CONTRACT_ACTIVE (CONTRACT_CLOSED / EXPIRED are
 *   already excluded by the PATH_A_BOOKABLE gate).
 */
export function getContractBookingAction(
  contract: Freight.IContract,
  bookings: Freight.IBooking[],
): ContractBookingAction {
  // GENERAL + customs: customer submits a shipment request; GL creates the booking.
  if (
    contract.customsClearingEnabled &&
    contract.contractKind === "GENERAL" &&
    contract.status === "CONTRACT_ACTIVE"
  ) {
    return {
      kind: "request",
      to: `/contracts/${contract.id}/shipment-requests/new`,
    };
  }

  // Other customs (ONE_TIME): booked by GL — no customer action.
  if (contract.customsClearingEnabled) return { kind: "none", to: "" };
  if (!PATH_A_BOOKABLE.includes(contract.status)) return { kind: "none", to: "" };

  const to = `/contracts/${contract.id}/bookings/new`;

  if (contract.contractKind === "ONE_TIME") {
    const mine = bookings.filter((b) => b.contractId === contract.id);
    // Split chain: a paid partial split frees the single-booking slot for the
    // remainder. The API enforces that the next booking takes the whole
    // leftover; once it is booked the contract closes (CONTRACT_CLOSED), which
    // the PATH_A_BOOKABLE gate above already excludes.
    const hasLiveSplit = mine.some(
      (b) => b.isSplit && !RELEASING_BOOKING_STATUSES.includes(b.status),
    );
    if (hasLiveSplit) return { kind: "book", to, remainderOnly: true };
    const hasActive = mine.some(
      (b) => !TERMINAL_BOOKING_STATUSES.includes(b.status),
    );
    if (hasActive) return { kind: "none", to: "" };
    const hasExpired = mine.some((b) => b.status === "EXPIRED");
    return { kind: hasExpired ? "rebook" : "book", to };
  }

  // GENERAL non-customs import/export: one-click bare booking instance — the
  // per-booking clearance runs first, cargo + shipment day come at completion.
  // No window gate here; the window is checked when the booking is completed.
  // DOMESTIC (intercity) keeps the direct booking form.
  if (contract.tradeDirection !== "DOMESTIC") {
    return { kind: "initiate", to: `/contracts/${contract.id}` };
  }

  return { kind: "book", to };
}
