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

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class ReadOnlyPanelSceneBuildersTests
{
    [Fact]
    public void Builder_keys_are_the_closed_scene_codes()
    {
        Assert.Equal("s5006", new S5006SceneMutationBuilder().BuilderKey);
        Assert.Equal("s5011", new S5011SceneMutationBuilder().BuilderKey);
        Assert.Equal("s5016", new S5016SceneMutationBuilder().BuilderKey);
        Assert.Equal("s50160", new S50160SceneMutationBuilder().BuilderKey);
        Assert.Equal("s6001", new S6001SceneMutationBuilder().BuilderKey);
    }

    [Fact]
    public void S5006_matches_original_background_value_and_mainform_direction_order()
    {
        var actual = new S5006SceneMutationBuilder().Build(new S5006SceneData(
            LegacyDomesticEquityMarket.NxtKospi,
            "삼성전자(NXT)",
            ScenePriceDirection.Down,
            72_100,
            -900,
            -1.23m,
            72_800,
            -0.27m,
            73_500,
            0.69m,
            71_900,
            -1.51m)).Select(Describe);

        Assert.Equal(
        [
            "V:title=삼성전자(NXT)",
            @"B:Video\큐브배경.vrv:1:1",
            "V:price=72,100",
            "V:changePrice3_=-900",
            "V:rate3_=-1.23",
            "V:price1=72,800",
            "V:ratio1=-0.27",
            "V:price2=73,500",
            "V:ratio2=0.69",
            "V:price3=71,900",
            "V:ratio3=-1.51",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:down=1",
            "S:bg1_=0", "S:changePrice1_=0", "S:wonText1_=0", "S:rate1_=0", "S:percent1_=0",
            "S:bg2_=0", "S:changePrice2_=0", "S:wonText2_=0", "S:rate2_=0", "S:percent2_=0",
            "S:bg3_=1", "S:changePrice3_=1", "S:wonText3_=1", "S:rate3_=1", "S:percent3_=1",
            @"A:pattern=Images\그림_파랑.png"
        ], actual);
    }

    [Fact]
    public void S5006_replaces_absolute_legacy_background_with_relative_asset_only()
    {
        var mutations = new S5006SceneMutationBuilder().Build(ValidS5006());
        var assets = mutations.SelectMany(mutation => mutation switch
        {
            PlayoutSetAssetValue asset => [asset.AssetPath],
            PlayoutSetBackgroundVideo video => [video.AssetPath],
            _ => Array.Empty<string>()
        }).ToArray();

        Assert.NotEmpty(assets);
        Assert.All(assets, asset =>
        {
            Assert.False(Path.IsPathRooted(asset));
            Assert.DoesNotContain(":", asset, StringComparison.Ordinal);
            Assert.DoesNotContain("MBN_STOCK_N", asset, StringComparison.OrdinalIgnoreCase);
        });
    }

    [Fact]
    public void S5011_face_value_branch_matches_original_order_and_nxt_marker()
    {
        var actual = new S5011SceneMutationBuilder().Build(new S5011SceneData(
            LegacyDomesticEquityMarket.NxtKosdaq,
            "테스트(NXT)",
            ScenePriceDirection.Up,
            1_250,
            25,
            2.04m,
            new S5011FaceValueDetail(
                500,
                1_234_567_890,
                9_876_543_210,
                3))).Select(Describe);

        Assert.Equal(
        [
            "V:title=테스트",
            "V:r_unit1=원", "V:r_unit2=억원", "V:r_unit3=억원", "V:r_unit4=위",
            "V:r_title1=액면가", "V:r_title2=자본금", "V:r_title3=시가총액", "V:r_title4=시총순위",
            "V:r_value1=500", "V:r_value2=12", "V:r_value3=98", "V:r_value4=3",
            "V:price=1,250", "V:changePrice1_=25", "V:rate1_=2.04",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:up=1",
            "S:bg1_=1", "S:changePrice1_=1", "S:rate1_=1", "S:percent1_=1",
            "S:bg2_=0", "S:changePrice2_=0", "S:rate2_=0", "S:percent2_=0",
            "S:bg3_=0", "S:changePrice3_=0", "S:rate3_=0", "S:percent3_=0",
            "S:mart=1"
        ], actual);
    }

    [Fact]
    public void S5011_valuation_branch_preserves_decimal_scale_and_label_order()
    {
        var actual = new S5011SceneMutationBuilder().Build(new S5011SceneData(
            LegacyDomesticEquityMarket.Kospi,
            "테스트",
            ScenePriceDirection.Flat,
            1_000,
            0,
            0,
            new S5011ValuationDetail(1.20m, 8.75m, 12_345, -321)));

        Assert.Equal(
        [
            "V:title=테스트",
            "V:r_unit1=배", "V:r_unit2=배", "V:r_unit3=원", "V:r_unit4=원",
            "V:r_title1=PBR", "V:r_title2=PER", "V:r_title3=BPS", "V:r_title4=EPS",
            "V:r_value1=1.20", "V:r_value2=8.75", "V:r_value3=12,345", "V:r_value4=-321"
        ], actual.Take(13).Select(Describe));
        Assert.Equal("V:changePrice2_=0", Describe(actual[14]));
        Assert.Equal("S:mart=0", Describe(actual[^1]));
        Assert.Equal(40, actual.Count);
        Assert.DoesNotContain(actual, mutation => mutation is PlayoutSetAssetValue);
        Assert.DoesNotContain(
            actual.OfType<PlayoutSetVisible>(),
            mutation => mutation.ObjectName.StartsWith("wonText", StringComparison.Ordinal));
    }

    [Fact]
    public void S5011_volume_branch_preserves_integer_division_and_labels()
    {
        var actual = new S5011SceneMutationBuilder().Build(new S5011SceneData(
            LegacyDomesticEquityMarket.Kosdaq,
            "테스트",
            ScenePriceDirection.Down,
            900,
            -100,
            -10,
            new S5011VolumeDetail(12_345, 9_876_543, 1_111, 2_222)));

        Assert.Equal(
        [
            "V:title=테스트",
            "V:r_unit1=주", "V:r_unit2=백만", "V:r_unit3=원", "V:r_unit4=원",
            "V:r_title1=거래량", "V:r_title2=거래대금", "V:r_title3=5MA", "V:r_title4=20MA",
            "V:r_value1=12,345", "V:r_value2=9", "V:r_value3=1,111", "V:r_value4=2,222"
        ], actual.Take(13).Select(Describe));
        Assert.Equal("V:changePrice3_=-100", Describe(actual[14]));
        Assert.Equal("S:percent3_=1", Describe(actual[^2]));
        Assert.DoesNotContain(actual, mutation => mutation is PlayoutSetAssetValue);
    }

    [Fact]
    public void S5016_matches_original_three_row_visibility_and_value_order()
    {
        var actual = new S5016SceneMutationBuilder().Build(new S5016SceneData(
            S5016PanelTarget.OverseasExchangeRates,
            [
                new("KOSPI A", null, 1_234.5m, 1.23456m, 1.2m, LegacyPanelQuoteSignal.Positive),
                new("B", null, 2_000m, 0m, 0m, LegacyPanelQuoteSignal.Negative),
                new("C", null, 3_000m, -2.5m, 3.4m, LegacyPanelQuoteSignal.Negative)
            ])).Select(Describe);

        Assert.Equal(
        [
            "V:title=해외 환율",
            "S:flat1=0", "S:up1=0", "S:upup1=0", "S:downdown1=0", "S:down1=0",
            "V:title1=코스피 A", "V:price1=1,234.50",
            "S:bg1_1=1", "S:bg1_2=0", "S:bg1_3=0",
            "S:rate1_1=1", "S:rate1_2=0", "S:rate1_3=0",
            "S:changePrice1_1=1", "S:changePrice1_2=0", "S:changePrice1_3=0",
            "S:percent1_1=1", "S:percent1_2=0", "S:percent1_3=0",
            "S:unit1_1=1", "S:unit1_2=0", "S:unit1_3=0",
            "S:up1=1", "V:changePrice1_1=1.2346", "V:rate1_1=1.20", "V:unitText=단위 : $",
            "S:flat2=0", "S:up2=0", "S:upup2=0", "S:downdown2=0", "S:down2=0",
            "V:title2=B", "V:price2=2,000.00",
            "S:bg2_1=0", "S:bg2_2=1", "S:bg2_3=0",
            "S:rate2_1=0", "S:rate2_2=1", "S:rate2_3=0",
            "S:changePrice2_1=0", "S:changePrice2_2=1", "S:changePrice2_3=0",
            "S:percent2_1=0", "S:percent2_2=1", "S:percent2_3=0",
            "S:unit2_1=0", "S:unit2_2=1", "S:unit2_3=0",
            "S:flat2=1", "V:changePrice2_2=0.0000", "V:rate2_2=0.00", "V:unitText=단위 : $",
            "S:flat3=0", "S:up3=0", "S:upup3=0", "S:downdown3=0", "S:down3=0",
            "V:title3=C", "V:price3=3,000.00",
            "S:bg3_1=0", "S:bg3_2=0", "S:bg3_3=1",
            "S:rate3_1=0", "S:rate3_2=0", "S:rate3_3=1",
            "S:changePrice3_1=0", "S:changePrice3_2=0", "S:changePrice3_3=1",
            "S:percent3_1=0", "S:percent3_2=0", "S:percent3_3=1",
            "S:unit3_1=0", "S:unit3_2=0", "S:unit3_3=1",
            "S:down3=1", "V:changePrice3_3=2.5000", "V:rate3_3=-3.40", "V:unitText=단위 : $"
        ], actual);
    }

    [Theory]
    [MemberData(nameof(S5016TargetCases))]
    public void S5016_target_mapping_is_closed_and_repeats_original_unit_order(
        S5016PanelTarget target,
        string title,
        string unit)
    {
        var quotes = Enumerable.Range(1, 3)
            .Select(index => new LegacyPanelQuoteData(
                "Name" + index,
                "Part" + index,
                index,
                0,
                0,
                LegacyPanelQuoteSignal.Flat))
            .ToArray();

        var actual = new S5016SceneMutationBuilder().Build(new S5016SceneData(target, quotes));

        Assert.Equal("V:title=" + title, Describe(actual[0]));
        Assert.Equal("V:unitText=" + unit, Describe(actual[26]));
        Assert.Equal("V:unitText=" + unit, Describe(actual[52]));
        Assert.Equal("V:unitText=" + unit, Describe(actual[78]));
    }

    [Fact]
    public void S5016_bond_branch_uses_part_name_not_generic_name()
    {
        var quotes = Enumerable.Range(1, 3)
            .Select(index => new LegacyPanelQuoteData(
                "generic" + index,
                "bond" + index,
                index,
                0,
                0,
                LegacyPanelQuoteSignal.Flat))
            .ToArray();

        var actual = new S5016SceneMutationBuilder().Build(new S5016SceneData(
            S5016PanelTarget.BondYields,
            quotes));

        Assert.Equal("V:title1=bond1", Describe(actual[6]));
        Assert.Equal("V:title2=bond2", Describe(actual[32]));
        Assert.Equal("V:title3=bond3", Describe(actual[58]));
    }

    [Fact]
    public void S50160_matches_original_two_row_unit_clear_and_visibility_order()
    {
        var actual = new S50160SceneMutationBuilder().Build(new S50160SceneData(
            S50160PanelTarget.Cotton,
            [
                new("Cotton", null, 100m, 2m, 1.5m, LegacyPanelQuoteSignal.Positive),
                new("KOSQ Cotton", null, 90m, -1m, 2m, LegacyPanelQuoteSignal.Negative)
            ])).Select(Describe);

        Assert.Equal(
        [
            "V:title=농산물",
            "S:flat1=0", "S:up1=0", "S:upup1=0", "S:downdown1=0", "S:down1=0",
            "V:title1=Cotton", "V:price1=100.00",
            "S:bg1_1=1", "S:bg1_2=0", "S:bg1_3=0",
            "S:rate1_1=1", "S:rate1_2=0", "S:rate1_3=0",
            "S:changePrice1_1=1", "S:changePrice1_2=0", "S:changePrice1_3=0",
            "S:percent1_1=1", "S:percent1_2=0", "S:percent1_3=0",
            "S:unit1_1=1", "S:unit1_2=0", "S:unit1_3=0",
            "S:up1=1", "V:changePrice1_1=2.00", "V:rate1_1=1.50",
            "V:unit1_1=", "V:unit1_2=", "V:unit1_3=", "V:unitText=단위 : $",
            "S:flat2=0", "S:up2=0", "S:upup2=0", "S:downdown2=0", "S:down2=0",
            "V:title2=코스닥 Cotton", "V:price2=90.00",
            "S:bg2_1=0", "S:bg2_2=0", "S:bg2_3=1",
            "S:rate2_1=0", "S:rate2_2=0", "S:rate2_3=1",
            "S:changePrice2_1=0", "S:changePrice2_2=0", "S:changePrice2_3=1",
            "S:percent2_1=0", "S:percent2_2=0", "S:percent2_3=1",
            "S:unit2_1=0", "S:unit2_2=0", "S:unit2_3=1",
            "S:down2=1", "V:changePrice2_3=1.00", "V:rate2_3=-2.00",
            "V:unit2_1=", "V:unit2_2=", "V:unit2_3=", "V:unitText=단위 : $"
        ], actual);
    }

    [Theory]
    [InlineData(S50160PanelTarget.Cotton, "농산물", "단위 : $")]
    [InlineData(S50160PanelTarget.InternationalPreciousMetals, "원자재", "단위 : $")]
    [InlineData(S50160PanelTarget.DomesticPreciousMetals, "원자재", "단위 : 원")]
    public void S50160_target_mapping_is_closed(
        S50160PanelTarget target,
        string title,
        string unit)
    {
        var rows = new[] { FlatQuote("A"), FlatQuote("B") };
        var actual = new S50160SceneMutationBuilder().Build(new S50160SceneData(target, rows));

        Assert.Equal("V:title=" + title, Describe(actual[0]));
        Assert.Equal("V:unitText=" + unit, Describe(actual[29]));
        Assert.Equal("V:unitText=" + unit, Describe(actual[58]));
    }

    [Fact]
    public void S6001_index_branch_matches_original_asset_value_and_color_order()
    {
        var actual = new S6001SceneMutationBuilder().Build(new S6001SceneData(
            S6001QuoteTarget.Nasdaq,
            "나스닥",
            18_000.123m,
            -15.5m,
            1.23m,
            ScenePriceDirection.Down)).Select(Describe);

        Assert.Equal(
        [
            "V:title=나스닥",
            @"A:movie=Video\20201008_미국.vrv",
            "V:price=18,000.12",
            "V:changePrice=15.50",
            "V:rate=-1.23",
            "V:wonText=",
            "V:unit=단위 : p",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:down=1",
            "C:bg:15:99:189:255", "C:changePrice:15:99:189:255",
            "C:wonText:15:99:189:255", "C:rate:15:99:189:255", "C:percent:15:99:189:255",
            @"A:pattern=Images\그림_파랑.png"
        ], actual);
    }

    [Fact]
    public void S6001_gold_branch_matches_original_asset_first_and_flat_colors()
    {
        var actual = new S6001SceneMutationBuilder().Build(new S6001SceneData(
            S6001QuoteTarget.Gold,
            "국제 금",
            2_300m,
            -1.1m,
            -0.05m,
            ScenePriceDirection.Flat)).Select(Describe);

        Assert.Equal(
        [
            @"A:movie=images\35752913_l.jpg",
            "V:unit=단위 : 달러/온스",
            "V:title=국제 금",
            "V:price=2,300.00",
            "V:changePrice=-1.10",
            "V:rate=-0.05",
            "V:wonText=",
            "S:upup=0", "S:up=0", "S:flat=0", "S:down=0", "S:downdown=0",
            "S:flat=1",
            "C:bg:95:95:95:255", "C:changePrice:52:52:52:255",
            "C:wonText:52:52:52:255", "C:rate:52:52:52:255", "C:percent:52:52:52:255",
            @"A:pattern=Images\그림_검정.png"
        ], actual);
    }

    [Theory]
    [MemberData(nameof(S6001TargetCases))]
    public void S6001_target_mapping_is_closed_and_never_accepts_raw_asset_paths(
        S6001QuoteTarget target,
        string asset,
        string unit,
        bool isIndex)
    {
        var actual = new S6001SceneMutationBuilder().Build(new S6001SceneData(
            target,
            "Title",
            1,
            0,
            0,
            ScenePriceDirection.Flat));

        var assetMutation = Assert.IsType<PlayoutSetAssetValue>(actual[isIndex ? 1 : 0]);
        Assert.Equal(asset, assetMutation.AssetPath);
        Assert.Equal("V:unit=" + unit, Describe(actual[isIndex ? 6 : 1]));
        Assert.False(Path.IsPathRooted(assetMutation.AssetPath));
    }

    [Fact]
    public void Builders_fail_closed_for_unknown_or_missing_database_values()
    {
        Assert.Throws<LegacySceneDataException>(() =>
            new S5006SceneMutationBuilder().Build(ValidS5006() with { StockName = "" }));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5006SceneMutationBuilder().Build(ValidS5006() with
            {
                Market = (LegacyDomesticEquityMarket)999
            }));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5011SceneMutationBuilder().Build(new S5011SceneData(
                LegacyDomesticEquityMarket.Kospi,
                "Name",
                ScenePriceDirection.Up,
                1,
                1,
                1,
                null!)));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5016SceneMutationBuilder().Build(new S5016SceneData(
                S5016PanelTarget.Oil,
                [FlatQuote("A"), FlatQuote("B")])));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5016SceneMutationBuilder().Build(new S5016SceneData(
                S5016PanelTarget.Oil,
                [
                    FlatQuote("A"),
                    FlatQuote("B"),
                    FlatQuote("")
                ])));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5016SceneMutationBuilder().Build(new S5016SceneData(
                (S5016PanelTarget)999,
                [FlatQuote("A"), FlatQuote("B"), FlatQuote("C")])));
        Assert.Throws<LegacySceneDataException>(() =>
            new S5016SceneMutationBuilder().Build(new S5016SceneData(
                S5016PanelTarget.Oil,
                [
                    FlatQuote("A"),
                    FlatQuote("B"),
                    FlatQuote("C") with { Signal = (LegacyPanelQuoteSignal)999 }
                ])));
        Assert.Throws<LegacySceneDataException>(() =>
            new S50160SceneMutationBuilder().Build(new S50160SceneData(
                (S50160PanelTarget)999,
                [FlatQuote("A"), FlatQuote("B")])));
        Assert.Throws<LegacySceneDataException>(() =>
            new S6001SceneMutationBuilder().Build(new S6001SceneData(
                S6001QuoteTarget.Gold,
                "",
                1,
                0,
                0,
                ScenePriceDirection.Flat)));
        Assert.Throws<LegacySceneDataException>(() =>
            new S6001SceneMutationBuilder().Build(new S6001SceneData(
                S6001QuoteTarget.Gold,
                "Gold",
                1,
                0,
                0,
                ScenePriceDirection.Unknown)));
        Assert.Throws<LegacySceneDataException>(() =>
            new S6001SceneMutationBuilder().Build(new S6001SceneData(
                (S6001QuoteTarget)999,
                "Gold",
                1,
                0,
                0,
                ScenePriceDirection.Flat)));
    }

    [Fact]
    public void Scene_dtos_expose_no_raw_path_object_or_method_surface()
    {
        Type[] dtoTypes =
        [
            typeof(S5006SceneData),
            typeof(S5011SceneData),
            typeof(S5011FaceValueDetail),
            typeof(S5011ValuationDetail),
            typeof(S5011VolumeDetail),
            typeof(S5016SceneData),
            typeof(S50160SceneData),
            typeof(LegacyPanelQuoteData),
            typeof(S6001SceneData)
        ];

        foreach (var dtoType in dtoTypes)
        {
            Assert.DoesNotContain(dtoType.GetProperties(), property =>
                property.Name.Contains("Path", StringComparison.OrdinalIgnoreCase) ||
                property.Name.Contains("Object", StringComparison.OrdinalIgnoreCase) ||
                property.Name.Contains("Method", StringComparison.OrdinalIgnoreCase) ||
                typeof(PlayoutMutation).IsAssignableFrom(property.PropertyType));
        }
    }

    public static IEnumerable<object[]> S5016TargetCases()
    {
        yield return [S5016PanelTarget.UnitedStatesIndices, "미국 지수", "단위 : p"];
        yield return [S5016PanelTarget.GreaterChinaIndices, "중화권 지수", "단위 : p"];
        yield return [S5016PanelTarget.EuropeanIndices, "유럽 지수", "단위 : p"];
        yield return [S5016PanelTarget.AsianIndices, "아시아 지수", "단위 : p"];
        yield return [S5016PanelTarget.UnitedStatesTreasuries, "미국 국채", "단위 : %"];
        yield return [S5016PanelTarget.BondYields, "채권 금리", "단위 : %"];
        yield return [S5016PanelTarget.Oil, "유가", "단위 : 배럴/달러"];
        yield return [S5016PanelTarget.Minerals, "광물", "단위 : $"];
        yield return [S5016PanelTarget.FoodMaterials, "식자재", "단위 : ￠"];
        yield return [S5016PanelTarget.OverseasExchangeRates, "해외 환율", "단위 : $"];
        yield return [S5016PanelTarget.DomesticExchangeRates, "환율", "단위 : 원"];
        yield return [S5016PanelTarget.AgricultureWheatCorn, "농산물", "단위 : $"];
        yield return [S5016PanelTarget.AgricultureSoyRice, "농산물", "단위 : $"];
        yield return [S5016PanelTarget.AgricultureCoffeeCocoaSugar, "농산물", "단위 : $"];
        yield return [S5016PanelTarget.AgricultureLivestock, "농산물", "단위 : $"];
        yield return [S5016PanelTarget.RawMaterialsCopperIronNickel, "원자재", "단위 : $"];
        yield return [S5016PanelTarget.RawMaterialsGasPlatinumPalladium, "원자재", "단위 : $"];
        yield return [S5016PanelTarget.RawMaterialsLeadZincTin, "원자재", "단위 : $"];
        yield return [S5016PanelTarget.MajorKospiKosdaqKospi200, "주요지수", "단위 : p"];
        yield return [S5016PanelTarget.MajorKospiKosdaqFutures, "주요지수", "단위 : p"];
        yield return [S5016PanelTarget.ExpectedKospiKosdaqKospi200, "예상체결지수", "단위 : p"];
        yield return [S5016PanelTarget.ExpectedKospiKosdaqFutures, "예상체결지수", "단위 : p"];
    }

    public static IEnumerable<object[]> S6001TargetCases()
    {
        yield return [S6001QuoteTarget.DowJones, @"Video\20201008_미국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Nasdaq, @"Video\20201008_미국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.StandardAndPoor500, @"Video\20201008_미국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Germany, @"Video\20201008_독일.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.UnitedKingdom, @"Video\20201008_영국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.France, @"Video\20201008_프랑스.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Japan, @"Video\20201008_일본.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.China, @"Video\20201008_중국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.HongKong, @"Video\20201008_홍콩.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Taiwan, @"Video\20201008_대만.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Singapore, @"Video\20201008_싱가포르.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Thailand, @"Video\20201008_태국.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Philippines, @"Video\20201008_필리핀.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Malaysia, @"Video\20201008_말레이시아.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.Indonesia, @"Video\20201008_인도네시아.vrv", "단위 : p", true];
        yield return [S6001QuoteTarget.DubaiCrude, @"images\주유기merge.png", "단위 : 배럴/달러", false];
        yield return [S6001QuoteTarget.BrentCrude, @"images\주유기merge.png", "단위 : 배럴/달러", false];
        yield return [S6001QuoteTarget.WtiCrude, @"images\주유기merge.png", "단위 : 배럴/달러", false];
        yield return [S6001QuoteTarget.Gold, @"images\35752913_l.jpg", "단위 : 달러/온스", false];
    }

    private static S5006SceneData ValidS5006() => new(
        LegacyDomesticEquityMarket.Kospi,
        "Name",
        ScenePriceDirection.Flat,
        1,
        0,
        0,
        1,
        0,
        1,
        0,
        1,
        0);

    private static LegacyPanelQuoteData FlatQuote(string name) =>
        new(name, name, 1, 0, 0, LegacyPanelQuoteSignal.Flat);

    private static string Describe(PlayoutMutation mutation) => mutation switch
    {
        PlayoutSetValue value => $"V:{value.ObjectName}={value.Value}",
        PlayoutSetAssetValue asset => $"A:{asset.ObjectName}={asset.AssetPath}",
        PlayoutSetVisible visible => $"S:{visible.ObjectName}={(visible.IsVisible ? 1 : 0)}",
        PlayoutSetFaceColor color =>
            $"C:{color.ObjectName}:{color.Red}:{color.Green}:{color.Blue}:{color.Alpha}",
        PlayoutSetBackgroundVideo video =>
            $"B:{video.AssetPath}:{video.LoopCount}:{video.LoopInfinite}",
        _ => mutation.GetType().Name
    };
}
