#nullable enable

using System.Globalization;

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

public sealed record RevenueSliceData(
    string Label,
    string PercentageText,
    double Percentage);

public sealed record S5076SceneData(
    string StockName,
    string Basis,
    IReadOnlyList<RevenueSliceData?> Slices) : ILegacySceneData;

public sealed class S5076SceneMutationBuilder : ILegacySceneMutationBuilder<S5076SceneData>
{
    public string BuilderKey => "s5076";

    public IReadOnlyList<PlayoutMutation> Build(S5076SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var stockName = LegacySceneBuilderGuard.Text(data.StockName, nameof(data.StockName));
        var basis = LegacySceneBuilderGuard.Text(
            data.Basis,
            nameof(data.Basis),
            allowEmpty: true);
        var slices = LegacySceneBuilderGuard.Count(data.Slices, 5, nameof(data.Slices));
        var mutations = new List<PlayoutMutation>(34)
        {
            new PlayoutSetValue("title", stockName + " 주요매출 구성"),
            new PlayoutSetValue("date", basis + " 기준")
        };

        for (var index = 1; index <= 5; index++)
        {
            mutations.Add(new PlayoutSetVisible("circle" + index, false));
            mutations.Add(new PlayoutSetVisible("group" + index, false));
        }

        double startAngle = 0;
        for (var index = 1; index <= 5; index++)
        {
            var slice = slices[index - 1];
            if (slice is null || string.IsNullOrEmpty(slice.Label))
            {
                continue;
            }

            var label = LegacySceneBuilderGuard.Text(
                slice.Label,
                $"Slices[{index - 1}].Label",
                allowEmpty: true);
            var percentageText = LegacySceneBuilderGuard.Text(
                slice.PercentageText,
                $"Slices[{index - 1}].PercentageText");
            if (!double.IsFinite(slice.Percentage))
            {
                throw new LegacySceneDataException("Revenue percentage must be finite.");
            }

            mutations.Add(new PlayoutSetVisible("circle" + index, true));
            mutations.Add(new PlayoutSetVisible("group" + index, true));
            mutations.Add(new PlayoutSetValue("word" + index, label));
            mutations.Add(new PlayoutSetValue("percent" + index, percentageText));

            var angle = slice.Percentage / 100d * 360d;
            mutations.Add(new PlayoutSetCircleAngleKey(
                "circle" + index,
                0,
                (float)startAngle,
                (float)startAngle,
                PlayoutAngleComponents.All));
            mutations.Add(new PlayoutSetCircleAngleKey(
                "circle" + index,
                1,
                (float)startAngle,
                (float)(startAngle + angle),
                PlayoutAngleComponents.All));
            startAngle += angle;
        }

        return mutations;
    }
}

public sealed record QuarterlyProfitData(string Quarter, int Value);

public sealed record S5081SceneData(
    string StockName,
    IReadOnlyList<QuarterlyProfitData> Quarters) : ILegacySceneData;

public sealed class S5081SceneMutationBuilder : ILegacySceneMutationBuilder<S5081SceneData>
{
    public string BuilderKey => "s5081";

    public IReadOnlyList<PlayoutMutation> Build(S5081SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var stockName = LegacySceneBuilderGuard.Text(data.StockName, nameof(data.StockName));
        var quarters = LegacySceneBuilderGuard.Count(data.Quarters, 6, nameof(data.Quarters));
        var mutations = new List<PlayoutMutation>(38)
        {
            new PlayoutSetValue("title", stockName + " 영업이익")
        };

        for (var index = 1; index <= 6; index++)
        {
            var quarter = LegacySceneBuilderGuard.NotNull(
                quarters[index - 1],
                $"Quarters[{index - 1}]");
            mutations.Add(new PlayoutSetValue(
                "quarter" + index,
                LegacySceneBuilderGuard.Text(
                    quarter.Quarter,
                    $"Quarters[{index - 1}].Quarter",
                    allowEmpty: true)));
        }

        var values = quarters.Select(item => item.Value).ToArray();
        for (var index = 1; index <= 6; index++)
        {
            var value = values[index - 1];
            var variant = value < 0 ? 2 : 1;
            mutations.Add(new PlayoutSetVisible($"barG{index}_1", false));
            mutations.Add(new PlayoutSetVisible($"barG{index}_2", false));
            mutations.Add(new PlayoutSetVisible($"barG{index}_{variant}", true));
            mutations.Add(new PlayoutSetValue(
                $"value{index}_{variant}",
                value.ToString("#,##0", CultureInfo.InvariantCulture)));
        }

        var maximum = values.Max();
        var minimum = values.Min();
        var allPositive = maximum > 0 && minimum > 0;
        var allNegative = maximum < 0 && minimum < 0;
        var center = allPositive
            ? -240f
            : allNegative
                ? 115f
                : SafeFloat((355d * Math.Abs(minimum) / (maximum + Math.Abs((double)minimum))) - 250d, -250f);
        mutations.Add(new PlayoutSetPositionKey(
            "centerbar",
            0,
            0,
            center,
            0,
            PlayoutVectorComponents.Y));

        for (var index = 1; index <= 6; index++)
        {
            var value = values[index - 1];
            var denominator = allPositive
                ? maximum
                : allNegative
                    ? minimum
                    : maximum - minimum;
            var scale = denominator == 0
                ? 0f
                : (float)(1.8d * Math.Abs(value / (double)denominator));
            var variant = value < 0 ? 2 : 1;
            mutations.Add(new PlayoutSetScale(
                $"bar{index}_{variant}",
                0,
                scale,
                0,
                PlayoutVectorComponents.Y));
        }

        return mutations;
    }

    private static float SafeFloat(double value, float fallback) =>
        double.IsFinite(value) ? (float)value : fallback;
}

public enum ProgramTradingMetric
{
    NetBuy,
    Arbitrage,
    NonArbitrage,
    Basis,
    Kospi200,
    Futures
}

public sealed record ProgramTradingMetricData(ProgramTradingMetric Metric, double Value);

public sealed record S5085SceneData(
    IReadOnlyList<ProgramTradingMetricData> Metrics) : ILegacySceneData;

public sealed class S5085SceneMutationBuilder : ILegacySceneMutationBuilder<S5085SceneData>
{
    public string BuilderKey => "s5085";

    public IReadOnlyList<PlayoutMutation> Build(S5085SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var metrics = LegacySceneBuilderGuard.Range(data.Metrics, 1, 6, nameof(data.Metrics));
        if (metrics.Select(item => item.Metric).Distinct().Count() != metrics.Count)
        {
            throw new LegacySceneDataException("Program trading metrics must be unique.");
        }

        var mutations = new List<PlayoutMutation>(metrics.Count * 3);
        foreach (var metric in metrics)
        {
            if (!double.IsFinite(metric.Value))
            {
                throw new LegacySceneDataException("Program trading value must be finite.");
            }

            var (index, title) = metric.Metric switch
            {
                ProgramTradingMetric.NetBuy => (1, "순매수"),
                ProgramTradingMetric.Arbitrage => (2, "차익"),
                ProgramTradingMetric.NonArbitrage => (3, "비차익"),
                ProgramTradingMetric.Basis => (4, "베이시스"),
                ProgramTradingMetric.Kospi200 => (5, "코스피200"),
                ProgramTradingMetric.Futures => (6, "선물"),
                _ => throw new LegacySceneDataException("Unknown program trading metric.")
            };
            var color = SignedColor(metric.Value, (171, 0, 0), (15, 99, 188), (47, 47, 47));
            mutations.Add(new PlayoutSetValue("title" + index, title));
            mutations.Add(new PlayoutSetFaceColor("price" + index, color.R, color.G, color.B));
            mutations.Add(new PlayoutSetValue(
                "price" + index,
                metric.Value.ToString("#,##0.##", CultureInfo.InvariantCulture)));
        }

        return mutations;
    }

    private static (int R, int G, int B) SignedColor(
        double value,
        (int R, int G, int B) positive,
        (int R, int G, int B) negative,
        (int R, int G, int B) zero) => value > 0 ? positive : value < 0 ? negative : zero;
}

public enum InstitutionMarket
{
    Kospi,
    Kosdaq,
    Other
}

public enum InstitutionKind
{
    Securities,
    Insurance,
    InvestmentTrust,
    Bank,
    Fund
}

public sealed record InstitutionNetBuyData(
    InstitutionMarket Market,
    InstitutionKind Institution,
    int Value);

public sealed record S6067SceneData(
    IReadOnlyList<InstitutionNetBuyData> Rows) : ILegacySceneData;

public sealed class S6067SceneMutationBuilder : ILegacySceneMutationBuilder<S6067SceneData>
{
    public string BuilderKey => "s6067";

    public IReadOnlyList<PlayoutMutation> Build(S6067SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var rows = LegacySceneBuilderGuard.Range(data.Rows, 1, 15, nameof(data.Rows));
        var mutations = new List<PlayoutMutation>(rows.Count * 2);
        foreach (var row in rows)
        {
            var market = row.Market switch
            {
                InstitutionMarket.Kospi => 1,
                InstitutionMarket.Kosdaq => 2,
                InstitutionMarket.Other => 3,
                _ => throw new LegacySceneDataException("Unknown institution market.")
            };
            var institution = row.Institution switch
            {
                InstitutionKind.Securities => 1,
                InstitutionKind.Insurance => 2,
                InstitutionKind.InvestmentTrust => 3,
                InstitutionKind.Bank => 4,
                InstitutionKind.Fund => 5,
                _ => throw new LegacySceneDataException("Unknown institution type.")
            };
            var name = $"row{market}_value{institution}";
            var color = row.Value > 0
                ? (R: 171, G: 0, B: 0)
                : row.Value < 0
                    ? (R: 15, G: 99, B: 188)
                    : (R: 47, G: 47, B: 47);
            mutations.Add(new PlayoutSetFaceColor(name, color.R, color.G, color.B));
            mutations.Add(new PlayoutSetValue(
                name,
                row.Value.ToString("#,##0", CultureInfo.InvariantCulture)));
        }

        return mutations;
    }
}

public enum WorldMarket
{
    Korea,
    China,
    HongKong,
    Japan,
    Taiwan,
    Dow,
    Nasdaq,
    StandardAndPoor,
    UnitedKingdom,
    Germany,
    France
}

public enum SceneMarketTrend
{
    Up,
    Flat,
    Down
}

public sealed record WorldMarketQuoteData(
    WorldMarket Market,
    double CurrentPrice,
    double NetChange,
    double Rate,
    SceneMarketTrend Trend);

public sealed record S8067SceneData(
    IReadOnlyList<WorldMarketQuoteData> Quotes) : ILegacySceneData;

public sealed class S8067SceneMutationBuilder : ILegacySceneMutationBuilder<S8067SceneData>
{
    public string BuilderKey => "s8067";

    public IReadOnlyList<PlayoutMutation> Build(S8067SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var quotes = LegacySceneBuilderGuard.Range(data.Quotes, 1, 11, nameof(data.Quotes));
        if (quotes.Select(item => item.Market).Distinct().Count() != quotes.Count)
        {
            throw new LegacySceneDataException("World market quotes must be unique.");
        }

        var mutations = new List<PlayoutMutation>(quotes.Count * 7);
        foreach (var quote in quotes)
        {
            if (!double.IsFinite(quote.CurrentPrice) || !double.IsFinite(quote.NetChange) ||
                !double.IsFinite(quote.Rate))
            {
                throw new LegacySceneDataException("World market quote values must be finite.");
            }

            var target = NameOf(quote.Market);
            mutations.Add(new PlayoutSetValue(
                "price_" + target,
                quote.CurrentPrice.ToString("#,##0.###", CultureInfo.InvariantCulture)));
            mutations.Add(new PlayoutSetValue(
                "rate_" + target,
                quote.Rate.ToString("#,##0.00", CultureInfo.InvariantCulture)));
            mutations.Add(new PlayoutSetVisible("up_" + target, false));
            mutations.Add(new PlayoutSetVisible("down_" + target, false));
            mutations.Add(new PlayoutSetVisible("flat_" + target, false));

            if (quote.Trend == SceneMarketTrend.Up)
            {
                mutations.Add(new PlayoutSetFaceColor("bg_" + target, 170, 0, 0));
                mutations.Add(new PlayoutSetVisible("up_" + target, true));
            }
            else if (quote.Trend == SceneMarketTrend.Down && quote.NetChange != 0)
            {
                mutations.Add(new PlayoutSetFaceColor("bg_" + target, 1, 101, 255));
                mutations.Add(new PlayoutSetVisible("down_" + target, true));
            }
            else
            {
                // Preserve the original global "bg" target used for flat rows.
                mutations.Add(new PlayoutSetFaceColor("bg", 191, 191, 191));
                mutations.Add(new PlayoutSetVisible("flat_" + target, true));
            }
        }

        return mutations;
    }

    private static string NameOf(WorldMarket market) => market switch
    {
        WorldMarket.Korea => "한국",
        WorldMarket.China => "중국",
        WorldMarket.HongKong => "홍콩",
        WorldMarket.Japan => "일본",
        WorldMarket.Taiwan => "대만",
        WorldMarket.Dow => "다우",
        WorldMarket.Nasdaq => "나스닥",
        WorldMarket.StandardAndPoor => "S&P",
        WorldMarket.UnitedKingdom => "영국",
        WorldMarket.Germany => "독일",
        WorldMarket.France => "프랑스",
        _ => throw new LegacySceneDataException("Unknown world market.")
    };
}

public enum CommodityUnit
{
    DollarsPerBarrel,
    DollarsPerOunce
}

public sealed record S8086SceneData(
    string Title,
    CommodityUnit Unit,
    double CurrentPrice,
    double ChangePrice,
    double Rate,
    SceneMarketTrend Trend) : ILegacySceneData;

public sealed class S8086SceneMutationBuilder : ILegacySceneMutationBuilder<S8086SceneData>
{
    public string BuilderKey => "s8086";

    public IReadOnlyList<PlayoutMutation> Build(S8086SceneData data)
    {
        ArgumentNullException.ThrowIfNull(data);
        var title = LegacySceneBuilderGuard.Text(data.Title, nameof(data.Title));
        if (!double.IsFinite(data.CurrentPrice) || !double.IsFinite(data.ChangePrice) ||
            !double.IsFinite(data.Rate))
        {
            throw new LegacySceneDataException("Commodity quote values must be finite.");
        }

        var unit = data.Unit switch
        {
            CommodityUnit.DollarsPerBarrel => "단위: 달러/배럴",
            CommodityUnit.DollarsPerOunce => "단위: 달러/온스",
            _ => throw new LegacySceneDataException("Unknown commodity unit.")
        };
        var color = data.Trend switch
        {
            SceneMarketTrend.Up => (R: 172, G: 0, B: 0),
            SceneMarketTrend.Down => (R: 14, G: 100, B: 188),
            SceneMarketTrend.Flat => (R: 95, G: 95, B: 95),
            _ => throw new LegacySceneDataException("Unknown commodity trend.")
        };

        return
        [
            new PlayoutSetValue("unit", unit),
            new PlayoutSetValue("title", title),
            new PlayoutSetValue("price", data.CurrentPrice.ToString("#,##0.00", CultureInfo.InvariantCulture)),
            new PlayoutSetValue("changePrice", data.ChangePrice.ToString("#,##0.00", CultureInfo.InvariantCulture)),
            new PlayoutSetValue("rate", data.Rate.ToString("#,##0.00", CultureInfo.InvariantCulture)),
            new PlayoutSetFaceColor("bg", color.R, color.G, color.B),
            new PlayoutSetFaceColor("changePrice", color.R, color.G, color.B),
            new PlayoutSetFaceColor("rate", color.R, color.G, color.B)
        ];
    }
}
