#nullable enable

using System.Globalization;

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

public enum LegacyDomesticEquityMarket
{
    Kospi,
    Kosdaq,
    NxtKospi,
    NxtKosdaq
}

public sealed record S5006SceneData(
    LegacyDomesticEquityMarket Market,
    string StockName,
    ScenePriceDirection Direction,
    long CurrentPrice,
    long NetChange,
    decimal Rate,
    long OpenPrice,
    decimal OpenRate,
    long HighPrice,
    decimal HighRate,
    long LowPrice,
    decimal LowRate) : ILegacySceneData;

public sealed class S5006SceneMutationBuilder : ILegacySceneMutationBuilder<S5006SceneData>
{
    private const string BackgroundAsset = @"Video\큐브배경.vrv";

    public string BuilderKey => "s5006";

    public IReadOnlyList<PlayoutMutation> Build(S5006SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        ClosedSceneMappings.RequireDomesticMarket(data.Market);
        var stockName = LegacySceneBuilderGuard.Text(data.StockName, nameof(data.StockName));
        var direction = ClosedSceneMappings.RequireDirection(data.Direction);
        var variant = ClosedSceneMappings.DirectionValueVariant(direction);

        var mutations = new List<PlayoutMutation>(38)
        {
            new PlayoutSetValue("title", stockName),
            new PlayoutSetBackgroundVideo(
                BackgroundAsset,
                1,
                1,
                PlayoutMutationTiming.InTransaction),
            new PlayoutSetValue("price", FormatInteger(data.CurrentPrice)),
            new PlayoutSetValue("changePrice" + variant, FormatInteger(data.NetChange)),
            new PlayoutSetValue("rate" + variant, FormatRate(data.Rate)),
            new PlayoutSetValue("price1", FormatInteger(data.OpenPrice)),
            new PlayoutSetValue("ratio1", FormatRate(data.OpenRate)),
            new PlayoutSetValue("price2", FormatInteger(data.HighPrice)),
            new PlayoutSetValue("ratio2", FormatRate(data.HighRate)),
            new PlayoutSetValue("price3", FormatInteger(data.LowPrice)),
            new PlayoutSetValue("ratio3", FormatRate(data.LowRate))
        };

        ClosedSceneMappings.AddDirectionHides(mutations, string.Empty);
        mutations.AddRange(LegacyPriceDirectionMutations.Build(direction, 0));
        return mutations;
    }

    private static string FormatInteger(long value) =>
        value.ToString("#,##0", CultureInfo.InvariantCulture);

    private static string FormatRate(decimal value) =>
        value.ToString("##0.00", CultureInfo.InvariantCulture);
}

public enum S5011DetailBranch
{
    FaceValue,
    Valuation,
    Volume
}

public abstract record S5011DetailData
{
    private protected S5011DetailData()
    {
    }

    public abstract S5011DetailBranch Branch { get; }
}

public sealed record S5011FaceValueDetail(
    long ListedPrice,
    long CapitalPrice,
    long MarketCapitalization,
    long MarketRank) : S5011DetailData
{
    public override S5011DetailBranch Branch => S5011DetailBranch.FaceValue;
}

public sealed record S5011ValuationDetail(
    decimal Pbr,
    decimal Per,
    long Bps,
    long Eps) : S5011DetailData
{
    public override S5011DetailBranch Branch => S5011DetailBranch.Valuation;
}

public sealed record S5011VolumeDetail(
    long Volume,
    long Turnover,
    long FiveDayAverage,
    long TwentyDayAverage) : S5011DetailData
{
    public override S5011DetailBranch Branch => S5011DetailBranch.Volume;
}

public sealed record S5011SceneData(
    LegacyDomesticEquityMarket Market,
    string StockName,
    ScenePriceDirection Direction,
    long CurrentPrice,
    long NetChange,
    decimal Rate,
    S5011DetailData Detail) : ILegacySceneData;

public sealed class S5011SceneMutationBuilder : ILegacySceneMutationBuilder<S5011SceneData>
{
    public string BuilderKey => "s5011";

    public IReadOnlyList<PlayoutMutation> Build(S5011SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var market = ClosedSceneMappings.RequireDomesticMarket(data.Market);
        var rawName = LegacySceneBuilderGuard.Text(data.StockName, nameof(data.StockName));
        var stockName = LegacySceneBuilderGuard.Text(
            rawName.Replace("(NXT)", string.Empty, StringComparison.Ordinal),
            nameof(data.StockName));
        var direction = ClosedSceneMappings.RequireDirection(data.Direction);
        var detail = LegacySceneBuilderGuard.NotNull(data.Detail, nameof(data.Detail));
        var variant = ClosedSceneMappings.DirectionValueVariant(direction);
        var mutations = new List<PlayoutMutation>(44)
        {
            new PlayoutSetValue("title", stockName)
        };

        AddDetailMutations(mutations, detail);
        mutations.Add(new PlayoutSetValue("price", FormatInteger(data.CurrentPrice)));
        mutations.Add(new PlayoutSetValue("changePrice" + variant, FormatInteger(data.NetChange)));
        mutations.Add(new PlayoutSetValue("rate" + variant, FormatRate(data.Rate)));
        ClosedSceneMappings.AddDirectionHides(mutations, string.Empty);
        AddAssetSupportedDirectionMutations(mutations, direction);
        mutations.Add(new PlayoutSetVisible("mart", ClosedSceneMappings.IsNxt(market)));
        return mutations;
    }

    private static void AddAssetSupportedDirectionMutations(
        ICollection<PlayoutMutation> mutations,
        ScenePriceDirection direction)
    {
        foreach (var mutation in LegacyPriceDirectionMutations.Build(direction, 0))
        {
            // The authoritative 5011.t2s has the bg/changePrice/rate/percent
            // variants but no wonText variants or pattern asset object.
            if (mutation is PlayoutSetVisible { ObjectName: var objectName } &&
                objectName.StartsWith("wonText", StringComparison.Ordinal))
            {
                continue;
            }

            if (mutation is PlayoutSetAssetValue)
            {
                continue;
            }

            mutations.Add(mutation);
        }
    }

    private static void AddDetailMutations(
        ICollection<PlayoutMutation> mutations,
        S5011DetailData detail)
    {
        switch (detail)
        {
            case S5011FaceValueDetail faceValue:
                AddLabels(
                    mutations,
                    ["원", "억원", "억원", "위"],
                    ["액면가", "자본금", "시가총액", "시총순위"]);
                AddValues(
                    mutations,
                    [
                        FormatInteger(faceValue.ListedPrice),
                        FormatInteger(faceValue.CapitalPrice / 100_000_000L),
                        FormatInteger(faceValue.MarketCapitalization / 100_000_000L),
                        FormatInteger(faceValue.MarketRank)
                    ]);
                break;

            case S5011ValuationDetail valuation:
                AddLabels(
                    mutations,
                    ["배", "배", "원", "원"],
                    ["PBR", "PER", "BPS", "EPS"]);
                AddValues(
                    mutations,
                    [
                        valuation.Pbr.ToString(CultureInfo.InvariantCulture),
                        valuation.Per.ToString(CultureInfo.InvariantCulture),
                        FormatInteger(valuation.Bps),
                        FormatInteger(valuation.Eps)
                    ]);
                break;

            case S5011VolumeDetail volume:
                AddLabels(
                    mutations,
                    ["주", "백만", "원", "원"],
                    ["거래량", "거래대금", "5MA", "20MA"]);
                AddValues(
                    mutations,
                    [
                        FormatInteger(volume.Volume),
                        FormatInteger(volume.Turnover / 1_000_000L),
                        volume.FiveDayAverage.ToString("#,##0.###", CultureInfo.InvariantCulture),
                        volume.TwentyDayAverage.ToString("#,##0.###", CultureInfo.InvariantCulture)
                    ]);
                break;

            default:
                throw new LegacySceneDataException("Unknown s5011 detail branch.");
        }
    }

    private static void AddLabels(
        ICollection<PlayoutMutation> mutations,
        IReadOnlyList<string> units,
        IReadOnlyList<string> titles)
    {
        for (var index = 1; index <= 4; index++)
        {
            mutations.Add(new PlayoutSetValue("r_unit" + index, units[index - 1]));
        }

        for (var index = 1; index <= 4; index++)
        {
            mutations.Add(new PlayoutSetValue("r_title" + index, titles[index - 1]));
        }
    }

    private static void AddValues(
        ICollection<PlayoutMutation> mutations,
        IReadOnlyList<string> values)
    {
        for (var index = 1; index <= 4; index++)
        {
            mutations.Add(new PlayoutSetValue("r_value" + index, values[index - 1]));
        }
    }

    private static string FormatInteger(long value) =>
        value.ToString("#,##0", CultureInfo.InvariantCulture);

    private static string FormatRate(decimal value) =>
        value.ToString("##0.00", CultureInfo.InvariantCulture);
}

public enum LegacyPanelQuoteSignal
{
    Positive,
    Flat,
    Negative
}

public sealed record LegacyPanelQuoteData(
    string Name,
    string? PartName,
    decimal Price,
    decimal Change,
    decimal Rate,
    LegacyPanelQuoteSignal Signal);

public enum S5016PanelTarget
{
    UnitedStatesIndices,
    GreaterChinaIndices,
    EuropeanIndices,
    AsianIndices,
    UnitedStatesTreasuries,
    BondYields,
    Oil,
    Minerals,
    FoodMaterials,
    OverseasExchangeRates,
    DomesticExchangeRates,
    AgricultureWheatCorn,
    AgricultureSoyRice,
    AgricultureCoffeeCocoaSugar,
    AgricultureLivestock,
    RawMaterialsCopperIronNickel,
    RawMaterialsGasPlatinumPalladium,
    RawMaterialsLeadZincTin,
    MajorKospiKosdaqKospi200,
    MajorKospiKosdaqFutures,
    ExpectedKospiKosdaqKospi200,
    ExpectedKospiKosdaqFutures
}

public sealed record S5016SceneData(
    S5016PanelTarget Target,
    IReadOnlyList<LegacyPanelQuoteData> Quotes) : ILegacySceneData;

public sealed class S5016SceneMutationBuilder : ILegacySceneMutationBuilder<S5016SceneData>
{
    public string BuilderKey => "s5016";

    public IReadOnlyList<PlayoutMutation> Build(S5016SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var metadata = Metadata(data.Target);
        var quotes = LegacySceneBuilderGuard.Count(data.Quotes, 3, nameof(data.Quotes));
        var mutations = new List<PlayoutMutation>(79)
        {
            new PlayoutSetValue("title", metadata.Title)
        };

        for (var index = 1; index <= 3; index++)
        {
            var quote = LegacySceneBuilderGuard.NotNull(quotes[index - 1], $"Quotes[{index - 1}]");
            var name = metadata.UsePartName
                ? LegacySceneBuilderGuard.Text(quote.PartName, $"Quotes[{index - 1}].PartName")
                : LegacySceneBuilderGuard.Text(quote.Name, $"Quotes[{index - 1}].Name")
                    .Replace("KOSPI", "코스피", StringComparison.Ordinal)
                    .Replace("KOSQ", "코스닥", StringComparison.Ordinal);
            LegacyPanelQuoteMutations.AddQuote(
                mutations,
                index,
                name,
                quote,
                metadata.ChangeDecimalPlaces);
            mutations.Add(new PlayoutSetValue("unitText", metadata.UnitText));
        }

        return mutations;
    }

    private static S5016Metadata Metadata(S5016PanelTarget target) => target switch
    {
        S5016PanelTarget.UnitedStatesIndices => new("미국 지수", "단위 : p"),
        S5016PanelTarget.GreaterChinaIndices => new("중화권 지수", "단위 : p"),
        S5016PanelTarget.EuropeanIndices => new("유럽 지수", "단위 : p"),
        S5016PanelTarget.AsianIndices => new("아시아 지수", "단위 : p"),
        S5016PanelTarget.UnitedStatesTreasuries => new("미국 국채", "단위 : %"),
        S5016PanelTarget.BondYields => new("채권 금리", "단위 : %", UsePartName: true),
        S5016PanelTarget.Oil => new("유가", "단위 : 배럴/달러"),
        S5016PanelTarget.Minerals => new("광물", "단위 : $"),
        S5016PanelTarget.FoodMaterials => new("식자재", "단위 : ￠"),
        S5016PanelTarget.OverseasExchangeRates => new(
            "해외 환율",
            "단위 : $",
            ChangeDecimalPlaces: 4),
        S5016PanelTarget.DomesticExchangeRates => new("환율", "단위 : 원"),
        S5016PanelTarget.AgricultureWheatCorn or
        S5016PanelTarget.AgricultureSoyRice or
        S5016PanelTarget.AgricultureCoffeeCocoaSugar or
        S5016PanelTarget.AgricultureLivestock => new("농산물", "단위 : $"),
        S5016PanelTarget.RawMaterialsCopperIronNickel or
        S5016PanelTarget.RawMaterialsGasPlatinumPalladium or
        S5016PanelTarget.RawMaterialsLeadZincTin => new("원자재", "단위 : $"),
        S5016PanelTarget.MajorKospiKosdaqKospi200 or
        S5016PanelTarget.MajorKospiKosdaqFutures => new("주요지수", "단위 : p"),
        S5016PanelTarget.ExpectedKospiKosdaqKospi200 or
        S5016PanelTarget.ExpectedKospiKosdaqFutures => new("예상체결지수", "단위 : p"),
        _ => throw new LegacySceneDataException("Unknown s5016 panel target.")
    };

    private readonly record struct S5016Metadata(
        string Title,
        string UnitText,
        bool UsePartName = false,
        int ChangeDecimalPlaces = 2);
}

public enum S50160PanelTarget
{
    Cotton,
    InternationalPreciousMetals,
    DomesticPreciousMetals
}

public sealed record S50160SceneData(
    S50160PanelTarget Target,
    IReadOnlyList<LegacyPanelQuoteData> Quotes) : ILegacySceneData;

public sealed class S50160SceneMutationBuilder : ILegacySceneMutationBuilder<S50160SceneData>
{
    public string BuilderKey => "s50160";

    public IReadOnlyList<PlayoutMutation> Build(S50160SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var metadata = Metadata(data.Target);
        var quotes = LegacySceneBuilderGuard.Count(data.Quotes, 2, nameof(data.Quotes));
        var mutations = new List<PlayoutMutation>(59)
        {
            new PlayoutSetValue("title", metadata.Title)
        };

        for (var index = 1; index <= 2; index++)
        {
            var quote = LegacySceneBuilderGuard.NotNull(quotes[index - 1], $"Quotes[{index - 1}]");
            var name = LegacySceneBuilderGuard.Text(quote.Name, $"Quotes[{index - 1}].Name")
                .Replace("KOSPI", "코스피", StringComparison.Ordinal)
                .Replace("KOSQ", "코스닥", StringComparison.Ordinal);
            LegacyPanelQuoteMutations.AddQuote(mutations, index, name, quote, 2);
            mutations.Add(new PlayoutSetValue("unit" + index + "_1", string.Empty));
            mutations.Add(new PlayoutSetValue("unit" + index + "_2", string.Empty));
            mutations.Add(new PlayoutSetValue("unit" + index + "_3", string.Empty));
            mutations.Add(new PlayoutSetValue("unitText", metadata.UnitText));
        }

        return mutations;
    }

    private static S50160Metadata Metadata(S50160PanelTarget target) => target switch
    {
        S50160PanelTarget.Cotton => new("농산물", "단위 : $"),
        S50160PanelTarget.InternationalPreciousMetals => new("원자재", "단위 : $"),
        S50160PanelTarget.DomesticPreciousMetals => new("원자재", "단위 : 원"),
        _ => throw new LegacySceneDataException("Unknown s50160 panel target.")
    };

    private readonly record struct S50160Metadata(string Title, string UnitText);
}

public enum S6001QuoteTarget
{
    DowJones,
    Nasdaq,
    StandardAndPoor500,
    Germany,
    UnitedKingdom,
    France,
    Japan,
    China,
    HongKong,
    Taiwan,
    Singapore,
    Thailand,
    Philippines,
    Malaysia,
    Indonesia,
    DubaiCrude,
    BrentCrude,
    WtiCrude,
    Gold
}

public sealed record S6001SceneData(
    S6001QuoteTarget Target,
    string Title,
    decimal Price,
    decimal Change,
    decimal Rate,
    ScenePriceDirection Direction) : ILegacySceneData;

public sealed class S6001SceneMutationBuilder : ILegacySceneMutationBuilder<S6001SceneData>
{
    public string BuilderKey => "s6001";

    public IReadOnlyList<PlayoutMutation> Build(S6001SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var metadata = Metadata(data.Target);
        var title = LegacySceneBuilderGuard.Text(data.Title, nameof(data.Title));
        var direction = ClosedSceneMappings.RequireDirection(data.Direction);
        if (data.Change == decimal.MinValue || data.Rate == decimal.MinValue)
        {
            throw new LegacySceneDataException("s6001 quote values are outside the supported range.");
        }

        var mutations = new List<PlayoutMutation>(19);
        if (metadata.IsIndex)
        {
            mutations.Add(new PlayoutSetValue("title", title));
            mutations.Add(new PlayoutSetAssetValue("movie", metadata.AssetPath));
            mutations.Add(new PlayoutSetValue("price", FormatDecimal(data.Price)));
            mutations.Add(new PlayoutSetValue("changePrice", FormatDecimal(Math.Abs(data.Change))));
            var signedRate = direction is ScenePriceDirection.Down or ScenePriceDirection.LimitDown
                ? -Math.Abs(data.Rate)
                : data.Rate;
            mutations.Add(new PlayoutSetValue("rate", FormatDecimal(signedRate)));
            mutations.Add(new PlayoutSetValue("wonText", string.Empty));
            mutations.Add(new PlayoutSetValue("unit", metadata.UnitText));
        }
        else
        {
            mutations.Add(new PlayoutSetAssetValue("movie", metadata.AssetPath));
            mutations.Add(new PlayoutSetValue("unit", metadata.UnitText));
            mutations.Add(new PlayoutSetValue("title", title));
            mutations.Add(new PlayoutSetValue("price", FormatDecimal(data.Price)));
            mutations.Add(new PlayoutSetValue("changePrice", FormatDecimal(data.Change)));
            mutations.Add(new PlayoutSetValue("rate", FormatDecimal(data.Rate)));
            mutations.Add(new PlayoutSetValue("wonText", string.Empty));
        }

        S6001DirectionMutations.Add(mutations, direction);
        return mutations;
    }

    private static S6001Metadata Metadata(S6001QuoteTarget target) => target switch
    {
        S6001QuoteTarget.DowJones or
        S6001QuoteTarget.Nasdaq or
        S6001QuoteTarget.StandardAndPoor500 => Index("미국"),
        S6001QuoteTarget.Germany => Index("독일"),
        S6001QuoteTarget.UnitedKingdom => Index("영국"),
        S6001QuoteTarget.France => Index("프랑스"),
        S6001QuoteTarget.Japan => Index("일본"),
        S6001QuoteTarget.China => Index("중국"),
        S6001QuoteTarget.HongKong => Index("홍콩"),
        S6001QuoteTarget.Taiwan => Index("대만"),
        S6001QuoteTarget.Singapore => Index("싱가포르"),
        S6001QuoteTarget.Thailand => Index("태국"),
        S6001QuoteTarget.Philippines => Index("필리핀"),
        S6001QuoteTarget.Malaysia => Index("말레이시아"),
        S6001QuoteTarget.Indonesia => Index("인도네시아"),
        S6001QuoteTarget.DubaiCrude or
        S6001QuoteTarget.BrentCrude or
        S6001QuoteTarget.WtiCrude => new(@"images\주유기merge.png", "단위 : 배럴/달러", false),
        S6001QuoteTarget.Gold => new(@"images\35752913_l.jpg", "단위 : 달러/온스", false),
        _ => throw new LegacySceneDataException("Unknown s6001 quote target.")
    };

    private static S6001Metadata Index(string nation) =>
        new(@"Video\20201008_" + nation + ".vrv", "단위 : p", true);

    private static string FormatDecimal(decimal value) =>
        value.ToString("#,##0.00", CultureInfo.InvariantCulture);

    private readonly record struct S6001Metadata(
        string AssetPath,
        string UnitText,
        bool IsIndex);
}

internal static class LegacyPanelQuoteMutations
{
    private static readonly string[] VariantFamilies =
        ["bg", "rate", "changePrice", "percent", "unit"];

    public static void AddQuote(
        ICollection<PlayoutMutation> mutations,
        int index,
        string name,
        LegacyPanelQuoteData quote,
        int changeDecimalPlaces)
    {
        if (quote.Change == decimal.MinValue)
        {
            throw new LegacySceneDataException("Panel quote change is outside the supported range.");
        }

        var variant = quote.Signal switch
        {
            LegacyPanelQuoteSignal.Positive => 1,
            LegacyPanelQuoteSignal.Flat => 2,
            LegacyPanelQuoteSignal.Negative when quote.Change == 0 => 2,
            LegacyPanelQuoteSignal.Negative => 3,
            _ => throw new LegacySceneDataException("Unknown panel quote signal.")
        };

        ClosedSceneMappings.AddPanelDirectionHides(
            mutations,
            index.ToString(CultureInfo.InvariantCulture));
        mutations.Add(new PlayoutSetValue("title" + index, name));
        mutations.Add(new PlayoutSetValue(
            "price" + index,
            quote.Price.ToString("#,##0.00", CultureInfo.InvariantCulture)));

        foreach (var family in VariantFamilies)
        {
            for (var visual = 1; visual <= 3; visual++)
            {
                mutations.Add(new PlayoutSetVisible(
                    family + index + "_" + visual,
                    visual == variant));
            }
        }

        var directionObject = variant switch
        {
            1 => "up",
            2 => "flat",
            3 => "down",
            _ => throw new InvalidOperationException("A known panel quote variant was expected.")
        };
        mutations.Add(new PlayoutSetVisible(directionObject + index, true));

        var changeFormat = changeDecimalPlaces switch
        {
            2 => "#,##0.00",
            4 => "#,##0.0000",
            _ => throw new LegacySceneDataException("Unsupported panel quote precision.")
        };
        mutations.Add(new PlayoutSetValue(
            "changePrice" + index + "_" + variant,
            Math.Abs(quote.Change).ToString(changeFormat, CultureInfo.InvariantCulture)));

        var ratePrefix = variant == 3 && quote.Rate >= 0 ? "-" : string.Empty;
        mutations.Add(new PlayoutSetValue(
            "rate" + index + "_" + variant,
            ratePrefix + quote.Rate.ToString("#,##0.00", CultureInfo.InvariantCulture)));
    }
}

internal static class S6001DirectionMutations
{
    private const string RedPattern = @"Images\그림_빨강.png";
    private const string FlatPattern = @"Images\그림_검정.png";
    private const string BluePattern = @"Images\그림_파랑.png";

    private static readonly string[] ColorObjects =
        ["bg", "changePrice", "wonText", "rate", "percent"];

    public static void Add(
        ICollection<PlayoutMutation> mutations,
        ScenePriceDirection direction)
    {
        ClosedSceneMappings.AddDirectionHides(mutations, string.Empty);

        var (directionObject, background, foreground, pattern) = direction switch
        {
            ScenePriceDirection.LimitUp => ("upup", (170, 0, 0), (170, 0, 0), RedPattern),
            ScenePriceDirection.Up => ("up", (170, 0, 0), (170, 0, 0), RedPattern),
            ScenePriceDirection.Flat => ("flat", (95, 95, 95), (52, 52, 52), FlatPattern),
            ScenePriceDirection.Down => ("down", (15, 99, 189), (15, 99, 189), BluePattern),
            ScenePriceDirection.LimitDown => ("downdown", (15, 99, 189), (15, 99, 189), BluePattern),
            _ => throw new LegacySceneDataException("Unknown s6001 price direction.")
        };

        mutations.Add(new PlayoutSetVisible(directionObject, true));
        for (var index = 0; index < ColorObjects.Length; index++)
        {
            var color = index == 0 ? background : foreground;
            mutations.Add(new PlayoutSetFaceColor(
                ColorObjects[index],
                color.Item1,
                color.Item2,
                color.Item3));
        }

        mutations.Add(new PlayoutSetAssetValue("pattern", pattern));
    }
}

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

    private static readonly string[] PanelDirectionObjects =
        ["flat", "up", "upup", "downdown", "down"];

    public static LegacyDomesticEquityMarket RequireDomesticMarket(
        LegacyDomesticEquityMarket market) => market switch
    {
        LegacyDomesticEquityMarket.Kospi => market,
        LegacyDomesticEquityMarket.Kosdaq => market,
        LegacyDomesticEquityMarket.NxtKospi => market,
        LegacyDomesticEquityMarket.NxtKosdaq => market,
        _ => throw new LegacySceneDataException("Unknown domestic equity market.")
    };

    public static bool IsNxt(LegacyDomesticEquityMarket market) => market switch
    {
        LegacyDomesticEquityMarket.Kospi or LegacyDomesticEquityMarket.Kosdaq => false,
        LegacyDomesticEquityMarket.NxtKospi or LegacyDomesticEquityMarket.NxtKosdaq => true,
        _ => throw new LegacySceneDataException("Unknown domestic equity market.")
    };

    public static ScenePriceDirection RequireDirection(ScenePriceDirection direction) =>
        direction switch
        {
            ScenePriceDirection.LimitUp => direction,
            ScenePriceDirection.Up => direction,
            ScenePriceDirection.Flat => direction,
            ScenePriceDirection.Down => direction,
            ScenePriceDirection.LimitDown => direction,
            _ => throw new LegacySceneDataException("Unknown price direction.")
        };

    public static string DirectionValueVariant(ScenePriceDirection direction) => direction switch
    {
        ScenePriceDirection.LimitUp or ScenePriceDirection.Up => "1_",
        ScenePriceDirection.Flat => "2_",
        ScenePriceDirection.Down or ScenePriceDirection.LimitDown => "3_",
        _ => throw new LegacySceneDataException("Unknown price direction.")
    };

    public static void AddDirectionHides(
        ICollection<PlayoutMutation> mutations,
        string suffix)
    {
        foreach (var objectName in DirectionObjects)
        {
            mutations.Add(new PlayoutSetVisible(objectName + suffix, false));
        }
    }

    public static void AddPanelDirectionHides(
        ICollection<PlayoutMutation> mutations,
        string suffix)
    {
        foreach (var objectName in PanelDirectionObjects)
        {
            mutations.Add(new PlayoutSetVisible(objectName + suffix, false));
        }
    }
}
