#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

public enum LegacySceneBackgroundKind
{
    None,
    Texture,
    Video
}

/// <summary>
/// Trusted, native configuration for the MainForm-level scene background and fade.
/// It is constructed by the app configuration layer and is never deserialized from Web input.
/// </summary>
public sealed record LegacySceneCueCompositionOptions(
    int FadeDuration,
    LegacySceneBackgroundKind BackgroundKind,
    string? BackgroundAssetPath = null,
    int BackgroundVideoLoopCount = 2004,
    bool BackgroundVideoLoopInfinite = true,
    PlayoutAssetRoot BackgroundAssetRoot = PlayoutAssetRoot.SceneDirectory)
{
    public static LegacySceneCueCompositionOptions Default { get; } = new(
        // MainForm initializes ComboDi.SelectedIndex to 6 and passes that value
        // to every SetSceneEffectType call.
        FadeDuration: 6,
        BackgroundKind: LegacySceneBackgroundKind.None);
}

/// <summary>
/// Supplies an immutable composition snapshot for one cue build. The native app may
/// atomically promote a separately validated next-cue draft between builds; Web content
/// never supplies an asset path.
/// </summary>
public interface ILegacySceneCueCompositionOptionsSource
{
    LegacySceneCueCompositionOptions Current { get; }
}

public sealed class MutableLegacySceneCueCompositionOptionsSource :
    ILegacySceneCueCompositionOptionsSource
{
    private LegacySceneCueCompositionOptions _current;

    public MutableLegacySceneCueCompositionOptionsSource(
        LegacySceneCueCompositionOptions initial)
    {
        _current = RegisteredLegacySceneCueProvider.ValidateOptions(initial);
    }

    public LegacySceneCueCompositionOptions Current => Volatile.Read(ref _current);

    public void Update(LegacySceneCueCompositionOptions value)
    {
        var validated = RegisteredLegacySceneCueProvider.ValidateOptions(value);
        Interlocked.Exchange(ref _current, validated);
    }
}

/// <summary>
/// One trusted DTO produced by a scene-specific database loader. BuilderKey resolves
/// conditional aliases such as 5032/8018 without accepting K3D object or method names.
/// </summary>
public sealed record LegacySceneDataPage(
    string BuilderKey,
    ILegacySceneData Data,
    int ItemCount);

public interface ILegacySceneDataSource
{
    Task<LegacySceneDataPage> LoadPageAsync(
        LegacyPlaylistEntry entry,
        int pageIndexZeroBased,
        CancellationToken cancellationToken = default);
}

/// <summary>
/// Optional read-only route that loads a bounded page-zero DTO with one extra row used
/// only to detect the 20-page truncation boundary. It never builds scene mutations.
/// </summary>
public interface ILegacyScenePagePlanDataSource
{
    Task<LegacySceneDataPage> LoadPagePlanDataAsync(
        LegacyPlaylistEntry entry,
        CancellationToken cancellationToken = default);
}

/// <summary>
/// Converts a native DB DTO into a complete cue while preserving the legacy order:
/// load/effect/background, BeginTransaction, ordered builder mutations,
/// QueryVariables, EndTransaction, Prepare. The latter operations are performed by
/// IPlayoutEngine; this provider exposes no COM or reflection surface.
/// </summary>
public sealed class RegisteredLegacySceneCueProvider :
    ILegacySceneCueProvider,
    ILegacyScenePagePlanProvider
{
    private readonly ILegacySceneDataSource _dataSource;
    private readonly ILegacyScenePagePlanDataSource? _pagePlanDataSource;
    private readonly LegacySceneMutationBuilderRegistry _builders;
    private readonly ILegacySceneCueCompositionOptionsSource _optionsSource;

    public RegisteredLegacySceneCueProvider(
        ILegacySceneDataSource dataSource,
        LegacySceneMutationBuilderRegistry? builders = null,
        LegacySceneCueCompositionOptions? options = null)
        : this(
            dataSource,
            new FixedCompositionOptionsSource(
                ValidateOptions(options ?? LegacySceneCueCompositionOptions.Default)),
            builders)
    {
    }

    public RegisteredLegacySceneCueProvider(
        ILegacySceneDataSource dataSource,
        ILegacySceneCueCompositionOptionsSource optionsSource,
        LegacySceneMutationBuilderRegistry? builders = null)
    {
        _dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
        _pagePlanDataSource = dataSource as ILegacyScenePagePlanDataSource;
        _builders = builders ?? LegacySceneMutationBuilderRegistry.Default;
        _optionsSource = optionsSource ?? throw new ArgumentNullException(nameof(optionsSource));
        _ = ValidateOptions(_optionsSource.Current);
    }

    public async Task<LegacySceneCuePage> CreatePageAsync(
        LegacyPlaylistEntry entry,
        int pageIndexZeroBased,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(entry);
        if (pageIndexZeroBased < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(pageIndexZeroBased));
        }

        // Capture once before the asynchronous DB load. F2/F3 changes can affect only a
        // later PREPARE and cannot alter a cue halfway through construction.
        var options = ValidateOptions(_optionsSource.Current);

        var loaded = await _dataSource.LoadPageAsync(
            entry,
            pageIndexZeroBased,
            cancellationToken).ConfigureAwait(false);
        ArgumentNullException.ThrowIfNull(loaded);
        ArgumentNullException.ThrowIfNull(loaded.Data);
        if (loaded.ItemCount < 0)
        {
            throw new LegacySceneDataException("A scene loader returned an invalid item count.");
        }

        var definition = RequireDefinition(entry, loaded);

        var pageSize = ConvertPageSize(definition.PageSize);
        if (pageSize is null && pageIndexZeroBased != 0)
        {
            throw new LegacySceneDataException("A non-paged scene cannot load another page.");
        }

        if (pageSize is { } size)
        {
            var window = ScenePaging.GetWindow(
                loaded.ItemCount,
                size,
                pageIndexZeroBased + 1);
            if (!window.IsValid)
            {
                throw new LegacySceneDataException(
                    "The requested scene page is outside the accessible 20-page range.");
            }
        }

        var builderMutations = _builders.Build(definition.BuilderKey, loaded.Data);
        var assetMutations = ApplyAssetContract(entry.CutCode, builderMutations);
        var mutations = new List<PlayoutMutation>(assetMutations.Count + 2);
        AddBackgroundMutations(mutations, options);
        mutations.AddRange(assetMutations);

        return new LegacySceneCuePage(
            new PlayoutCue(
                entry.CutCode + ".t2s",
                entry.CutCode,
                FadeDuration: entry.FadeDuration ?? options.FadeDuration,
                Mutations: Array.AsReadOnly(mutations.ToArray())),
            loaded.ItemCount,
            pageSize,
            definition.BuilderKey,
            LegacyScenePreviewFactory.Create(Array.AsReadOnly(mutations.ToArray())));
    }

    public async Task<LegacyScenePagePlan> CreatePagePlanAsync(
        LegacyPlaylistEntry entry,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(entry);
        var source = _pagePlanDataSource ?? throw new InvalidOperationException(
            "The registered scene data source does not expose page preflight data.");
        var loaded = await source.LoadPagePlanDataAsync(entry, cancellationToken)
            .ConfigureAwait(false);
        ArgumentNullException.ThrowIfNull(loaded);
        ArgumentNullException.ThrowIfNull(loaded.Data);
        if (loaded.ItemCount < 0)
        {
            throw new LegacySceneDataException("A scene page preflight returned an invalid item count.");
        }

        var definition = RequireDefinition(entry, loaded);
        var pageSize = ConvertPageSize(definition.PageSize) ?? throw new LegacySceneDataException(
            "Page preflight is available only for a 5-, 6-, or 12-row scene.");
        var result = ScenePaging.CreatePlan(loaded.ItemCount, pageSize);
        if (!result.IsValid || result.Plan is null)
        {
            throw new LegacySceneDataException("The scene page preflight returned invalid paging data.");
        }

        var plan = result.Plan;
        return new LegacyScenePagePlan(
            definition.BuilderKey,
            plan.ItemCount,
            plan.PageSize,
            plan.TotalPages,
            plan.AccessibleItemCount,
            plan.ExcludedItemCount > 0);
    }

    private static LegacySceneDefinition RequireDefinition(
        LegacyPlaylistEntry entry,
        LegacySceneDataPage loaded)
    {
        var definition = LegacySceneCatalog.Definitions.SingleOrDefault(candidate =>
            string.Equals(candidate.BuilderKey, loaded.BuilderKey, StringComparison.Ordinal));
        if (definition is null ||
            definition.Reachability == LegacySceneReachability.NoMainFormDispatch ||
            !definition.CutAliases.Contains(entry.CutCode, StringComparer.Ordinal))
        {
            throw new LegacySceneDataException(
                "The scene loader returned a builder that is not valid for the selected cut.");
        }

        return definition;
    }

    private static ScenePageSize? ConvertPageSize(LegacyScenePageSize value) => value switch
    {
        LegacyScenePageSize.None => null,
        LegacyScenePageSize.Five => ScenePageSize.Five,
        LegacyScenePageSize.Six => ScenePageSize.Six,
        LegacyScenePageSize.Twelve => ScenePageSize.Twelve,
        _ => throw new LegacySceneDataException("The scene catalog contains an invalid page size.")
    };

    private static IReadOnlyList<PlayoutMutation> ApplyAssetContract(
        string cutCode,
        IReadOnlyList<PlayoutMutation> mutations)
    {
        if (string.Equals(cutCode, "5032", StringComparison.Ordinal))
        {
            return mutations.Where(static mutation => mutation switch
            {
                PlayoutSetAssetValue { ObjectName: "pattern" } => false,
                PlayoutSetVisible visible when IsAbsent5032Decoration(visible.ObjectName) => false,
                _ => true
            }).ToArray();
        }

        if (!string.Equals(cutCode, "8032", StringComparison.Ordinal))
        {
            return mutations;
        }

        return mutations.Where(static mutation => mutation switch
        {
            PlayoutSetAssetValue { ObjectName: "pattern1" or "pattern2" } => false,
            PlayoutSetValue { ObjectName: "wonText1" or "wonText2" } => false,
            PlayoutSetVisible visible when IsAbsent8032Variant(visible.ObjectName) => false,
            _ => true
        }).ToArray();
    }

    private static bool IsAbsent8032Variant(string objectName) => objectName is
        "percent1_1" or "percent2_1" or "percent3_1" or
        "percent1_2" or "percent2_2" or "percent3_2" or
        "wonText1_1" or "wonText2_1" or "wonText3_1" or
        "wonText1_2" or "wonText2_2" or "wonText3_2";

    private static bool IsAbsent5032Decoration(string objectName) => objectName is
        "bg1_" or "changePrice1_" or "wonText1_" or "rate1_" or "percent1_" or
        "bg2_" or "changePrice2_" or "wonText2_" or "rate2_" or "percent2_" or
        "bg3_" or "changePrice3_" or "wonText3_" or "rate3_" or "percent3_" or
        "unit1_" or "unit2_" or "unit3_" or
        "price1_" or "price2_" or "price3_";

    internal static LegacySceneCueCompositionOptions ValidateOptions(
        LegacySceneCueCompositionOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);
        if (options.FadeDuration is < 0 or > 19 ||
            !Enum.IsDefined(options.BackgroundKind) ||
            !Enum.IsDefined(options.BackgroundAssetRoot) ||
            (options.BackgroundKind == LegacySceneBackgroundKind.Video &&
                options.BackgroundVideoLoopCount is < 0 or > 10_000))
        {
            throw new ArgumentException("The legacy cue composition options are invalid.", nameof(options));
        }

        if (options.BackgroundKind == LegacySceneBackgroundKind.None)
        {
            if (!string.IsNullOrEmpty(options.BackgroundAssetPath))
            {
                throw new ArgumentException(
                    "A disabled background cannot specify an asset.",
                    nameof(options));
            }

            return options;
        }

        var path = options.BackgroundAssetPath;
        if (string.IsNullOrWhiteSpace(path) || path.Length > 512 ||
            Path.IsPathRooted(path) || path.Contains("..", StringComparison.Ordinal) ||
            path.Any(char.IsControl))
        {
            throw new ArgumentException(
                "The background asset must be a safe relative path.",
                nameof(options));
        }

        return options with { BackgroundAssetPath = path.Trim() };
    }

    private static void AddBackgroundMutations(
        ICollection<PlayoutMutation> mutations,
        LegacySceneCueCompositionOptions options)
    {
        switch (options.BackgroundKind)
        {
            case LegacySceneBackgroundKind.None:
                mutations.Add(new PlayoutUseBackground(false));
                break;
            case LegacySceneBackgroundKind.Texture:
                mutations.Add(new PlayoutSetBackgroundTexture(
                    options.BackgroundAssetPath!,
                    AssetRoot: options.BackgroundAssetRoot));
                mutations.Add(new PlayoutUseBackground(true));
                break;
            case LegacySceneBackgroundKind.Video:
                mutations.Add(new PlayoutSetBackgroundVideo(
                    options.BackgroundAssetPath!,
                    options.BackgroundVideoLoopCount,
                    // MainForm passes the vendor's raw Int32 flag 10 here. This is
                    // deliberately distinct from scene s5006, which passes 1.
                    options.BackgroundVideoLoopInfinite ? 10 : 0,
                    AssetRoot: options.BackgroundAssetRoot));
                mutations.Add(new PlayoutUseBackground(true));
                break;
            default:
                throw new InvalidOperationException("Unknown background kind.");
        }
    }

    private sealed class FixedCompositionOptionsSource(
        LegacySceneCueCompositionOptions current) : ILegacySceneCueCompositionOptionsSource
    {
        public LegacySceneCueCompositionOptions Current { get; } = current;
    }
}
