using ErsatzTV.Core.Domain;
using OrderSet = System.Collections.Generic.HashSet<ErsatzTV.Core.Domain.PlaybackOrder>;

namespace ErsatzTV.Core.Scheduling;

/// <summary>
///     The scheduling engine families that turn a <see cref="PlaybackOrder" /> into an enumerator, and which
///     orders each one actually handles. This is the single declared support matrix for #403.
/// </summary>
/// <remarks>
///     Each engine keeps BOTH a <c>Supported</c> and an <c>Unsupported</c> set, maintained by hand ON PURPOSE:
///     <c>PlaybackOrderSupportTests</c> asserts the two sets partition every <see cref="PlaybackOrder" /> value
///     (union is total, intersection empty), so adding a new order fails that test until it is consciously
///     classified here. Deriving <c>Unsupported</c> 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 (<c>PlayoutBuilder</c>,
///     <c>PlaylistEnumerator</c>, <c>BlockPlayoutBuilder</c>, <c>EnumeratorCache</c> [YAML],
///     <c>SchedulingEngine.EnumeratorForContent</c> [Scripted]); the builders remain the real logic. Only
///     <see cref="SchedulingEngineKind.Block" /> 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.
/// </remarks>
public static class PlaybackOrderSupport
{
    private sealed record EngineSupport(
        IReadOnlySet<PlaybackOrder> Supported,
        IReadOnlySet<PlaybackOrder> Unsupported);

    private static readonly IReadOnlyDictionary<SchedulingEngineKind, EngineSupport> Matrix =
        new Dictionary<SchedulingEngineKind, EngineSupport>
        {
            // 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<SchedulingEngineKind> Engines => Matrix.Keys.ToList();

    /// <summary>The orders <paramref name="engine" /> can turn into an enumerator.</summary>
    public static IReadOnlySet<PlaybackOrder> SupportedBy(SchedulingEngineKind engine) => Matrix[engine].Supported;

    /// <summary>The orders explicitly known NOT to be handled by <paramref name="engine" />.</summary>
    public static IReadOnlySet<PlaybackOrder> UnsupportedBy(SchedulingEngineKind engine) => Matrix[engine].Unsupported;

    public static bool IsSupported(SchedulingEngineKind engine, PlaybackOrder order) =>
        Matrix[engine].Supported.Contains(order);

    /// <summary>
    ///     True when <paramref name="order" /> appears in either set for <paramref name="engine" />. A new enum
    ///     value is classified in neither until a human adds it — which is what the tripwire test asserts.
    /// </summary>
    public static bool IsClassified(SchedulingEngineKind engine, PlaybackOrder order) =>
        Matrix[engine].Supported.Contains(order) || Matrix[engine].Unsupported.Contains(order);
}
