import type { BaseEntity } from "../common";
import type { IYard } from "./index";

// ── Contract classification ─────────────────────────────────────────────────

/**
 * A contract is the legal/commercial agreement (scope + unit rates, no
 * quantities). ONE_TIME allows a single active shipment booking at a time;
 * GENERAL allows many shipment cycles over the validity window.
 */
export enum ContractKind {
  OneTime = "ONE_TIME",
  General = "GENERAL",
}

export type ContractTradeDirection = "IMPORT" | "EXPORT" | "DOMESTIC";

export enum ContractFreightType {
  Container = "CONTAINER",
  Bulk = "BULK",
}

/**
 * The nine UN/ADR dangerous-goods classes. A hazardous contract must declare
 * exactly one class plus the shipment's UN number — both are captured with the
 * hazard documents in the portal and reviewed by the two hazardous approvers in
 * the backoffice.
 */
export const HAZARD_CLASSES = [
  { value: "CLASS_1", label: "Class 1: Explosives" },
  { value: "CLASS_2", label: "Class 2: Gases" },
  { value: "CLASS_3", label: "Class 3: Flammable Liquids" },
  { value: "CLASS_4", label: "Class 4: Flammable Solids" },
  { value: "CLASS_5", label: "Class 5: Oxidizers and Organic Peroxides" },
  { value: "CLASS_6", label: "Class 6: Toxic and Infectious Substances" },
  { value: "CLASS_7", label: "Class 7: Radioactive Materials" },
  { value: "CLASS_8", label: "Class 8: Corrosive Substances" },
  { value: "CLASS_9", label: "Class 9: Miscellaneous Dangerous Goods" },
] as const;

export type HazardClass = (typeof HAZARD_CLASSES)[number]["value"];

export const HAZARD_CLASS_VALUES: readonly string[] = HAZARD_CLASSES.map(
  (c) => c.value,
);

/** Human label for a stored hazard class code; falls back to the raw code. */
export function hazardClassLabel(
  value: string | null | undefined,
): string | null {
  if (!value) return null;
  return HAZARD_CLASSES.find((c) => c.value === value)?.label ?? value;
}

/** Who created a shipment booking under a contract. */
export type BookingCreatedByRole = "CUSTOMER" | "GL_ET" | "STAFF";

/** GL region that owns a clearance step / milestone. */
export type OwnerRegion = "ET" | "DJ" | "OPS" | "CUST";

// ── Contract status machine (doc Appendix A) ────────────────────────────────

export const CONTRACT_STATUSES = [
  "DRAFT",
  "SUBMITTED",
  "PRICE_CHANGED_PENDING_CONFIRM",
  "CHANGES_REQUESTED",
  "PENDING_APPROVAL",
  "APPROVED",
  "APPROVED_PENDING_SIGNATURE",
  "CONTRACT_READY",
  "SIGNED_CUSTOMER",
  // transport-only execution (Path A)
  "FULLY_EXECUTED", // ONE_TIME
  "CONTRACT_ACTIVE", // GENERAL
  // customs clearance execution (Path B, pre-booking)
  "AWAITING_CLEARANCE_DOCUMENTS",
  "CLEARANCE_UNDER_REVIEW",
  "CLEARANCE_READY_FOR_BOOKING",
  "ACTIVE_SHIPMENT_IN_PROGRESS",
  // terminal
  "CONTRACT_CLOSED",
  "EXPIRED",
  "REJECTED",
  "CANCELLED",
  // renewal branch
  "RENEWAL_DRAFT",
  "RENEWAL_SUBMITTED",
  "RENEWAL_PENDING_APPROVAL",
  "AMENDMENTS_PROPOSED",
  "ARCHIVED",
] as const;

export type ContractStatus = (typeof CONTRACT_STATUSES)[number];

/**
 * Pre-booking clearance gate carried on the contract (Path B only). Separate
 * from the contract status so transport-only contracts stay NOT_APPLICABLE.
 */
export const CONTRACT_CLEARANCE_STATUSES = [
  "NOT_APPLICABLE",
  "AWAITING_DOCUMENTS",
  "DOCUMENTS_UNDER_REVIEW",
  "CLEARANCE_READY_FOR_BOOKING", // Path B — GL may create the booking
  "SELF_CLEARED", // Path A — Operations approved; customer may book
  "ACTIVE_SHIPMENT_IN_PROGRESS",
] as const;

export type ContractClearanceStatus =
  (typeof CONTRACT_CLEARANCE_STATUSES)[number];

/** Statuses where the customer may edit contract fields. */
export const CONTRACT_CUSTOMER_EDITABLE_STATUSES: ContractStatus[] = [
  "DRAFT",
  "CHANGES_REQUESTED",
];

// ── Pricing (unit-rate display at contract phase, doc §9.1) ─────────────────

export type ContractRateUnit =
  | "per_container"
  | "per_wagon"
  | "per_ton"
  | "per_item"
  | "per_km"
  | "flat";

export interface ContractUnitRateLineItem {
  code: string;
  label: string;
  unit: ContractRateUnit;
  unitPrice: number;
  /** "20ft" | "40ft" when the rate is container-size specific. */
  containerSize?: string | null;
  /** "is_hazardous" | "is_reefer" when this is a conditional surcharge. */
  conditionalOn?: string | null;
  cargoTypeCode?: string | null;
  /**
   * True for the customs clearance service fee — billed separately in advance
   * (before document upload), never part of shipment booking totals.
   */
  isClearance?: boolean;
}

/** Contract `pricing_breakdown` shape — unit rates, no totals. */
export interface ContractPricingBreakdown {
  displayMode: "UNIT_RATES";
  currency: string;
  lineItems: ContractUnitRateLineItem[];
  generatedAt: string;
}

// ── Contract child collections ──────────────────────────────────────────────

export interface IContractRoute {
  id: string;
  contractId: string;
  originYardId: string;
  originYard?: IYard | null;
  destinationYardId: string;
  destinationYard?: IYard | null;
  /** Road billing distance; null for rail-only. */
  km?: number | null;
  sortOrder: number;
}

export interface IContractCargoScope {
  id: string;
  contractId: string;
  /** "20ft" | "40ft"; null for bulk. */
  containerSize?: string | null;
  cargoTypeId?: string | null;
  /** Bulk cargo type detail (name + unit), loaded on the contract detail. */
  cargoType?: {
    id: string;
    code?: string | null;
    cargoTypeName?: string | null;
    unitOfMeasure?: string | null;
  } | null;
  cargoFreeText?: string | null;
  /**
   * GENERAL contracts: total quantity bookable across all shipments on this line
   * (containers per size, or tons/items for bulk). null = uncapped / ONE_TIME.
   */
  quantityCap?: number | null;
}

/** Remaining bookable quantity per cargo-scope line (GENERAL contracts). */
export interface ContractCapacityLine {
  containerSize?: string | null;
  cargoTypeId?: string | null;
  cap: number | null;
  booked: number;
  remaining: number | null;
}

export interface IContractRateSnapshot {
  id: string;
  contractId: string;
  rateId?: string | null;
  rateCode: string;
  description?: string | null;
  unitPrice: number;
  unitOfMeasure: ContractRateUnit;
  currency: string;
  containerSize?: string | null;
  isSurcharge: boolean;
  conditionalOn?: string | null;
}

export type ContractSignatureRole = "CUSTOMER" | "STAFF" | "DIRECTOR" | "CEO";

export interface IContractSignature {
  id: string;
  contractId: string;
  role: ContractSignatureRole;
  signerDisplayName: string;
  signatureFileId?: string | null;
  /** Company stamp/seal image, required for the CUSTOMER and STAFF parties. */
  stampFileId?: string | null;
  consentText?: string | null;
  signedAt: string;
}

// ── Per-contract document snapshot (staff-editable articles for one contract) ─

export interface IContractDocumentArticle {
  id: string;
  title: string;
  body: string;
  order: number;
}

/**
 * A per-contract frozen copy of the resolved document template. Staff may edit
 * its articles for a single contract in the accept/edit dialog — this never
 * writes back to the shared six contract templates. Null → the PDF renders from
 * the live template.
 */
export interface IContractDocumentSnapshot {
  code?: string | null;
  name?: string | null;
  documentTitle?: string | null;
  whereasClauses: string[];
  articles: IContractDocumentArticle[];
}

/** Editable document draft returned for the accept/edit editor. */
export interface IContractDocumentDraft {
  documentTitle: string | null;
  whereasClauses: string[];
  articles: IContractDocumentArticle[];
  code: string | null;
  name: string | null;
  /** True when THIS caller may not edit — the inverse of `editableByMe`. */
  locked: boolean;
  /**
   * Whether the requesting user is the approver whose turn it is. The document
   * stays editable through the whole approval chain, but only by the approver
   * currently holding it, so editability is caller-dependent.
   */
  editableByMe: boolean;
  /** Role holding editing rights right now, for "locked because…" messaging. */
  nextApproverRole: string | null;
  generatedAt: string | null;
  status: ContractStatus;
}

/** One recorded change within a contract-document revision. */
export type IContractDocumentChange =
  | { kind: 'ARTICLE_ADDED'; articleId: string; title: string }
  | { kind: 'ARTICLE_REMOVED'; articleId: string; title: string }
  | {
      kind: 'ARTICLE_RENAMED';
      articleId: string;
      title: string;
      fromTitle: string;
    }
  | {
      kind: 'ARTICLE_BODY_CHANGED';
      articleId: string;
      title: string;
      /** Body before / after the edit. Absent on revisions recorded earlier. */
      fromBody?: string;
      toBody?: string;
    }
  | {
      kind: 'ARTICLE_REORDERED';
      articleId: string;
      title: string;
      fromOrder: number;
      toOrder: number;
    }
  | { kind: 'DOCUMENT_TITLE_CHANGED'; title: string; fromTitle: string | null }
  | { kind: 'WHEREAS_CHANGED'; added: number; removed: number }
  /** A contract field (not a document article) changed on a customer edit. */
  | {
      kind: 'FIELD_CHANGED';
      field: string;
      label: string;
      from: string | null;
      to: string | null;
    };

/** An audit entry for one edit to a contract's document. */
export interface IContractDocumentRevision {
  id: string;
  contractId: string;
  actorId: string | null;
  /** Who made the edit, captured at the time (survives renames/deletions). */
  actorName?: string | null;
  /** The approval step's required role at the time of the edit. */
  actorRole: string | null;
  stepId: string | null;
  summary: string | null;
  changes: IContractDocumentChange[];
  createdAt: string;
}

export type ContractApprovalStepStatus =
  | "PENDING"
  | "APPROVED"
  | "REJECTED"
  | "SKIPPED";

export interface IContractApprovalStep {
  id: string;
  contractId: string;
  stepOrder: number;
  requiredRole: string;
  blocksRole?: string | null;
  status: ContractApprovalStepStatus;
  actedByStaffId?: string | null;
  actedAt?: string | null;
  note?: string | null;
}

// ── Pre-booking clearance (Path B, doc §5.16) ───────────────────────────────

export type ContractDocReviewStatus = "PENDING" | "APPROVED" | "QUERIED";

export interface IContractDocumentReview {
  id: string;
  contractId: string;
  clearanceCycleId?: string | null;
  settingCode: string;
  fileKey: string;
  fileRecordId?: string | null;
  status: ContractDocReviewStatus;
  note?: string | null;
  uploadedByRole: "CUSTOMER" | "GL_ET" | "GL_DJ";
  reviewedByStaffId?: string | null;
  reviewedAt?: string | null;
}

export interface IContractClearanceCycle {
  id: string;
  contractId: string;
  cycleNumber: number;
  status: string;
  bookingId?: string | null;
  startedAt: string;
  clearanceReadyAt?: string | null;
  completedAt?: string | null;
}

/**
 * The pre-booking clearance view for a contract (Path B). Mirrors
 * {@link ClearanceView} for bookings but keyed on the contract.
 */
export interface ContractClearanceDocument {
  fileKey: string;
  label: string;
  required: boolean;
  uploadedBy: "customer" | "gl" | "gl_et" | "gl_dj";
  phase: ContractDocPhase;
  settingCode: string;
  file: { id: string; name: string; url: string } | null;
  reviewStatus: ContractDocReviewStatus | null;
  note: string | null;
  /** When the review decision (approve/query) was recorded. */
  reviewedAt?: string | null;
  /** Staff id that recorded the decision (no user directory to resolve names). */
  reviewedByStaffId?: string | null;
}

/**
 * Post-allocation T1 transit document state for the booking linked to an import
 * customs flow. GL Djibouti uploads after wagon allocation; uploads lock once the
 * train departs; GL Ethiopia closes (accepts) T1 when the train arrives.
 */
export interface ClearanceT1State {
  bookingId: string;
  wagonAllocated: boolean;
  trainDepartedAt: string | null;
  trainArrivedAt: string | null;
  closed: boolean;
  closedAt?: string | null;
}

/** Train link state for the booking tied to a customs clearance flow. */
export interface ClearanceTrainState {
  scheduleId: string | null;
  wagonAllocated: boolean;
  departedAt: string | null;
  arrivedAt: string | null;
}

/** Billing invoice `type` for the GL Djibouti post-offload final invoice. */
export const GL_FINAL_INVOICE_TYPE = "GL_FINAL";

/**
 * Post-offload final invoice raised by GL Djibouti: customer pays offline and
 * attaches a slip; GL (ET or DJ) confirms to mark it paid.
 */
export interface ClearanceFinalInvoiceSummary {
  id: string;
  invoiceNumber: string;
  status: string;
  totalAmount: number;
  currency: string;
  description?: string | null;
  invoiceFile: { id: string; name: string; url: string } | null;
  slipFile: { id: string; name: string; url: string } | null;
  confirmedAt: string | null;
}

/**
 * Post-arrival second duty/tax round (import): GL ET advises an additional
 * amount with a notice; the customer attaches another payment slip. Optional —
 * GL ET may mark it skipped when no further duty applies.
 */
export interface ClearanceSecondDuty {
  advised: boolean;
  skipped: boolean;
  amount: number | null;
  currency: string | null;
  declarationSerial?: string | null;
  noticeFile: { id: string; name: string; url: string } | null;
  slipFile: { id: string; name: string; url: string } | null;
  paid: boolean;
}

export interface ContractClearanceView {
  contractId: string;
  /** Overall contract status (e.g. CLEARANCE_UNDER_REVIEW). */
  status?: string;
  clearanceStatus: ContractClearanceStatus;
  cycleNumber: number;
  /**
   * True when the contract bundles EDR customs clearance (Path B, GL-reviewed).
   * False for Path A self-clearance, reviewed by the Operations team.
   */
  includesCustoms?: boolean;
  /** Resolved customer-input / GL-output clearance setting codes. */
  inputCode?: string | null;
  outputCode?: string | null;
  documents: ContractClearanceDocument[];
  /** True once every required customer document is APPROVED. */
  allApproved: boolean;
  /** Current phased clearance step (ONE_TIME customs). */
  phase?: ContractDocPhase | null;
  /** Pre-booking milestone rows for this contract cycle. */
  milestones?: IClearanceMilestone[];
  /** What should happen next in the workflow. */
  nextAction?: ClearanceNextAction | null;
  dutyRequired?: boolean | null;
  roHold?: boolean;
  roHoldReason?: string | null;
  vesselDepartureDate?: string | null;
  /** Import DO: vessel arrival + DO collection dates, recorded by GL Djibouti. */
  vesselArrivalDate?: string | null;
  doCollectedDate?: string | null;
  roAmendmentRequestedAt?: string | null;
  bookingReady?: boolean;
  preClearanceFinalized?: boolean;
  /** Export post-booking clearance finalized after transit permit upload. */
  exportClearanceFinalized?: boolean;
  linkedBookingId?: string | null;
  /** Reference + status of the GL-created shipment booking, once it exists. */
  linkedBookingReference?: string | null;
  linkedBookingStatus?: string | null;
  /** Operations' latest "needs changes" note on that booking — GL acts on it. */
  linkedBookingReviewNote?: string | null;
  /** Shipment day the booking holds; the default when GL resubmits it. */
  linkedBookingScheduledDate?: string | null;
  /**
   * Pre-declaration handshake with GL Djibouti: who handles the shipment in
   * transit. `name` stays null until Djibouti answers, and GL Ethiopia cannot
   * file the customs declaration before it is set.
   */
  transitAssignee?: {
    requestedAt: string | null;
    requestNote: string | null;
    name: string | null;
    assignedAt: string | null;
  } | null;
  dutyAdvice?: {
    amount: number;
    currency: string;
    declarationSerial?: string | null;
    noticeFile?: { id: string; name: string; url: string } | null;
  } | null;
  /**
   * The customer's open objection to the advised duty. Present only until GL
   * re-advises; `rounds` counts how many times it has been sent back.
   */
  dutyDispute?: {
    note: string;
    raisedAt: string;
    rounds: number;
  } | null;
  /** Phased customs uploads (IM4, DO, transit permit, etc.) with friendly labels. */
  workflowFiles?: import("./clearance-files.catalog").ClearanceWorkflowFile[];
  /** Import post-allocation T1 transit document state (null until a booking is linked). */
  t1?: ClearanceT1State | null;
  /** Train link state for the booking (both directions; null until a booking is linked). */
  train?: ClearanceTrainState | null;
  gatepassGranted?: boolean;
  gatepassAt?: string | null;
  t1Closed?: boolean;
  t1ClosedAt?: string | null;
  offloaded?: boolean;
  /** GL Djibouti post-offload final invoice (export). */
  finalInvoice?: ClearanceFinalInvoiceSummary | null;
  /** Customs risk level assigned by GL ET (import; visible to the customer). */
  riskLevel?: string | null;
  riskAssignedAt?: string | null;
  /** Every risk decision, oldest first; the last entry is the current level. */
  riskHistory?: RiskAssignmentRecord[];
  /** Post-arrival additional duty/tax round (import). */
  secondDuty?: ClearanceSecondDuty | null;
  importReleaseGranted?: boolean;
}

export type ClearanceActorRole = "CUSTOMER" | "GL_ET" | "GL_DJ" | "OPERATIONS";

export interface ClearanceNextAction {
  actor: ClearanceActorRole;
  action: string;
  milestoneCode?: string | null;
  blockedReason?: string | null;
}

/** Phased clearance document upload slots (doc §5.13). */
export enum ContractDocPhase {
  CustomerIntake = "CUSTOMER_INTAKE",
  GlEtReview = "GL_ET_REVIEW",
  GlDjCollection = "GL_DJ_COLLECTION",
  GlEtOutput = "GL_ET_OUTPUT",
  CustomerDuty = "CUSTOMER_DUTY",
  GlEtPostClearance = "GL_ET_POST_CLEARANCE",
  GlDjLoading = "GL_DJ_LOADING",
  PostTransit = "POST_TRANSIT",
}

// ── Clearance milestones (doc §5.12, Appendix B/C) ──────────────────────────

export type MilestoneStatus = "PENDING" | "COMPLETED" | "SKIPPED";

export const CUSTOMS_RISK_LEVELS = ["GREEN", "YELLOW", "RED"] as const;
export type CustomsRiskLevel = (typeof CUSTOMS_RISK_LEVELS)[number];

/**
 * One customs risk decision. The level stays correctable until duty is advised
 * off it and is customer-visible, so every assignment is kept rather than
 * overwritten.
 */
export interface RiskAssignmentRecord {
  level: CustomsRiskLevel;
  /** The level this replaced; absent on the first assignment. */
  previousLevel?: CustomsRiskLevel;
  assignedAt: string;
  assignedByUserId?: string | null;
  /** Display name resolved at assignment time, so the trail never shows a UUID. */
  assignedBy?: string | null;
  note?: string | null;
}

/** Structured payload carried by RISK_ASSIGNED / DUTY_TAXES_ADVISED milestones. */
export interface MilestoneMetadata {
  riskLevel?: CustomsRiskLevel;
  /** Every risk decision, oldest first; the last entry matches `riskLevel`. */
  riskHistory?: RiskAssignmentRecord[];
  dutyAmount?: number;
  dutyCurrency?: string;
  declarationSerial?: string;
}

export interface IClearanceMilestone {
  id: string;
  bookingId?: string | null;
  contractId?: string | null;
  clearanceCycleId?: string | null;
  milestoneCode: string;
  milestoneLabel: string;
  status: MilestoneStatus;
  ownerRegion?: OwnerRegion | null;
  triggeredByDoc: boolean;
  triggeredAt?: string | null;
  triggeredByUserId?: string | null;
  note?: string | null;
  metadata?: MilestoneMetadata | null;
  sortOrder: number;
}

// ── GL cargo exception / damage reports (doc §11 GL Import US-07) ────────────

export const INCIDENT_TYPES = [
  "SEAL_BROKEN",
  "CONTAINER_OPENED",
  "CONTAINER_DAMAGED",
  "FLUID_LEAKING",
  "OTHER",
] as const;
export type IncidentType = (typeof INCIDENT_TYPES)[number];

export interface IClearanceIncident {
  id: string;
  bookingId: string;
  incidentType: IncidentType;
  description: string;
  photoFileIds: string[];
  reportedByUserId?: string | null;
  reportedAt: string;
  createdAt: string;
}

export const IMPORT_MILESTONES = [
  "IMPORT_DOCS_UPLOADED",
  "PENDING_DOCUMENT_REVIEW",
  "DOCUMENTS_APPROVED",
  "UNDER_CUSTOMS_CLEARANCE",
  "DECLARED",
  "DUTY_TAXES_ADVISED",
  "DUTY_TAX_PAID",
  "TRANSIT_PERMIT_UPLOADED",
  "DO_COLLECTED",
  "WAGON_REQUESTED",
  "FREIGHT_PAYMENT_SETTLED",
  "WAGON_ALLOCATED",
  "GATEPASS_GRANTED",
  "READY_FOR_LOADING",
  "LOADED",
  "DEPARTED_FROM_DJIBOUTI",
  "ARRIVED_ETHIOPIA",
  "OFFLOADED",
  "T1_CLOSED",
  "RISK_ASSIGNED",
  "SECOND_DUTY_ADVISED",
  "SECOND_DUTY_PAID",
  "IMPORT_RELEASE_GRANTED",
  "IMPORT_PROCESS_COMPLETED",
  "STORAGE_INVOICE_RAISED",
  "EXIT_NOTE_GENERATED",
] as const;

export type ImportMilestoneCode = (typeof IMPORT_MILESTONES)[number];

export const EXPORT_MILESTONES = [
  "EXPORT_DOCS_UPLOADED",
  "PENDING_DOCUMENT_REVIEW",
  "DOCUMENTS_APPROVED",
  "RELEASE_ORDER_SECURED",
  "UNDER_CUSTOMS_CLEARANCE",
  "DECLARED",
  "EXPORT_RELEASED",
  "WAGON_REQUESTED",
  "FREIGHT_PAYMENT_PENDING",
  "FREIGHT_PAYMENT_SETTLED",
  "WAGON_ALLOCATED",
  "EXPORT_TRANSPORT_ISSUED",
  "CARGO_ARRIVED",
  "READY_FOR_LOADING",
  "LOADED",
  "DEPARTED_TO_DJIBOUTI",
  "ARRIVED_AT_DJIBOUTI",
  "GATEPASS_GRANTED",
  "T1_CLOSED",
  "OFFLOADED",
] as const;

export type ExportMilestoneCode = (typeof EXPORT_MILESTONES)[number];

// ── Booking container units (per-unit detail at booking, doc §5.10) ─────────

export interface IBookingContainerUnit {
  id: string;
  bookingContainerId: string;
  containerNumber: string;
  sealNumber?: string | null;
  vgmTons: number;
  isHazardous: boolean;
  isReefer: boolean;
  sortOrder: number;
}

// ── Contract aggregate ──────────────────────────────────────────────────────

export interface IContract extends BaseEntity {
  reference: string;

  companyId?: string | null;
  companyProfileId?: string | null;
  /** Loaded company relation (list + detail responses include it). */
  company?: BookingRequestCompany | null;
  isGovernment: boolean;
  governmentInstitution?: string | null;

  contractKind: ContractKind;
  renewalOfId?: string | null;
  tradeDirection: ContractTradeDirection;
  freightType: ContractFreightType;

  serviceTypeId: string;
  /** Loaded service-type relation (queue + detail responses include it). */
  serviceType?: {
    id: string;
    code: string;
    serviceName: string;
    description?: string | null;
    canBeBookedAlone: boolean;
    includesFirstMile?: boolean;
    includesLastMile?: boolean;
    includesCustoms: boolean;
  } | null;
  paymentCurrency: string;
  customsClearingEnabled: boolean;
  customsClearingAgent?: string | null;
  equipmentReturn?: string | null;

  firstMilePickupAddress?: string | null;
  firstMilePickupLat?: number | null;
  firstMilePickupLng?: number | null;
  lastMileDeliveryAddress?: string | null;
  lastMileDeliveryLat?: number | null;
  lastMileDeliveryLng?: number | null;

  isHazardous: boolean;
  /** UN/ADR dangerous-goods class — set only when `isHazardous`. */
  hazardClass?: HazardClass | string | null;
  /** UN number of the dangerous good — set only when `isHazardous`. */
  unNumber?: string | null;
  isReefer: boolean;

  estimatedShipmentDate?: string | null;
  contractValidityDays?: number | null;
  contractValidFrom?: string | null;
  contractValidUntil?: string | null;
  expiresAt?: string | null;

  status: ContractStatus;
  /**
   * Body of the latest staff CHANGES_REQUESTED review note (detail response
   * only, when status is CHANGES_REQUESTED). Lets the portal show the customer
   * exactly what to fix before resubmitting.
   */
  latestChangeRequestNote?: string | null;
  /**
   * Body of the latest REJECTION review note (detail response only, when
   * status is REJECTED). Shows staff and customer why the contract was
   * rejected.
   */
  latestRejectionNote?: string | null;
  /**
   * Body of the latest send-back STAFF_NOTE (detail response only, while the
   * contract is PENDING_APPROVAL and no step has acted since the send-back).
   * Tells the returned-to approver why the chain came back to them.
   */
  latestSendBackNote?: string | null;
  clearanceStatus: ContractClearanceStatus;
  clearanceCycleNumber: number;
  /**
   * Latest clearance cycle's current phase (list responses only). Lets list
   * consumers show step-accurate customer actions without fetching the full
   * clearance view per contract.
   */
  clearancePhase?: ContractDocPhase | string | null;
  /**
   * Latest clearance cycle's linked booking id + status (list responses only).
   * An EXPIRED status means the customer never paid — GL may rebook.
   */
  latestCycleBookingId?: string | null;
  latestCycleBookingStatus?: string | null;

  pricingBreakdown?: ContractPricingBreakdown | null;
  pricingDisplayMode?: "UNIT_RATES";

  contractType?: string | null;
  contractTemplateKey?: string | null;
  contractGeneratedAt?: string | null;
  /** Per-contract frozen document (articles + WHEREAS) captured at staff accept. */
  documentSnapshot?: IContractDocumentSnapshot | null;
  contractSummary?: string | null;
  versionNumber: number;
  financialTerms?: string | null;

  approvedByStaffId?: string | null;
  approvedByStaffAt?: string | null;
  signedByDirectorId?: string | null;
  signedByDirectorAt?: string | null;
  signedByCeoId?: string | null;
  signedByCeoAt?: string | null;
  customerSignedAt?: string | null;
  fullyExecutedAt?: string | null;
  lockedAt?: string | null;

  routes?: IContractRoute[];
  cargoScope?: IContractCargoScope[];
  rateSnapshots?: IContractRateSnapshot[];
  signatures?: IContractSignature[];
  approvalSteps?: IContractApprovalStep[];
  clearanceCycles?: IContractClearanceCycle[];

  files?: Array<{
    id: string;
    code: string;
    name: string;
    url: string;
    mimeType: string;
    size: number;
    resourceId: string;
    resource: string;
    signedUrl?: string | null;
  }>;
}

// ── DTOs ─────────────────────────────────────────────────────────────────────

export interface CreateContractCargoScopeDto {
  /** "20ft" | "40ft"; omit for bulk. */
  containerSize?: string | null;
  cargoTypeId?: string | null;
  cargoFreeText?: string | null;
  /** GENERAL only: total bookable quantity for this line. Omit for uncapped. */
  quantityCap?: number | null;
}

export interface CreateContractRouteInputDto {
  originYardId: string;
  destinationYardId: string;
  km?: number;
  sortOrder?: number;
}

export interface CreateContractDto {
  reference?: string;
  isGovernment?: boolean;
  governmentInstitution?: string;
  companyId?: string;

  contractKind: ContractKind;
  renewalOfId?: string;
  tradeDirection: ContractTradeDirection;
  freightType: ContractFreightType;

  serviceTypeId: string;
  /**
   * Ignored on create — a contract always quotes in USD now. The billing
   * currency is chosen per shipment (at booking, or on the shipment request
   * when GL books on the customer's behalf).
   *
   * @deprecated
   */
  paymentCurrency?: string;
  customsClearingEnabled?: boolean;
  customsClearingAgent?: string;
  equipmentReturn?: string;

  firstMilePickupAddress?: string;
  firstMilePickupLat?: number;
  firstMilePickupLng?: number;
  lastMileDeliveryAddress?: string;
  lastMileDeliveryLat?: number;
  lastMileDeliveryLng?: number;

  isHazardous?: boolean;
  /** Required when `isHazardous` — one of HAZARD_CLASSES. */
  hazardClass?: string;
  /** Required when `isHazardous` — the shipment's UN number. */
  unNumber?: string;
  isReefer?: boolean;

  estimatedShipmentDate?: string;
  contractType?: string;

  cargoScope: CreateContractCargoScopeDto[];
  routes: CreateContractRouteInputDto[];
}

export type UpdateContractDto = Partial<CreateContractDto>;

// ── Booking-under-contract DTOs (doc §8) ────────────────────────────────────

export interface CreateContainerUnitDto {
  containerNumber: string;
  sealNumber?: string;
  vgmTons: number;
  /** Per-container handling opt-ins, entered alongside this container's VGM. */
  isHazardous?: boolean;
  isReefer?: boolean;
  /** This container ships back empty (equipment return). */
  isReturn?: boolean;
}

export interface CreateBookingContainerLineDto {
  /** "20ft" | "40ft" — must be in the contract's cargo scope. */
  containerSize: string;
  quantity: number;
  /**
   * Line totals, derived from the per-unit switches above. The API recomputes
   * them from `units` whenever any unit carries a flag, so they are only
   * authoritative for callers that don't send per-unit flags.
   */
  hazardousQuantity?: number;
  reeferQuantity?: number;
  /**
   * How many units ship with empty-container return (≤ quantity). Only allowed
   * when the contract was created WITH_RETURN (container freight only).
   */
  returnQuantity?: number;
  units: CreateContainerUnitDto[];
}

export interface CreateBulkLineDto {
  cargoTypeId?: string | null;
  cargoWeightTons?: number;
  itemCount?: number;
  hazardousQuantity?: number;
  reeferQuantity?: number;
}

/** Shipment booking created under a contract (Path A customer, Path B GL ET). */
export interface CreateBookingUnderContractDto {
  /** Required for GENERAL multi-route contracts; ONE_TIME auto-selected. */
  contractRouteId?: string;
  /** Billing currency for this shipment. Intercity is always ETB. */
  paymentCurrency?: string;
  /** Binding shipment day. Omitted for intercity (DOMESTIC) bookings — staff assign a passing train later. */
  scheduledDate?: string;
  /** "WITH_RETURN" | "WITHOUT_RETURN" — per-shipment override; falls back to the contract's equipment return. */
  equipmentReturn?: string;
  containers?: CreateBookingContainerLineDto[];
  bulkLines?: CreateBulkLineDto[];
  /** What the containers carry — captured per booking (container freight). */
  cargoFreeText?: string;
  notes?: string;
}

// ── Shipment / booking requests (GENERAL + customs, Path B) ─────────────────
// On a GENERAL customs contract the customer cannot book directly. They submit a
// shipment request (date + quantities); Global Logistics reviews it, then creates
// the booking on their behalf and per-booking clearance begins.

export const BOOKING_REQUEST_STATUSES = [
  "PENDING",
  "ACCEPTED",
  "REJECTED",
  "CANCELLED",
] as const;
export type BookingRequestStatus = (typeof BOOKING_REQUEST_STATUSES)[number];

/** Requested quantities — container lines OR a single bulk line (no per-unit data). */
export interface RequestedShipmentLines {
  containers?: Array<{
    containerSize: string;
    quantity: number;
    hazardousQuantity?: number;
    reeferQuantity?: number;
  }>;
  bulk?: {
    cargoTypeId?: string | null;
    cargoWeightTons?: number;
    itemCount?: number;
    hazardousQuantity?: number;
  };
}

export interface IBookingRequest extends BaseEntity {
  reference: string;
  contractId: string;
  requestedByUserId?: string | null;
  contractRouteId?: string | null;
  /** Customer's preferred shipment day — informational; GL sets the binding date. */
  scheduledDate?: string | null;
  status: BookingRequestStatus;
  requestedLines: RequestedShipmentLines;
  /** Billing currency the customer chose; GL books the shipment in it. */
  paymentCurrency?: string | null;
  notes?: string | null;
  /** Set when GL accepts and creates the booking. */
  createdBookingId?: string | null;
  reviewedByStaffId?: string | null;
  reviewedAt?: string | null;
  reviewNote?: string | null;
  /** Loaded contract relation (request detail response includes it). */
  contract?: BookingRequestContract | null;
}

/** Customer (company) summary carried on a request's contract. */
export interface BookingRequestCompany {
  id: string;
  name?: string | null;
  tin?: string | null;
  email?: string | null;
  phone?: string | null;
  address?: string | null;
  contactPersonName?: string | null;
  contactPersonPhone?: string | null;
}

/**
 * The slice of the contract surfaced on the shipment-request detail page:
 * identity, service type (mile/customs flags), customer, routes and cargo scope.
 */
export interface BookingRequestContract {
  id: string;
  reference: string;
  contractKind: ContractKind;
  tradeDirection: ContractTradeDirection;
  freightType: ContractFreightType;
  customsClearingEnabled: boolean;
  paymentCurrency: string;
  contractValidUntil?: string | null;
  company?: BookingRequestCompany | null;
  serviceType?: IContract["serviceType"];
  routes?: IContractRoute[];
  cargoScope?: IContractCargoScope[];
}

export interface CreateBookingRequestDto {
  contractRouteId?: string;
  scheduledDate?: string;
  containers?: Array<{
    containerSize: string;
    quantity: number;
    hazardousQuantity?: number;
    reeferQuantity?: number;
  }>;
  bulk?: {
    cargoTypeId?: string | null;
    cargoWeightTons?: number;
    itemCount?: number;
    hazardousQuantity?: number;
  };
  /** Billing currency for the shipment GL will book. Intercity is always ETB. */
  paymentCurrency?: string;
  notes?: string;
}

export interface ReviewBookingRequestDto {
  note?: string;
}

// ── Clearance review / finalize DTOs (Path B) ───────────────────────────────

export interface ReviewContractClearanceDocumentDto {
  fileKey: string;
  status: Extract<ContractDocReviewStatus, "APPROVED" | "QUERIED">;
  note?: string;
}

export interface RenewContractDto {
  /** Reference of the prior contract being renewed. */
  previousContractReference?: string;
}
