using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

namespace MBN_STOCK_WEBVIEW.LegacyApplication;

public sealed record LegacyCutCatalogRow(
    int PhysicalIndex,
    int? DisplayOrdinal,
    string RawLabel,
    bool IsSeparator);

public sealed record LegacyOperatorPlaylistRow(
    string RowId,
    bool IsEnabled,
    string MarketText,
    string StockName,
    string GraphicType,
    string Subtype,
    string PageText,
    string StockCode,
    string RawCutLabel,
    int FadeDuration,
    bool IsSelected = false,
    string SceneAlias = "",
    LegacySceneSelection? PlayoutSelection = null,
    bool IsActive = false)
{
    public bool IsSceneResolved => !string.Equals(
        SceneAlias,
        LegacyPlaylistEntry.UnresolvedCutCode,
        StringComparison.Ordinal);

    public LegacyPlaylistEntry ToPlayoutEntry()
    {
        var cutCode = SceneAlias.Length > 0
            ? SceneAlias
            : LegacyStockCutCatalog.ResolveCutAlias(
                RawCutLabel,
                StockName.Contains("(NXT)", StringComparison.Ordinal));
        return new LegacyPlaylistEntry(
            RowId,
            cutCode,
            IsEnabled,
            FadeDuration,
            PlayoutSelection ?? new LegacySceneSelection(
                MarketText,
                StockName,
                GraphicType,
                Subtype,
                StockCode),
            CreatePageNavigation());
    }

    private LegacyPlaylistPageNavigation? CreatePageNavigation()
    {
        var pageSize = GraphicType switch
        {
            "5단 표그래프" => ScenePageSize.Five,
            "6종목 현재가" => ScenePageSize.Six,
            "12종목 현재가" => ScenePageSize.Twelve,
            _ => (ScenePageSize?)null
        };
        if (!pageSize.HasValue)
        {
            // MainForm's PageN branches compared the visible FarPoint column text
            // exactly. A scene alias that happens to resolve to a paged builder is still
            // an ordinary playlist row when that column uses any other spelling.
            return LegacyPlaylistPageNavigation.Disabled;
        }

        var separator = PageText.IndexOf('/');
        if (separator <= 0 || separator != PageText.LastIndexOf('/') ||
            !int.TryParse(
                PageText.AsSpan(0, separator),
                System.Globalization.NumberStyles.None,
                System.Globalization.CultureInfo.InvariantCulture,
                out var currentPage) ||
            !int.TryParse(
                PageText.AsSpan(separator + 1),
                System.Globalization.NumberStyles.None,
                System.Globalization.CultureInfo.InvariantCulture,
                out var totalPages) ||
            totalPages is < 1 or > ScenePaging.MaximumPageCount ||
            currentPage < 1 || currentPage > totalPages)
        {
            // MainForm persisted the visible PageN text verbatim, including 1/0 when
            // the backing query returned no rows. Loading that historical value must
            // not reject the complete named playlist. A null navigation contract keeps
            // the row lossless and lets the scene provider derive current paging if the
            // operator later selects this row for PREPARE/TAKE IN. Empty or otherwise
            // invalid current data is then rejected for this row only, before dispatch.
            return null;
        }

        return new LegacyPlaylistPageNavigation(pageSize, totalPages);
    }
}

public static class LegacyStockCutCatalog
{
    public const string RuntimeAssetSha256 =
        "45DFB1804828F0E4A45F73D681AF0709CE5662872FAA49CFC9F7A0B940409E20";
    public const string NxtAllowedLabel = "1열판기본_현재가";

    private static readonly string?[] RuntimeRows =
    [
        "1열판기본_예상체결가",
        "1열판기본_현재가",
        "1열판기본_시간외단일가",
        null,
        "1열판상세_시가",
        "1열판상세_액면가",
        "1열판상세_PBR",
        "1열판상세_거래량",
        null,
        "수익률그래프_5일",
        "수익률그래프_20일",
        "수익률그래프_60일",
        "수익률그래프_120일",
        "수익률그래프_240일",
        "캔들그래프_일봉",
        "캔들그래프_5일",
        "캔들그래프_20일",
        "캔들그래프_60일",
        "캔들그래프_120일",
        "캔들그래프_240일",
        "캔들그래프(거래량)_일봉",
        "캔들그래프(거래량)_5일",
        "캔들그래프(거래량)_20일",
        "캔들그래프(거래량)_60일",
        "캔들그래프(거래량)_120일",
        "캔들그래프(거래량)_240일",
        "캔들그래프(예상체결가)_5일",
        "캔들그래프(예상체결가)_20일",
        "캔들그래프(예상체결가)_60일",
        "캔들그래프(예상체결가)_120일",
        "캔들그래프(예상체결가)_240일",
        "호가창_표그래프",
        "거래원_표그래프"
    ];

    public static IReadOnlyList<LegacyCutCatalogRow> Rows { get; } = CreateRows();

    public static string ResolveCutAlias(string rawLabel, bool isNxt)
    {
        ArgumentException.ThrowIfNullOrEmpty(rawLabel);
        if (isNxt)
        {
            if (!string.Equals(rawLabel, NxtAllowedLabel, StringComparison.Ordinal))
            {
                throw new InvalidOperationException("NXT는 1열판기본_현재가만 지원합니다.");
            }

            return "N5001";
        }

        if (rawLabel.StartsWith("1열판기본_", StringComparison.Ordinal))
        {
            return "5001";
        }

        if (string.Equals(rawLabel, "1열판상세_시가", StringComparison.Ordinal))
        {
            return "5006";
        }

        if (rawLabel.StartsWith("1열판상세_", StringComparison.Ordinal))
        {
            return "5011";
        }

        if (rawLabel.StartsWith("수익률그래프_", StringComparison.Ordinal))
        {
            return "5086";
        }

        if (rawLabel.StartsWith("캔들그래프", StringComparison.Ordinal))
        {
            var split = rawLabel.LastIndexOf('_');
            var period = split >= 0 ? rawLabel[(split + 1)..] : string.Empty;
            return period switch
            {
                "일봉" => "8035",
                "5일" => "8061",
                "20일" => "8040",
                "60일" => "8046",
                "120일" => "8051",
                "240일" => "8056",
                _ => throw UnknownCut(rawLabel)
            };
        }

        return rawLabel switch
        {
            "호가창_표그래프" => "8003",
            "거래원_표그래프" => "5037",
            _ => throw UnknownCut(rawLabel)
        };
    }

    public static LegacyOperatorPlaylistRow CreatePlaylistRow(
        string rowId,
        string stockName,
        LegacyStockIdentity? identity,
        LegacyCutCatalogRow cut,
        int fadeDuration = 6)
    {
        ArgumentException.ThrowIfNullOrEmpty(rowId);
        ArgumentNullException.ThrowIfNull(stockName);
        ArgumentNullException.ThrowIfNull(cut);
        if (cut.IsSeparator || string.IsNullOrEmpty(cut.RawLabel))
        {
            throw new ArgumentException("A separator cannot become a playlist row.", nameof(cut));
        }

        var split = cut.RawLabel.IndexOf('_');
        if (split <= 0 || split == cut.RawLabel.Length - 1)
        {
            throw UnknownCut(cut.RawLabel);
        }

        var marketText = identity?.MarketText ?? string.Empty;
        var graphicType = cut.RawLabel[..split];
        var subtype = cut.RawLabel[(split + 1)..];
        var stockCode = identity?.StockCode ?? string.Empty;
        var runtimeDetailBranch = cut.RawLabel switch
        {
            "1열판상세_액면가" => nameof(S5011DetailBranch.FaceValue),
            "1열판상세_PBR" => nameof(S5011DetailBranch.Valuation),
            "1열판상세_거래량" => nameof(S5011DetailBranch.Volume),
            _ => null
        };
        var playoutSelection = runtimeDetailBranch is null
            ? null
            : new LegacySceneSelection(
                marketText,
                stockName,
                graphicType,
                runtimeDetailBranch,
                stockCode);

        return new LegacyOperatorPlaylistRow(
            rowId,
            true,
            marketText,
            stockName,
            graphicType,
            subtype,
            "1/1",
            stockCode,
            cut.RawLabel,
            fadeDuration,
            PlayoutSelection: playoutSelection);
    }

    private static IReadOnlyList<LegacyCutCatalogRow> CreateRows()
    {
        var displayOrdinal = 0;
        var rows = new LegacyCutCatalogRow[RuntimeRows.Length];
        for (var index = 0; index < RuntimeRows.Length; index++)
        {
            var label = RuntimeRows[index];
            var separator = label is null;
            rows[index] = new LegacyCutCatalogRow(
                index,
                separator ? null : ++displayOrdinal,
                label ?? string.Empty,
                separator);
        }

        return Array.AsReadOnly(rows);
    }

    private static InvalidOperationException UnknownCut(string label) =>
        new($"The legacy stock cut is unknown: {label}");
}
