#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// Page sizes represented by the legacy scene catalog. None is used by non-paged scenes.
/// </summary>
public enum LegacyScenePageSize
{
    None = 0,
    Five = 5,
    Six = 6,
    Twelve = 12
}

/// <summary>
/// Describes whether the legacy MainForm can construct a builder.
/// </summary>
public enum LegacySceneReachability
{
    MainFormDispatch,
    ConditionalMainFormDispatch,
    NoMainFormDispatch
}

/// <summary>
/// Closed metadata describing the K3D mutation families observed in a legacy builder.
/// It deliberately contains no object names, method names, values, or paths.
/// </summary>
[Flags]
public enum LegacySceneMutationCapability
{
    None = 0,
    Value = 1 << 0,
    AssetValue = 1 << 1,
    Visibility = 1 << 2,
    FaceColor = 1 << 3,
    Position = 1 << 4,
    PositionKey = 1 << 5,
    Scale = 1 << 6,
    Crop = 1 << 7,
    CircleAngleKey = 1 << 8,
    PathPoints = 1 << 9,
    PathShapePoints = 1 << 10,
    BackgroundVideo = 1 << 11
}

/// <summary>
/// Catalog-only marker that keeps every legacy source slot unique. Concrete runtime DTOs
/// implement ILegacySceneData and are exposed by LegacySceneMutationBuilderRegistry.
/// </summary>
public interface ILegacySceneDtoMarker
{
}

/// <summary>
/// Immutable metadata for one of the 35 legacy scene builders.
/// </summary>
public sealed class LegacySceneDefinition
{
    private const LegacySceneMutationCapability AllKnownCapabilities =
        LegacySceneMutationCapability.Value |
        LegacySceneMutationCapability.AssetValue |
        LegacySceneMutationCapability.Visibility |
        LegacySceneMutationCapability.FaceColor |
        LegacySceneMutationCapability.Position |
        LegacySceneMutationCapability.PositionKey |
        LegacySceneMutationCapability.Scale |
        LegacySceneMutationCapability.Crop |
        LegacySceneMutationCapability.CircleAngleKey |
        LegacySceneMutationCapability.PathPoints |
        LegacySceneMutationCapability.PathShapePoints |
        LegacySceneMutationCapability.BackgroundVideo;

    internal LegacySceneDefinition(
        string builderKey,
        string sceneCode,
        IEnumerable<string> cutAliases,
        LegacyScenePageSize pageSize,
        LegacySceneReachability reachability,
        Type dtoTypeMarker,
        string sourceHashKey,
        LegacySceneMutationCapability mutationCapabilities)
    {
        BuilderKey = RequireBuilderKey(builderKey);
        SceneCode = RequireDigits(sceneCode, nameof(sceneCode));
        PageSize = RequireDefined(pageSize, nameof(pageSize));
        Reachability = RequireDefined(reachability, nameof(reachability));
        DtoTypeMarker = RequireDtoType(dtoTypeMarker);
        SourceHashKey = RequireSourceHashKey(sourceHashKey);
        MutationCapabilities = RequireCapabilities(mutationCapabilities);

        ArgumentNullException.ThrowIfNull(cutAliases);
        var aliases = cutAliases
            .Select(alias => RequireAlias(alias))
            .ToArray();
        if (aliases.Distinct(StringComparer.Ordinal).Count() != aliases.Length)
        {
            throw new ArgumentException(
                "Cut aliases must be unique within a scene definition.",
                nameof(cutAliases));
        }

        if (reachability == LegacySceneReachability.NoMainFormDispatch)
        {
            if (aliases.Length != 0)
            {
                throw new ArgumentException(
                    "A scene with no MainForm dispatch cannot declare an active cut alias.",
                    nameof(cutAliases));
            }
        }
        else if (aliases.Length == 0)
        {
            throw new ArgumentException(
                "A reachable scene requires at least one cut alias.",
                nameof(cutAliases));
        }

        CutAliases = Array.AsReadOnly(aliases);
    }

    public string BuilderKey { get; }

    public string SceneCode { get; }

    public IReadOnlyList<string> CutAliases { get; }

    public LegacyScenePageSize PageSize { get; }

    public LegacySceneReachability Reachability { get; }

    public Type DtoTypeMarker { get; }

    /// <summary>
    /// Key used to locate the source hash entry; it is not the hash value or a file path.
    /// </summary>
    public string SourceHashKey { get; }

    public LegacySceneMutationCapability MutationCapabilities { get; }

    public bool HasCapability(LegacySceneMutationCapability capability) =>
        capability != LegacySceneMutationCapability.None &&
        (MutationCapabilities & capability) == capability;

    private static string RequireBuilderKey(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);
        if (value.Length < 2 || value[0] != 's' ||
            value.AsSpan(1).IndexOfAnyExceptInRange('0', '9') >= 0)
        {
            throw new ArgumentException(
                "BuilderKey must be a lowercase 's' followed by digits.",
                nameof(value));
        }

        return value;
    }

    private static string RequireDigits(string value, string parameterName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value, parameterName);
        if (value.AsSpan().IndexOfAnyExceptInRange('0', '9') >= 0)
        {
            throw new ArgumentException("The value must contain digits only.", parameterName);
        }

        return value;
    }

    private static string RequireAlias(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);
        if (!value.All(char.IsLetterOrDigit))
        {
            throw new ArgumentException(
                "A cut alias may contain letters and digits only.",
                nameof(value));
        }

        return value;
    }

    private static string RequireSourceHashKey(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);
        if (value.Any(character =>
                !(char.IsAsciiLetterOrDigit(character) || character is '-' or '_')))
        {
            throw new ArgumentException(
                "SourceHashKey must be an opaque key, not a path.",
                nameof(value));
        }

        return value;
    }

    private static Type RequireDtoType(Type value)
    {
        ArgumentNullException.ThrowIfNull(value);
        if (!typeof(ILegacySceneDtoMarker).IsAssignableFrom(value) ||
            value == typeof(ILegacySceneDtoMarker))
        {
            throw new ArgumentException(
                "DtoTypeMarker must be a concrete scene DTO marker type.",
                nameof(value));
        }

        return value;
    }

    private static TEnum RequireDefined<TEnum>(TEnum value, string parameterName)
        where TEnum : struct, Enum
    {
        if (!Enum.IsDefined(value))
        {
            throw new ArgumentOutOfRangeException(parameterName, value, "Unknown enum value.");
        }

        return value;
    }

    private static LegacySceneMutationCapability RequireCapabilities(
        LegacySceneMutationCapability value)
    {
        if (value == LegacySceneMutationCapability.None ||
            (value & ~AllKnownCapabilities) != 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(value),
                value,
                "At least one known mutation capability is required.");
        }

        return value;
    }
}
