using MMDAL.DTO.Scheduling; namespace MMBLL.Scheduling.Engine { // Topological sort via Kahn's algorithm. // Throws SchedulingEngineException on cycle — never proceeds to scheduling with a cyclic graph. public static class DependencyGraph { public static IReadOnlyList Sort(IReadOnlyList eus) { var euById = eus.ToDictionary(e => e.EuId); var inDegree = eus.ToDictionary(e => e.EuId, _ => 0); var adjacency = new Dictionary>(); foreach (var eu in eus) { adjacency[eu.EuId] = new List(); } // Build adjacency list: predecessor → successor foreach (var eu in eus) { foreach (var predId in eu.PredecessorEuIds) { if (!euById.ContainsKey(predId)) continue; // external predecessor — ignore adjacency[predId].Add(eu.EuId); inDegree[eu.EuId]++; } } // Kahn's algorithm — seed with nodes that have no incoming edges var queue = new Queue(inDegree.Where(kv => kv.Value == 0).Select(kv => kv.Key)); var sorted = new List(eus.Count); while (queue.Count > 0) { int current = queue.Dequeue(); var eu = euById[current]; // Propagate ComputedEarliestStart from predecessors (partial dependency offset) foreach (var predId in eu.PredecessorEuIds) { if (!euById.TryGetValue(predId, out var pred) || pred.ScheduledStart is null) continue; DateTime inheritedStart; // Check for partial dependency: requires explicit TransferQuantity / TotalQuantity // stored on the EU — if not set, use full predecessor end (FullFinish) if (eu.MaxWaitMinutes == 0) { inheritedStart = pred.ScheduledEnd ?? pred.ComputedEarliestStart; } else { inheritedStart = pred.ComputedEarliestStart; } if (inheritedStart > eu.ComputedEarliestStart) eu.ComputedEarliestStart = inheritedStart; } sorted.Add(eu); foreach (int successorId in adjacency[current]) { inDegree[successorId]--; if (inDegree[successorId] == 0) queue.Enqueue(successorId); } } if (sorted.Count != eus.Count) throw new SchedulingEngineException( "Circular dependency detected in routing predecessor definitions. " + $"Processed {sorted.Count} of {eus.Count} execution units before deadlock."); return sorted; } } public sealed class SchedulingEngineException : Exception { public SchedulingEngineException(string message) : base(message) { } } }