export const BELT_TYPES = [
  { id: "transport-belt", baseItemsPerSecond: 15 },
  { id: "fast-transport-belt", baseItemsPerSecond: 30 },
  { id: "express-transport-belt", baseItemsPerSecond: 45 },
  { id: "turbo-transport-belt", baseItemsPerSecond: 60 },
] as const;

export type BeltId = (typeof BELT_TYPES)[number]["id"];
export type BeltStackSize = 1 | 2 | 3 | 4;

export interface BeltLoad {
  capacityPerSecond: number;
  exactBelts: number;
  beltsRequired: number;
  lastBeltUtilization: number;
}

export interface ResolvedBeltLoad extends BeltLoad {
  requestedBeltId: BeltId;
  effectiveBeltId: BeltId;
  requestedCapacityPerSecond: number;
  overloaded: boolean;
  automaticallyUpgraded: boolean;
}

const EPSILON = 1e-12;

/**
 * Belt entity quality does not alter transport speed. Throughput is the
 * prototype's two-lane base rate multiplied by the researched belt stack size.
 */
export function beltCapacityPerSecond(
  beltId: BeltId,
  stackSize: BeltStackSize,
): number {
  const belt =
    BELT_TYPES.find((candidate) => candidate.id === beltId) ?? BELT_TYPES[0];
  return belt.baseItemsPerSecond * stackSize;
}

export function calculateBeltLoad(
  itemsPerSecond: number,
  beltId: BeltId,
  stackSize: BeltStackSize,
): BeltLoad {
  const capacityPerSecond = beltCapacityPerSecond(beltId, stackSize);
  const exactBelts = Math.max(0, itemsPerSecond) / capacityPerSecond;
  const beltsRequired =
    exactBelts <= EPSILON ? 0 : Math.ceil(exactBelts - EPSILON);
  const whole = Math.floor(exactBelts + EPSILON);
  const remainder = exactBelts - whole;
  const lastBeltUtilization =
    beltsRequired === 0
      ? 0
      : Math.abs(remainder) <= EPSILON
        ? 1
        : remainder;
  return {
    capacityPerSecond,
    exactBelts,
    beltsRequired,
    lastBeltUtilization,
  };
}

export function recommendedBeltForRate(
  itemsPerSecond: number,
  stackSize: BeltStackSize,
): BeltId {
  const rate = Math.max(0, itemsPerSecond);
  return (
    BELT_TYPES.find(
      (belt) => belt.baseItemsPerSecond * stackSize + EPSILON >= rate,
    )?.id ?? BELT_TYPES.at(-1)!.id
  );
}

export function resolveBeltLoad(
  itemsPerSecond: number,
  requestedBeltId: BeltId,
  stackSize: BeltStackSize,
  autoUpgrade = true,
): ResolvedBeltLoad {
  const requestedCapacityPerSecond = beltCapacityPerSecond(
    requestedBeltId,
    stackSize,
  );
  const overloaded = itemsPerSecond > requestedCapacityPerSecond + EPSILON;
  const recommended = recommendedBeltForRate(itemsPerSecond, stackSize);
  const requestedIndex = BELT_TYPES.findIndex(
    (belt) => belt.id === requestedBeltId,
  );
  const recommendedIndex = BELT_TYPES.findIndex(
    (belt) => belt.id === recommended,
  );
  const effectiveBeltId =
    overloaded && autoUpgrade && recommendedIndex > requestedIndex
      ? recommended
      : requestedBeltId;
  return {
    ...calculateBeltLoad(itemsPerSecond, effectiveBeltId, stackSize),
    requestedBeltId,
    effectiveBeltId,
    requestedCapacityPerSecond,
    overloaded,
    automaticallyUpgraded: effectiveBeltId !== requestedBeltId,
  };
}
