#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout;

/// <summary>
/// The only page sizes used by the legacy PageN/Nxt_PageN workflow.
/// </summary>
public enum ScenePageSize
{
    Five = 5,
    Six = 6,
    Twelve = 12
}

public enum ScenePagingResultKind
{
    Valid,
    Invalid
}

/// <summary>
/// A validated paging plan. Page numbers are one-based; item offsets are zero-based.
/// Items beyond <see cref="ScenePaging.MaximumPageCount"/> pages are deliberately excluded.
/// </summary>
public sealed record ScenePagingPlan
{
    internal ScenePagingPlan(
        int itemCount,
        ScenePageSize pageSize,
        int totalPages,
        int accessibleItemCount)
    {
        ItemCount = itemCount;
        PageSize = pageSize;
        TotalPages = totalPages;
        AccessibleItemCount = accessibleItemCount;
    }

    public int ItemCount { get; }

    public ScenePageSize PageSize { get; }

    public int TotalPages { get; }

    public int AccessibleItemCount { get; }

    public int ExcludedItemCount => ItemCount - AccessibleItemCount;
}

public sealed record ScenePagingPlanResult(
    ScenePagingResultKind Kind,
    ScenePagingPlan? Plan,
    string Error)
{
    public bool IsValid => Kind == ScenePagingResultKind.Valid;
}

/// <summary>
/// Describes one valid page. PageNumberOneBased is one-based; OffsetZeroBased is zero-based.
/// RemainingItemCount is limited to the accessible 20-page range.
/// </summary>
public sealed record ScenePageWindow(
    int PageNumberOneBased,
    int TotalPages,
    int OffsetZeroBased,
    int ItemCount,
    int RemainingItemCount);

public sealed record ScenePageWindowResult(
    ScenePagingResultKind Kind,
    ScenePagingPlan? Plan,
    ScenePageWindow? Window,
    string Error)
{
    public bool IsValid => Kind == ScenePagingResultKind.Valid;
}

/// <summary>
/// A playlist definition. ItemCount must be zero for a non-paged cue.
/// </summary>
public sealed record ScenePlaylistCue(
    string CueCode,
    bool IsEnabled,
    ScenePageSize? PageSize = null,
    int ItemCount = 0);

/// <summary>
/// Runtime state used to decide NEXT. CurrentCueIndexZeroBased is zero-based and
/// CurrentPageNumberOneBased is one-based. A non-paged cue, or a paged cue with no
/// available items, uses a null current page.
/// </summary>
public sealed record SceneNextState(
    IReadOnlyList<ScenePlaylistCue>? Playlist,
    int CurrentCueIndexZeroBased,
    int? CurrentPageNumberOneBased);

public enum SceneNextDecisionKind
{
    SameCueNextPage,
    NextEnabledCue,
    EndOfPlaylist,
    Invalid
}

public sealed record SceneNextDecision(
    SceneNextDecisionKind Kind,
    int? TargetCueIndexZeroBased,
    int? TargetPageNumberOneBased,
    ScenePageWindow? TargetPage,
    string Error)
{
    public bool IsValid => Kind != SceneNextDecisionKind.Invalid;
}

/// <summary>
/// Pure PageN/Nxt_PageN calculations with no database, COM, or UI dependency.
/// </summary>
public static class ScenePaging
{
    public const int MaximumPageCount = 20;

    public static ScenePagingPlanResult CreatePlan(
        int itemCount,
        ScenePageSize pageSize)
    {
        if (itemCount < 0)
        {
            return InvalidPlan("ItemCount cannot be negative.");
        }

        if (!IsSupported(pageSize))
        {
            return InvalidPlan("PageSize must be 5, 6, or 12.");
        }

        var size = (int)pageSize;
        var uncappedTotalPages = itemCount == 0
            ? 0
            : ((itemCount - 1) / size) + 1;
        var totalPages = Math.Min(MaximumPageCount, uncappedTotalPages);
        var accessibleItemCount = Math.Min(itemCount, MaximumPageCount * size);

        return new ScenePagingPlanResult(
            ScenePagingResultKind.Valid,
            new ScenePagingPlan(itemCount, pageSize, totalPages, accessibleItemCount),
            string.Empty);
    }

    public static ScenePageWindowResult GetWindow(
        int itemCount,
        ScenePageSize pageSize,
        int pageNumberOneBased)
    {
        var planResult = CreatePlan(itemCount, pageSize);
        if (!planResult.IsValid || planResult.Plan is null)
        {
            return new ScenePageWindowResult(
                ScenePagingResultKind.Invalid,
                null,
                null,
                planResult.Error);
        }

        var plan = planResult.Plan;
        if (pageNumberOneBased < 1 || pageNumberOneBased > plan.TotalPages)
        {
            return new ScenePageWindowResult(
                ScenePagingResultKind.Invalid,
                plan,
                null,
                "PageNumberOneBased is outside the available page range.");
        }

        var size = (int)plan.PageSize;
        var offset = (pageNumberOneBased - 1) * size;
        var count = Math.Min(size, plan.AccessibleItemCount - offset);
        var remaining = plan.AccessibleItemCount - offset - count;
        var window = new ScenePageWindow(
            pageNumberOneBased,
            plan.TotalPages,
            offset,
            count,
            remaining);

        return new ScenePageWindowResult(
            ScenePagingResultKind.Valid,
            plan,
            window,
            string.Empty);
    }

    public static SceneNextDecision DecideNext(SceneNextState? state)
    {
        if (state?.Playlist is null)
        {
            return InvalidDecision("Playlist state is required.");
        }

        var playlist = state.Playlist;
        if (state.CurrentCueIndexZeroBased < 0 ||
            state.CurrentCueIndexZeroBased >= playlist.Count)
        {
            return InvalidDecision("CurrentCueIndexZeroBased is outside the playlist.");
        }

        var plans = new ScenePagingPlan?[playlist.Count];
        for (var index = 0; index < playlist.Count; index++)
        {
            var cue = playlist[index];
            if (cue is null || string.IsNullOrWhiteSpace(cue.CueCode))
            {
                return InvalidDecision("Every playlist cue requires a cue code.");
            }

            if (cue.PageSize is null)
            {
                if (cue.ItemCount != 0)
                {
                    return InvalidDecision("A non-paged cue must have ItemCount zero.");
                }

                continue;
            }

            var planResult = CreatePlan(cue.ItemCount, cue.PageSize.Value);
            if (!planResult.IsValid || planResult.Plan is null)
            {
                return InvalidDecision(planResult.Error);
            }

            plans[index] = planResult.Plan;
        }

        var currentIndex = state.CurrentCueIndexZeroBased;
        var currentCue = playlist[currentIndex];
        var currentPlan = plans[currentIndex];

        if (currentPlan is null)
        {
            if (state.CurrentPageNumberOneBased is not null)
            {
                return InvalidDecision("A non-paged cue cannot have a current page.");
            }
        }
        else if (currentPlan.TotalPages == 0)
        {
            if (state.CurrentPageNumberOneBased is not null)
            {
                return InvalidDecision("A paged cue with no items cannot have a current page.");
            }
        }
        else
        {
            var currentPage = state.CurrentPageNumberOneBased;
            if (currentPage is null ||
                currentPage.Value < 1 ||
                currentPage.Value > currentPlan.TotalPages)
            {
                return InvalidDecision("CurrentPageNumberOneBased is outside the current cue.");
            }

            if (currentPage.Value < currentPlan.TotalPages)
            {
                var nextPage = currentPage.Value + 1;
                var pageResult = GetWindow(
                    currentCue.ItemCount,
                    currentCue.PageSize!.Value,
                    nextPage);
                if (!pageResult.IsValid || pageResult.Window is null)
                {
                    return InvalidDecision(pageResult.Error);
                }

                return new SceneNextDecision(
                    SceneNextDecisionKind.SameCueNextPage,
                    currentIndex,
                    nextPage,
                    pageResult.Window,
                    string.Empty);
            }
        }

        for (var index = currentIndex + 1; index < playlist.Count; index++)
        {
            if (!playlist[index].IsEnabled)
            {
                continue;
            }

            var targetPlan = plans[index];
            int? targetPageNumber = targetPlan?.TotalPages > 0 ? 1 : null;
            ScenePageWindow? targetPage = null;
            if (targetPageNumber is not null)
            {
                var pageResult = GetWindow(
                    playlist[index].ItemCount,
                    playlist[index].PageSize!.Value,
                    targetPageNumber.Value);
                if (!pageResult.IsValid || pageResult.Window is null)
                {
                    return InvalidDecision(pageResult.Error);
                }

                targetPage = pageResult.Window;
            }

            return new SceneNextDecision(
                SceneNextDecisionKind.NextEnabledCue,
                index,
                targetPageNumber,
                targetPage,
                string.Empty);
        }

        return new SceneNextDecision(
            SceneNextDecisionKind.EndOfPlaylist,
            null,
            null,
            null,
            string.Empty);
    }

    private static bool IsSupported(ScenePageSize pageSize) =>
        pageSize is ScenePageSize.Five or ScenePageSize.Six or ScenePageSize.Twelve;

    private static ScenePagingPlanResult InvalidPlan(string error) =>
        new(ScenePagingResultKind.Invalid, null, error);

    private static SceneNextDecision InvalidDecision(string error) =>
        new(SceneNextDecisionKind.Invalid, null, null, null, error);
}
