using System.CodeDom.Compiler;
using System.IO;
using System.Linq;

namespace Spectre.Console.SourceGenerator.Colors;

/// <summary>
/// Emits C# source code for colors.
/// </summary>
internal static class ColorEmitter
{
    private const string AutoGeneratedHeader = """
        //------------------------------------------------------------------------------
        // <auto-generated>
        //     This code was generated by a tool.
        //
        //     Changes to this file may cause incorrect behavior and will be lost if
        //     the code is regenerated.
        // </auto-generated>
        //------------------------------------------------------------------------------

        """;

    /// <summary>
    /// Emits the Color.Generated.g.cs file content.
    /// </summary>
    public static string EmitColorProperties(EquatableArray<ColorModel> colors)
    {
        using var stringWriter = new StringWriter();
        using var writer = new IndentedTextWriter(stringWriter, "    ");

        writer.WriteLine(AutoGeneratedHeader);
        writer.WriteLine("using System.Diagnostics.CodeAnalysis;");
        writer.WriteLine();
        writer.WriteLine("namespace Spectre.Console");
        writer.WriteLine("{");
        writer.Indent++;

        writer.WriteLine("/// <summary>");
        writer.WriteLine("/// Represents a color.");
        writer.WriteLine("/// </summary>");
        writer.WriteLine("public partial struct Color");
        writer.WriteLine("{");
        writer.Indent++;

        // Emit internal constructor
        writer.WriteLine("internal Color(byte number, byte red, byte green, byte blue, bool isDefault = false)");
        writer.Indent++;
        writer.WriteLine(": this(red, green, blue)");
        writer.Indent--;
        writer.WriteLine("{");
        writer.Indent++;
        writer.WriteLine("Number = number;");
        writer.WriteLine("IsDefault = isDefault;");
        writer.Indent--;
        writer.WriteLine("}");
        writer.WriteLine();

        foreach (var color in colors)
        {
            var hasUnderscore = color.Name.Contains("_");

            writer.WriteLine("/// <summary>");
            writer.WriteLine($"/// Gets the color \"{color.Name}\" (RGB {color.R},{color.G},{color.B}).");
            writer.WriteLine("/// </summary>");

            if (hasUnderscore)
            {
                writer.WriteLine("[SuppressMessage(\"Naming\", \"CA1707:Identifiers should not contain underscores\")]");
            }

            writer.WriteLine($"public static Color {color.Name} {{ get; }} = new Color({color.Number}, {color.R}, {color.G}, {color.B});");
            writer.WriteLine();
        }

        writer.Indent--;
        writer.WriteLine("}");
        writer.Indent--;
        writer.WriteLine("}");

        return stringWriter.ToString();
    }

    /// <summary>
    /// Emits the ColorPalette.Generated.g.cs file content.
    /// </summary>
    public static string EmitColorPalette(EquatableArray<ColorModel> colors)
    {
        using var stringWriter = new StringWriter();
        using var writer = new IndentedTextWriter(stringWriter, "    ");

        writer.WriteLine(AutoGeneratedHeader);
        writer.WriteLine("using System.Collections.Generic;");
        writer.WriteLine();
        writer.WriteLine("namespace Spectre.Console");
        writer.WriteLine("{");
        writer.Indent++;

        writer.WriteLine("internal static partial class ColorPalette");
        writer.WriteLine("{");
        writer.Indent++;

        // GenerateLegacyPalette - colors 0-7 (only primary, no aliases)
        EmitPaletteMethod(writer, "GenerateLegacyPalette", colors, 0, 7, null);

        writer.WriteLine();

        // GenerateStandardPalette - colors 8-15 (only primary, no aliases)
        EmitStandardPaletteMethod(writer, colors);

        writer.WriteLine();

        // GenerateEightBitPalette - colors 16-255 (only primary, no aliases)
        EmitEightBitPaletteMethod(writer, colors);

        writer.Indent--;
        writer.WriteLine("}");
        writer.Indent--;
        writer.WriteLine("}");

        return stringWriter.ToString();
    }

    private static void EmitPaletteMethod(IndentedTextWriter writer, string methodName, EquatableArray<ColorModel> colors, int startNumber, int endNumber, string? parameterName)
    {
        if (parameterName != null)
        {
            writer.WriteLine($"private static List<Color> {methodName}(IReadOnlyList<Color> {parameterName})");
        }
        else
        {
            writer.WriteLine($"private static List<Color> {methodName}()");
        }
        writer.WriteLine("{");
        writer.Indent++;

        if (parameterName != null)
        {
            writer.WriteLine($"return new List<Color>({parameterName})");
        }
        else
        {
            writer.WriteLine("return new List<Color>");
        }
        writer.WriteLine("{");
        writer.Indent++;

        // Filter to primary colors only (no aliases) within the range
        var paletteColors = colors
            .Where(c => !c.IsAlias && c.Number >= startNumber && c.Number <= endNumber)
            .OrderBy(c => c.Number)
            .ToArray();

        foreach (var color in paletteColors)
        {
            writer.WriteLine($"Color.{color.Name},");
        }

        writer.Indent--;
        writer.WriteLine("};");
        writer.Indent--;
        writer.WriteLine("}");
    }

    private static void EmitStandardPaletteMethod(IndentedTextWriter writer, EquatableArray<ColorModel> colors)
    {
        writer.WriteLine("private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)");
        writer.WriteLine("{");
        writer.Indent++;
        writer.WriteLine("return new List<Color>(legacy)");
        writer.WriteLine("{");
        writer.Indent++;

        // Colors 8-15 (only primary, no aliases)
        var standardColors = colors
            .Where(c => !c.IsAlias && c.Number >= 8 && c.Number <= 15)
            .OrderBy(c => c.Number)
            .ToArray();

        foreach (var color in standardColors)
        {
            writer.WriteLine($"Color.{color.Name},");
        }

        writer.Indent--;
        writer.WriteLine("};");
        writer.Indent--;
        writer.WriteLine("}");
    }

    private static void EmitEightBitPaletteMethod(IndentedTextWriter writer, EquatableArray<ColorModel> colors)
    {
        writer.WriteLine("private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)");
        writer.WriteLine("{");
        writer.Indent++;
        writer.WriteLine("return new List<Color>(standard)");
        writer.WriteLine("{");
        writer.Indent++;

        // Colors 16-255 (only primary, no aliases)
        var eightBitColors = colors
            .Where(c => !c.IsAlias && c.Number >= 16 && c.Number <= 255)
            .OrderBy(c => c.Number)
            .ToArray();

        foreach (var color in eightBitColors)
        {
            writer.WriteLine($"Color.{color.Name},");
        }

        writer.Indent--;
        writer.WriteLine("};");
        writer.Indent--;
        writer.WriteLine("}");
    }

    /// <summary>
    /// Emits the ColorTable.Generated.g.cs file content.
    /// </summary>
    public static string EmitColorTable(EquatableArray<ColorModel> colors)
    {
        using var stringWriter = new StringWriter();
        using var writer = new IndentedTextWriter(stringWriter, "    ");

        writer.WriteLine(AutoGeneratedHeader);
        writer.WriteLine("using System;");
        writer.WriteLine("using System.Collections.Generic;");
        writer.WriteLine();
        writer.WriteLine("namespace Spectre.Console");
        writer.WriteLine("{");
        writer.Indent++;

        writer.WriteLine("internal static partial class ColorTable");
        writer.WriteLine("{");
        writer.Indent++;

        writer.WriteLine("private static Dictionary<string, int> GenerateTable()");
        writer.WriteLine("{");
        writer.Indent++;
        writer.WriteLine("return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)");
        writer.WriteLine("{");
        writer.Indent++;

        // All colors including aliases, ordered by number then by alias status
        var orderedColors = colors
            .OrderBy(c => c.Number)
            .ThenBy(c => c.IsAlias)
            .ToArray();

        foreach (var color in orderedColors)
        {
            writer.WriteLine($"{{ \"{color.Name.ToLowerInvariant()}\", {color.Number} }},");
        }

        writer.Indent--;
        writer.WriteLine("};");
        writer.Indent--;
        writer.WriteLine("}");

        writer.Indent--;
        writer.WriteLine("}");
        writer.Indent--;
        writer.WriteLine("}");

        return stringWriter.ToString();
    }
}
