/** * Draw order for a schedule's consist. * * A slot's stored `sequenceNo` is its place in the wagon PLAN, not its place in * the train. The train's real coupling order lives on the physical wagons * (`wagons.sequence_number`), which the caller passes in already ordered — ASC * normally, DESC for a `reverseWagonOrder` schedule. * * Ordering by the physical wagon is what keeps the drawing honest: * - moving a load between wagons repaints WHICH wagon is loaded and never * shuffles the train, because each slot is drawn wherever its wagon sits; * - a train-builder reorder lands on the next read, allocations included, * since the order is derived on every read instead of copied at pin time. * * Slots with no physical wagon (not pinned yet, or a schedule that isn't tied * to a built train) have no place in the consist — they keep slot order, last. */ export interface ConsistOrderable { sequenceNo: number; physicalWagonId?: string | null; } export interface ConsistOrderOptions { /** * Every wagon coupled to the built train, in real coupling order (already * reversed by the caller for a `reverseWagonOrder` schedule). Empty for a * frozen schedule or one with no built train — the consist then keeps slot * order. */ physicalWagonIdsInOrder: string[]; reverseWagonOrder?: boolean; } export const orderConsistWagons = ( wagons: T[], { physicalWagonIdsInOrder, reverseWagonOrder }: ConsistOrderOptions, ): (T & { position: number })[] => { const physicalOrder = new Map(physicalWagonIdsInOrder.map((id, index) => [id, index])); const bySlotSequence = (a: T, b: T) => reverseWagonOrder ? b.sequenceNo - a.sequenceNo : a.sequenceNo - b.sequenceNo; const ordered = physicalOrder.size ? [...wagons].sort((a, b) => { const ai = a.physicalWagonId ? physicalOrder.get(a.physicalWagonId) : undefined; const bi = b.physicalWagonId ? physicalOrder.get(b.physicalWagonId) : undefined; if (ai == null && bi == null) return bySlotSequence(a, b); if (ai == null) return 1; if (bi == null) return -1; return ai - bi; }) : [...wagons].sort(bySlotSequence); return ordered.map((wagon, index) => ({ ...wagon, position: index + 1 })); };