#nullable enable

using System.Globalization;

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

public enum PagedQuoteBranch
{
    Domestic,
    Nxt,
    Theme,
    NxtTheme,
    Expert,
    Vi
}

public enum PagedQuoteHeaderStyle
{
    StandardWon,
    IndustryPoints,
    ExpertReturns
}

public enum PagedQuoteTitleStyle
{
    Exact,
    ExpectedExecution
}

public enum NxtMarketSession
{
    NotApplicable,
    PreMarket,
    AfterMarket
}

public sealed record PagedQuoteRowData(
    string Name,
    decimal PrimaryValue,
    decimal SecondaryValue,
    double Rate,
    ScenePriceDirection Direction);

/// <summary>
/// Trusted page input shared by the legacy 5-, 6-, and 12-row quote scenes.
/// PageNumberOneBased follows ScenePaging; Rows always contains the complete logical result.
/// </summary>
public sealed record PagedQuoteSceneData(
    PagedQuoteBranch Branch,
    PagedQuoteHeaderStyle HeaderStyle,
    PagedQuoteTitleStyle TitleStyle,
    NxtMarketSession NxtSession,
    string Title,
    IReadOnlyList<PagedQuoteRowData> Rows,
    int PageNumberOneBased) : ILegacySceneData;

public sealed class S5074SceneMutationBuilder : ILegacySceneMutationBuilder<PagedQuoteSceneData>
{
    public string BuilderKey => "s5074";

    public IReadOnlyList<PlayoutMutation> Build(PagedQuoteSceneData data) =>
        PagedQuoteMutations.Build(
            data,
            ScenePageSize.Five,
            useWideRateVariants: false,
            includeTitle: true,
            includeUnit: true,
            includeColumns: true);
}

public sealed class S5077SceneMutationBuilder : ILegacySceneMutationBuilder<PagedQuoteSceneData>
{
    public string BuilderKey => "s5077";

    public IReadOnlyList<PlayoutMutation> Build(PagedQuoteSceneData data) =>
        PagedQuoteMutations.Build(
            data,
            ScenePageSize.Six,
            useWideRateVariants: true,
            includeTitle: false,
            includeUnit: false,
            includeColumns: false);
}

public sealed class S5088SceneMutationBuilder : ILegacySceneMutationBuilder<PagedQuoteSceneData>
{
    public string BuilderKey => "s5088";

    public IReadOnlyList<PlayoutMutation> Build(PagedQuoteSceneData data) =>
        PagedQuoteMutations.Build(
            data,
            ScenePageSize.Twelve,
            useWideRateVariants: true,
            includeTitle: true,
            includeUnit: true,
            includeColumns: false);
}

internal static class PagedQuoteMutations
{
    private static readonly string[] DirectionNames =
        ["upup", "up", "flat", "down", "downdown"];

    public static IReadOnlyList<PlayoutMutation> Build(
        PagedQuoteSceneData data,
        ScenePageSize pageSize,
        bool useWideRateVariants,
        bool includeTitle,
        bool includeUnit,
        bool includeColumns)
    {
        ArgumentNullException.ThrowIfNull(data);
        var title = LegacySceneBuilderGuard.Text(data.Title, nameof(data.Title));
        ValidatePresentation(data);
        var rows = LegacySceneBuilderGuard.NotNull(data.Rows, nameof(data.Rows));
        var windowResult = ScenePaging.GetWindow(
            rows.Count,
            pageSize,
            data.PageNumberOneBased);
        if (!windowResult.IsValid || windowResult.Window is null)
        {
            throw new LegacySceneDataException(
                "The requested quote page is outside the accessible 20-page range.");
        }

        var window = windowResult.Window;
        var selectedRows = new PagedQuoteRowData[window.ItemCount];
        for (var index = 0; index < window.ItemCount; index++)
        {
            var sourceIndex = window.OffsetZeroBased + index;
            var row = LegacySceneBuilderGuard.NotNull(
                rows[sourceIndex],
                $"Rows[{sourceIndex}]");
            if (!double.IsFinite(row.Rate))
            {
                throw new LegacySceneDataException(
                    $"Scene data field 'Rows[{sourceIndex}].Rate' must be finite.");
            }

            if (data.HeaderStyle != PagedQuoteHeaderStyle.IndustryPoints &&
                (decimal.Truncate(row.PrimaryValue) != row.PrimaryValue ||
                 decimal.Truncate(row.SecondaryValue) != row.SecondaryValue))
            {
                throw new LegacySceneDataException(
                    $"Scene data field 'Rows[{sourceIndex}]' requires whole-won values.");
            }

            selectedRows[index] = row with
            {
                Name = LegacySceneBuilderGuard.Text(
                    row.Name,
                    $"Rows[{sourceIndex}].Name")
            };
        }

        var size = (int)pageSize;
        var estimatedPerRow = useWideRateVariants ? 27 : 19;
        var mutations = new List<PlayoutMutation>(14 + (size * estimatedPerRow))
        {
            // Every legacy constructor hides the market mark before entering its branch.
            new PlayoutSetVisible("mart", false)
        };
        for (var slot = 1; slot <= size; slot++)
        {
            mutations.Add(new PlayoutSetVisible("row" + slot, false));
        }

        // The authoritative 5077.t2s has no global title/header objects, while
        // 5088.t2s has title/unit but no column1..3. Keep the shared row logic,
        // but never send mutations for objects absent from the selected asset.
        if (includeTitle)
        {
            mutations.Add(new PlayoutSetValue("title", BuildTitle(title, data.TitleStyle)));
        }

        AddMarketMark(mutations, data.Branch, data.NxtSession);
        AddHeader(mutations, data.HeaderStyle, includeUnit, includeColumns);

        for (var slot = 1; slot <= size; slot++)
        {
            if (slot <= selectedRows.Length)
            {
                AddPopulatedRow(
                    mutations,
                    slot,
                    selectedRows[slot - 1],
                    useWideRateVariants,
                    usePointValues: data.HeaderStyle == PagedQuoteHeaderStyle.IndustryPoints);
            }
            else
            {
                AddClearedRow(mutations, slot, useWideRateVariants);
            }
        }

        return mutations;
    }

    private static void ValidatePresentation(PagedQuoteSceneData data)
    {
        if (!Enum.IsDefined(data.Branch) || !Enum.IsDefined(data.HeaderStyle) ||
            !Enum.IsDefined(data.TitleStyle) || !Enum.IsDefined(data.NxtSession))
        {
            throw new LegacySceneDataException("Unknown paged quote presentation value.");
        }

        var isNxt = data.Branch is PagedQuoteBranch.Nxt or PagedQuoteBranch.NxtTheme;
        if (isNxt != (data.NxtSession != NxtMarketSession.NotApplicable))
        {
            throw new LegacySceneDataException(
                "Only NXT branches can select a pre-market or after-market mark.");
        }

        if ((data.Branch == PagedQuoteBranch.Expert) !=
            (data.HeaderStyle == PagedQuoteHeaderStyle.ExpertReturns))
        {
            throw new LegacySceneDataException(
                "The expert branch requires the expert return columns.");
        }

        if (data.HeaderStyle == PagedQuoteHeaderStyle.IndustryPoints &&
            data.Branch != PagedQuoteBranch.Domestic)
        {
            throw new LegacySceneDataException(
                "Industry point columns are available only to the domestic branch.");
        }
    }

    private static string BuildTitle(string title, PagedQuoteTitleStyle style) => style switch
    {
        PagedQuoteTitleStyle.Exact => title,
        PagedQuoteTitleStyle.ExpectedExecution => title + " \uC608\uC0C1 \uCCB4\uACB0\uAC00",
        _ => throw new LegacySceneDataException("Unknown paged quote title style.")
    };

    private static void AddMarketMark(
        ICollection<PlayoutMutation> mutations,
        PagedQuoteBranch branch,
        NxtMarketSession session)
    {
        if (branch is not (PagedQuoteBranch.Nxt or PagedQuoteBranch.NxtTheme))
        {
            mutations.Add(new PlayoutSetVisible("mart", false));
            return;
        }

        var relativeAsset = session switch
        {
            NxtMarketSession.PreMarket => "Images\\\uD504\uB9AC\uB9C8\uCF13.png",
            NxtMarketSession.AfterMarket => "Images\\\uC560\uD504\uD130\uB9C8\uCF13.png",
            _ => throw new LegacySceneDataException("An NXT market session is required.")
        };
        mutations.Add(new PlayoutSetAssetValue("mart", relativeAsset));
        mutations.Add(new PlayoutSetVisible("mart", true));
    }

    private static void AddHeader(
        ICollection<PlayoutMutation> mutations,
        PagedQuoteHeaderStyle style,
        bool includeUnit,
        bool includeColumns)
    {
        var values = style switch
        {
            PagedQuoteHeaderStyle.StandardWon =>
                (Unit: "\uB2E8\uC704 : \uC6D0", Column1: "\uD604\uC7AC\uAC00", Column2: "\uB4F1\uB77D\uAC00", Column3: "\uB4F1\uB77D\uB960"),
            PagedQuoteHeaderStyle.IndustryPoints =>
                (Unit: "\uB2E8\uC704 : p", Column1: "\uD604\uC7AC", Column2: "\uB4F1\uB77D", Column3: "\uB4F1\uB77D\uB960"),
            PagedQuoteHeaderStyle.ExpertReturns =>
                (Unit: "\uB2E8\uC704 : \uC6D0", Column1: "\uB9E4\uC218\uAC00", Column2: "\uD604\uC7AC\uAC00", Column3: "\uC218\uC775\uB960"),
            _ => throw new LegacySceneDataException("Unknown paged quote header style.")
        };
        if (includeUnit)
        {
            mutations.Add(new PlayoutSetValue("unit", values.Unit));
        }

        if (includeColumns)
        {
            mutations.Add(new PlayoutSetValue("column1", values.Column1));
            mutations.Add(new PlayoutSetValue("column2", values.Column2));
            mutations.Add(new PlayoutSetValue("column3", values.Column3));
        }
    }

    private static void AddPopulatedRow(
        ICollection<PlayoutMutation> mutations,
        int slot,
        PagedQuoteRowData row,
        bool useWideRateVariants,
        bool usePointValues)
    {
        mutations.Add(new PlayoutSetVisible("row" + slot, true));
        mutations.Add(new PlayoutSetValue("title" + slot, row.Name));
        mutations.Add(new PlayoutSetValue(
            "price" + slot,
            FormatNumber(row.PrimaryValue, usePointValues)));
        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetValue(
                $"changePrice{slot}_{variant}",
                FormatNumber(row.SecondaryValue, usePointValues)));
        }

        var rate = row.Rate.ToString("##0.00", CultureInfo.InvariantCulture);
        if (useWideRateVariants)
        {
            for (var variant = 1; variant <= 3; variant++)
            {
                mutations.Add(new PlayoutSetValue($"rate{slot}_{variant}", rate));
            }
        }
        else
        {
            mutations.Add(new PlayoutSetValue("rate" + slot, rate));
        }

        AddDirectionHidden(mutations, slot);
        var selectedVariant = row.Direction switch
        {
            ScenePriceDirection.LimitUp or ScenePriceDirection.Up => 1,
            ScenePriceDirection.Flat or ScenePriceDirection.Unknown => 2,
            ScenePriceDirection.Down or ScenePriceDirection.LimitDown => 3,
            _ => throw new LegacySceneDataException("Unknown price direction.")
        };
        AddVariantVisibility(mutations, slot, selectedVariant, useWideRateVariants);

        var activeDirection = row.Direction switch
        {
            ScenePriceDirection.LimitUp => "upup",
            ScenePriceDirection.Up => "up",
            ScenePriceDirection.Flat or ScenePriceDirection.Unknown => "flat",
            ScenePriceDirection.Down => "down",
            ScenePriceDirection.LimitDown => "downdown",
            _ => throw new LegacySceneDataException("Unknown price direction.")
        };
        mutations.Add(new PlayoutSetVisible(activeDirection + slot, true));
    }

    private static void AddClearedRow(
        ICollection<PlayoutMutation> mutations,
        int slot,
        bool useWideRateVariants)
    {
        // The original hides all row groups first. Explicit value/variant clearing is the
        // approved partial-page correction required for retained on-air scene updates.
        mutations.Add(new PlayoutSetVisible("row" + slot, false));
        mutations.Add(new PlayoutSetValue("title" + slot, string.Empty));
        mutations.Add(new PlayoutSetValue("price" + slot, string.Empty));
        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetValue($"changePrice{slot}_{variant}", string.Empty));
        }

        if (useWideRateVariants)
        {
            for (var variant = 1; variant <= 3; variant++)
            {
                mutations.Add(new PlayoutSetValue($"rate{slot}_{variant}", string.Empty));
            }
        }
        else
        {
            mutations.Add(new PlayoutSetValue("rate" + slot, string.Empty));
        }

        AddDirectionHidden(mutations, slot);
        AddVariantVisibility(mutations, slot, selectedVariant: 0, useWideRateVariants);
    }

    private static void AddDirectionHidden(
        ICollection<PlayoutMutation> mutations,
        int slot)
    {
        foreach (var name in DirectionNames)
        {
            mutations.Add(new PlayoutSetVisible(name + slot, false));
        }
    }

    private static void AddVariantVisibility(
        ICollection<PlayoutMutation> mutations,
        int slot,
        int selectedVariant,
        bool useWideRateVariants)
    {
        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetVisible(
                $"bg{slot}_{variant}",
                variant == selectedVariant));
        }

        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetVisible(
                $"changePrice{slot}_{variant}",
                variant == selectedVariant));
        }

        if (!useWideRateVariants)
        {
            return;
        }

        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetVisible(
                $"rate{slot}_{variant}",
                variant == selectedVariant));
        }

        for (var variant = 1; variant <= 3; variant++)
        {
            mutations.Add(new PlayoutSetVisible(
                $"percent{slot}_{variant}",
                variant == selectedVariant));
        }
    }

    private static string FormatNumber(decimal value, bool usePointValues) =>
        value.ToString(
            usePointValues ? "#,##0.00" : "#,##0",
            CultureInfo.InvariantCulture);
}
