using ErsatzTV.Core.Domain; using OrderSet = System.Collections.Generic.HashSet; namespace ErsatzTV.Core.Scheduling; /// /// The scheduling engine families that turn a into an enumerator, and which /// orders each one actually handles. This is the single declared support matrix for #403. /// /// /// Each engine keeps BOTH a Supported and an Unsupported set, maintained by hand ON PURPOSE: /// PlaybackOrderSupportTests asserts the two sets partition every value /// (union is total, intersection empty), so adding a new order fails that test until it is consciously /// classified here. Deriving Unsupported as "everything not supported" would let a new order fall /// through silently — the very defect #403 exists to kill — so it is spelled out instead. /// /// Membership here mirrors the executable dispatch in each builder (PlayoutBuilder, /// PlaylistEnumerator, BlockPlayoutBuilder, EnumeratorCache [YAML], /// SchedulingEngine.EnumeratorForContent [Scripted]); the builders remain the real logic. Only /// consumes this table at runtime (its allow-list), so the table /// is not merely test scaffolding. When you add a case to one of those switches, update the matching set /// here. /// public static class PlaybackOrderSupport { private sealed record EngineSupport( IReadOnlySet Supported, IReadOnlySet Unsupported); private static readonly IReadOnlyDictionary Matrix = new Dictionary { // PlayoutBuilder.GetMediaCollectionEnumerator switch; default arm falls back to Random (now loud). [SchedulingEngineKind.Classic] = new EngineSupport( new OrderSet { PlaybackOrder.Chronological, PlaybackOrder.SeasonEpisode, PlaybackOrder.Random, PlaybackOrder.Shuffle, PlaybackOrder.ShuffleInOrder, PlaybackOrder.MultiEpisodeShuffle, PlaybackOrder.Marathon, PlaybackOrder.WeightedShuffle }, new OrderSet { PlaybackOrder.None, PlaybackOrder.RandomRotation }), // PlaylistEnumerator.Create switch; no default arm meant a null enumerator -> silent drop (now loud). [SchedulingEngineKind.Playlist] = new EngineSupport( new OrderSet { PlaybackOrder.Chronological, PlaybackOrder.SeasonEpisode, PlaybackOrder.Random, PlaybackOrder.Shuffle, PlaybackOrder.ShuffleInOrder, PlaybackOrder.MultiEpisodeShuffle }, new OrderSet { PlaybackOrder.None, PlaybackOrder.RandomRotation, PlaybackOrder.Marathon, PlaybackOrder.WeightedShuffle }), // BlockPlayoutBuilder allow-list (this very set) + the GetEnumerator switch. [SchedulingEngineKind.Block] = new EngineSupport( new OrderSet { PlaybackOrder.Chronological, PlaybackOrder.SeasonEpisode, PlaybackOrder.Shuffle, PlaybackOrder.Random, PlaybackOrder.RandomRotation }, new OrderSet { PlaybackOrder.None, PlaybackOrder.ShuffleInOrder, PlaybackOrder.MultiEpisodeShuffle, PlaybackOrder.Marathon, PlaybackOrder.WeightedShuffle }), // EnumeratorCache (YAML, non-playlist path); unsupported orders LogWarning + None. [SchedulingEngineKind.Yaml] = new EngineSupport( new OrderSet { PlaybackOrder.Chronological, PlaybackOrder.Shuffle }, new OrderSet { PlaybackOrder.None, PlaybackOrder.Random, PlaybackOrder.SeasonEpisode, PlaybackOrder.ShuffleInOrder, PlaybackOrder.MultiEpisodeShuffle, PlaybackOrder.RandomRotation, PlaybackOrder.Marathon, PlaybackOrder.WeightedShuffle }), // SchedulingEngine.EnumeratorForContent (Scripted); unsupported orders LogWarning + None. [SchedulingEngineKind.Scripted] = new EngineSupport( new OrderSet { PlaybackOrder.Chronological, PlaybackOrder.Shuffle }, new OrderSet { PlaybackOrder.None, PlaybackOrder.Random, PlaybackOrder.SeasonEpisode, PlaybackOrder.ShuffleInOrder, PlaybackOrder.MultiEpisodeShuffle, PlaybackOrder.RandomRotation, PlaybackOrder.Marathon, PlaybackOrder.WeightedShuffle }) }; public static IReadOnlyCollection Engines => Matrix.Keys.ToList(); /// The orders can turn into an enumerator. public static IReadOnlySet SupportedBy(SchedulingEngineKind engine) => Matrix[engine].Supported; /// The orders explicitly known NOT to be handled by . public static IReadOnlySet UnsupportedBy(SchedulingEngineKind engine) => Matrix[engine].Unsupported; public static bool IsSupported(SchedulingEngineKind engine, PlaybackOrder order) => Matrix[engine].Supported.Contains(order); /// /// True when appears in either set for . A new enum /// value is classified in neither until a human adds it — which is what the tripwire test asserts. /// public static bool IsClassified(SchedulingEngineKind engine, PlaybackOrder order) => Matrix[engine].Supported.Contains(order) || Matrix[engine].Unsupported.Contains(order); }