import type { RateAppliesTo, RateTrigger, RateUnit } from './rate.entity';

/** How the bulk commodity a rate is scoped to is counted (cargo_types.unit_of_measure). */
export type CargoUom = 'PER_TON' | 'PER_ITEM' | null | undefined;

/**
 * Units billed against a booking's bulk quantity. That quantity is recorded in
 * the commodity's own unit — tonnes for a PER_TON commodity, item count for a
 * PER_ITEM one — so both units scale off the same field and only differ in what
 * they are called.
 */
export const isBulkQuantityUnit = (unit: string): boolean =>
  unit === 'PER_TON' || unit === 'PER_ITEM';

/**
 * Which rate units make sense for a given rate shape. The weighting basis is
 * driven by the *type* of thing being billed — a container leg bills per
 * container, bulk freight per ton, an intercity move can be per-km, a
 * cancellation is a flat/per-invoice fee, and overweight is always per excess
 * ton. This keeps the rate table dynamic yet non-conflicting: the admin can
 * only pick a unit the pricing engine knows how to apply.
 *
 * A rate scoped to a break-bulk commodity (unit_of_measure = PER_ITEM) offers
 * PER_ITEM wherever a weighed commodity offers PER_TON — machinery is priced
 * per unit shipped, wheat per tonne. Per-wagon is offered either way.
 *
 * Returned lists are ordered with the most natural/default unit first.
 */
export function allowedRateUnits(input: {
  appliesTo: RateAppliesTo;
  trigger: RateTrigger;
  /** CUSTOMS_CLEARANCE only: which cargo kind the fee covers. */
  cargoKind?: 'CONTAINER' | 'BULK' | null;
  /** Unit of measure of the bulk commodity the rate is scoped to, when any. */
  cargoUnitOfMeasure?: CargoUom;
}): RateUnit[] {
  const units = unitsForShape(input);
  return input.cargoUnitOfMeasure === 'PER_ITEM'
    ? units.map((u) => (u === 'PER_TON' ? 'PER_ITEM' : u))
    : units;
}

function unitsForShape(input: {
  appliesTo: RateAppliesTo;
  trigger: RateTrigger;
  cargoKind?: 'CONTAINER' | 'BULK' | null;
}): RateUnit[] {
  const { appliesTo, trigger } = input;

  // Surcharges (Applies to = Other) are governed by their trigger.
  if (appliesTo === 'OTHER') {
    switch (trigger) {
      case 'OVERWEIGHT':
        // Overweight always bills the excess tonnage — per ton, nothing else.
        return ['PER_TON'];
      case 'REEFER':
      case 'HAZARDOUS':
        // Scale with the freight shape: per container for boxes, per ton for bulk.
        return ['PER_CONTAINER', 'PER_TON'];
      case 'DEMURRAGE':
        return ['PER_CONTAINER', 'PER_TON'];
      case 'WITH_RETURN':
        // Container-only empty-return service — per returned container, per
        // wagon the empties ride back on, or a flat fee.
        return ['PER_CONTAINER', 'PER_WAGON', 'FLAT'];
      case 'CANCELLATION':
        return ['FLAT', 'PER_INVOICE'];
      case 'CUSTOMS_CLEARANCE':
        // Sold per cargo kind: container fees bill per box or per wagon, bulk
        // fees per ton or per wagon. Billed on the booking invoice.
        return input.cargoKind === 'BULK'
          ? ['PER_TON', 'PER_WAGON']
          : ['PER_CONTAINER', 'PER_WAGON'];
      case 'LASHING':
        // Bulk-only cargo securing — per ton or per wagon.
        return ['PER_TON', 'PER_WAGON'];
      case 'CONSOLIDATION':
        return ['PER_CONTAINER', 'FLAT'];
      case 'SHIPPING_LINE':
      case 'PIL_EXTRA_FEE':
        return ['PER_CONTAINER', 'FLAT'];
      default:
        return ['FLAT', 'PER_TON', 'PER_CONTAINER'];
    }
  }

  // Base freight + first/last mile scale with the cargo type.
  switch (appliesTo) {
    case 'CONTAINER':
      return ['PER_CONTAINER', 'PER_WAGON'];
    case 'BULK':
      return ['PER_TON', 'PER_WAGON'];
    case 'INTERCITY':
      return ['PER_CONTAINER', 'PER_TON', 'PER_WAGON', 'PER_KM'];
    case 'FIRST_MILE':
    case 'LAST_MILE':
      return ['PER_CONTAINER', 'PER_TON', 'PER_KM', 'FLAT'];
    default:
      return ['FLAT'];
  }
}

/** The default (first / most natural) unit for a rate shape. */
export function defaultRateUnit(input: { appliesTo: RateAppliesTo; trigger: RateTrigger }): RateUnit {
  return allowedRateUnits(input)[0];
}

/** True when `unit` is a valid weighting basis for the given rate shape. */
export function isRateUnitAllowed(input: {
  appliesTo: RateAppliesTo;
  trigger: RateTrigger;
  cargoKind?: 'CONTAINER' | 'BULK' | null;
  cargoUnitOfMeasure?: CargoUom;
  unit: RateUnit;
}): boolean {
  return allowedRateUnits(input).includes(input.unit);
}
