#nullable enable

using System.Collections.ObjectModel;
using System.Reflection;

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// Read-only metadata for one trusted scene mutation builder. Instances can only be
/// created by <see cref="LegacySceneMutationBuilderRegistry"/>.
/// </summary>
public sealed class LegacySceneMutationBuilderRegistration
{
    internal LegacySceneMutationBuilderRegistration(string builderKey, Type dataType)
    {
        BuilderKey = builderKey;
        DataType = dataType;
    }

    /// <summary>
    /// Closed builder identity from <see cref="LegacySceneCatalog"/>.
    /// </summary>
    public string BuilderKey { get; }

    /// <summary>
    /// Trusted native-data contract accepted by the registered builder.
    /// </summary>
    public Type DataType { get; }
}

/// <summary>
/// Closed, COM-neutral dispatch table for every ported legacy scene builder.
/// The registry accepts only a catalog builder key and an <see cref="ILegacySceneData"/>
/// instance; it has no raw K3D method, object-name, mutation, or asset-path input.
/// </summary>
public sealed class LegacySceneMutationBuilderRegistry
{
    private static readonly Lazy<LegacySceneMutationBuilderRegistry> DefaultRegistry =
        new(CreateDefault, LazyThreadSafetyMode.ExecutionAndPublication);

    private readonly IReadOnlyDictionary<string, BuilderAdapter> _byBuilderKey;

    private LegacySceneMutationBuilderRegistry(
        IReadOnlyDictionary<string, BuilderAdapter> byBuilderKey,
        IReadOnlyList<LegacySceneMutationBuilderRegistration> registrations)
    {
        _byBuilderKey = byBuilderKey;
        Registrations = registrations;
    }

    /// <summary>
    /// Registry discovered once from the Core assembly and validated against all 35
    /// entries in <see cref="LegacySceneCatalog"/>.
    /// </summary>
    public static LegacySceneMutationBuilderRegistry Default => DefaultRegistry.Value;

    /// <summary>
    /// Registrations in catalog order. The collection and its entries are immutable.
    /// </summary>
    public IReadOnlyList<LegacySceneMutationBuilderRegistration> Registrations { get; }

    /// <summary>
    /// Looks up a closed builder identity. Invalid or unregistered strings are rejected
    /// without being interpreted as a scene file, type name, or K3D object name.
    /// </summary>
    public bool TryGetRegistration(
        string? builderKey,
        out LegacySceneMutationBuilderRegistration? registration)
    {
        registration = null;
        if (!IsBuilderKey(builderKey) ||
            !_byBuilderKey.TryGetValue(builderKey!, out var adapter))
        {
            return false;
        }

        registration = adapter.Registration;
        return true;
    }

    /// <summary>
    /// Dispatches trusted scene data to the builder registered for <paramref name="builderKey"/>.
    /// A DTO registered for any other key is rejected before its builder is called.
    /// </summary>
    public IReadOnlyList<PlayoutMutation> Build(
        string builderKey,
        ILegacySceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        if (!IsBuilderKey(builderKey) ||
            !_byBuilderKey.TryGetValue(builderKey, out var adapter))
        {
            throw new ArgumentException(
                "The scene builder key is not registered.",
                nameof(builderKey));
        }

        return adapter.Build(data);
    }

    private static LegacySceneMutationBuilderRegistry CreateDefault()
    {
        var builderContract = typeof(ILegacySceneMutationBuilder<>);
        var candidates = typeof(LegacySceneMutationBuilderRegistry)
            .Assembly
            .DefinedTypes
            .Where(type => type.IsClass && !type.IsAbstract)
            .Select(type => new
            {
                Type = type,
                Contracts = type.ImplementedInterfaces
                    .Where(contract =>
                        contract.IsGenericType &&
                        contract.GetGenericTypeDefinition() == builderContract)
                    .ToArray()
            })
            .Where(candidate => candidate.Contracts.Length != 0)
            .OrderBy(candidate => candidate.Type.FullName, StringComparer.Ordinal)
            .ToArray();

        var adapters = new Dictionary<string, BuilderAdapter>(StringComparer.Ordinal);
        foreach (var candidate in candidates)
        {
            if (candidate.Contracts.Length != 1)
            {
                throw new InvalidOperationException(
                    "A legacy scene builder must implement exactly one typed builder contract.");
            }

            var constructor = candidate.Type.GetConstructor(
                BindingFlags.Instance | BindingFlags.Public,
                binder: null,
                Type.EmptyTypes,
                modifiers: null);
            if (constructor is null)
            {
                throw new InvalidOperationException(
                    "Every legacy scene builder must expose a public parameterless constructor.");
            }

            var dataType = candidate.Contracts[0].GenericTypeArguments[0];
            ValidateDataType(dataType);

            var builder = constructor.Invoke(null);
            var adapter = CreateAdapter(dataType, builder);
            if (!IsBuilderKey(adapter.Registration.BuilderKey))
            {
                throw new InvalidOperationException(
                    "A legacy scene builder returned an invalid builder key.");
            }

            if (!adapters.TryAdd(adapter.Registration.BuilderKey, adapter))
            {
                throw new InvalidOperationException(
                    "Every legacy scene builder key must have exactly one implementation.");
            }
        }

        var catalogKeys = LegacySceneCatalog.Definitions
            .Select(definition => definition.BuilderKey)
            .ToArray();
        var discoveredKeys = adapters.Keys
            .OrderBy(key => key, StringComparer.Ordinal)
            .ToArray();
        if (adapters.Count != LegacySceneCatalog.ExpectedDefinitionCount ||
            !catalogKeys.OrderBy(key => key, StringComparer.Ordinal)
                .SequenceEqual(discoveredKeys, StringComparer.Ordinal))
        {
            throw new InvalidOperationException(
                "Legacy scene builders must have exact one-to-one parity with the 35-scene catalog.");
        }

        var orderedAdapters = catalogKeys
            .Select(key => adapters[key])
            .ToArray();
        var registrations = Array.AsReadOnly(
            orderedAdapters.Select(adapter => adapter.Registration).ToArray());
        var readOnlyAdapters = new ReadOnlyDictionary<string, BuilderAdapter>(
            orderedAdapters.ToDictionary(
                adapter => adapter.Registration.BuilderKey,
                StringComparer.Ordinal));

        return new LegacySceneMutationBuilderRegistry(readOnlyAdapters, registrations);
    }

    private static BuilderAdapter CreateAdapter(Type dataType, object builder)
    {
        var factory = typeof(LegacySceneMutationBuilderRegistry)
            .GetMethod(
                nameof(CreateTypedAdapter),
                BindingFlags.Static | BindingFlags.NonPublic)!
            .MakeGenericMethod(dataType);
        try
        {
            return (BuilderAdapter)factory.Invoke(null, [builder])!;
        }
        catch (TargetInvocationException exception) when (exception.InnerException is not null)
        {
            throw exception.InnerException;
        }
    }

    private static BuilderAdapter CreateTypedAdapter<TData>(object builder)
        where TData : ILegacySceneData =>
        new TypedBuilderAdapter<TData>((ILegacySceneMutationBuilder<TData>)builder);

    private static void ValidateDataType(Type dataType)
    {
        if (dataType == typeof(ILegacySceneData) ||
            !typeof(ILegacySceneData).IsAssignableFrom(dataType) ||
            dataType.IsInterface ||
            dataType.ContainsGenericParameters)
        {
            throw new InvalidOperationException(
                "A legacy scene builder must use a closed, concrete scene-data contract.");
        }
    }

    private static bool IsBuilderKey(string? value)
    {
        if (value is null || value.Length is < 2 or > 16 || value[0] != 's')
        {
            return false;
        }

        return value.AsSpan(1).IndexOfAnyExceptInRange('0', '9') < 0;
    }

    private abstract class BuilderAdapter
    {
        protected BuilderAdapter(string builderKey, Type dataType)
        {
            Registration = new LegacySceneMutationBuilderRegistration(builderKey, dataType);
        }

        public LegacySceneMutationBuilderRegistration Registration { get; }

        public abstract IReadOnlyList<PlayoutMutation> Build(ILegacySceneData data);
    }

    private sealed class TypedBuilderAdapter<TData> : BuilderAdapter
        where TData : ILegacySceneData
    {
        private readonly ILegacySceneMutationBuilder<TData> _builder;

        public TypedBuilderAdapter(ILegacySceneMutationBuilder<TData> builder)
            : base(builder.BuilderKey, typeof(TData))
        {
            _builder = builder;
        }

        public override IReadOnlyList<PlayoutMutation> Build(ILegacySceneData data)
        {
            if (data is not TData typedData)
            {
                throw new ArgumentException(
                    "The scene data does not match the registered builder key.",
                    nameof(data));
            }

            var mutations = _builder.Build(typedData) ??
                throw new InvalidOperationException(
                    "A legacy scene mutation builder returned no mutation collection.");
            if (mutations.Any(mutation => mutation is null))
            {
                throw new InvalidOperationException(
                    "A legacy scene mutation builder returned a null mutation.");
            }

            return Array.AsReadOnly(mutations.ToArray());
        }
    }
}
