using System.Text.Json;
using System.Text.Json.Serialization;
using SixLabors.ImageSharp;

namespace FrameProcessor.Domain;

/// <summary>
/// One palette entry mapping a display color (what the eye sees on the panel) to
/// a device color (the RGB value firmware expects in the input PNG).
/// Hex strings from configuration are parsed once into <see cref="Color"/> values;
/// the entry does not retain the original strings.
/// </summary>
[JsonConverter(typeof(PaletteEntryJsonConverter))]
public sealed record PaletteEntry(string Name, Color DisplayColor, Color DeviceColor);

internal sealed class PaletteEntryJsonConverter : JsonConverter<PaletteEntry>
{
    public override PaletteEntry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException("Palette entry must be a JSON object.");
        }

        string? name = null;
        Color? displayColor = null;
        Color? deviceColor = null;

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                if (name is null)
                {
                    throw new JsonException("Palette entry is missing required field 'name'.");
                }

                if (displayColor is null)
                {
                    throw new JsonException("Palette entry is missing required field 'color'.");
                }

                if (deviceColor is null)
                {
                    throw new JsonException("Palette entry is missing required field 'deviceColor'.");
                }

                return new PaletteEntry(name, displayColor.Value, deviceColor.Value);
            }

            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            var propertyName = reader.GetString();
            if (!reader.Read())
            {
                throw new JsonException("Unexpected end of palette entry.");
            }

            switch (propertyName)
            {
                case "name":
                    var n = reader.GetString();
                    if (string.IsNullOrWhiteSpace(n))
                    {
                        throw new JsonException("Palette entry 'name' must be a non-empty string.");
                    }

                    name = n;
                    break;
                case "color":
                    displayColor = ParseHex(reader.GetString(), "color");
                    break;
                case "deviceColor":
                    deviceColor = ParseHex(reader.GetString(), "deviceColor");
                    break;
                default:
                    reader.Skip();
                    break;
            }
        }

        throw new JsonException("Unexpected end of palette entry.");
    }

    public override void Write(Utf8JsonWriter writer, PaletteEntry value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("name", value.Name);
        writer.WriteString("color", ToRgbHex(value.DisplayColor));
        writer.WriteString("deviceColor", ToRgbHex(value.DeviceColor));
        writer.WriteEndObject();
    }

    private static Color ParseHex(string? s, string field)
    {
        if (string.IsNullOrWhiteSpace(s))
        {
            throw new JsonException($"Palette entry '{field}' must be a non-empty hex string.");
        }

        if (!Color.TryParseHex(s, out var color))
        {
            throw new JsonException($"'{s}' is not a valid hex color for '{field}'. Expected '#RRGGBB'.");
        }

        return color;
    }

    private static string ToRgbHex(Color color)
    {
        // ImageSharp's Color.ToHex() returns 8 characters (RRGGBBAA); SPEC writes 6-digit '#RRGGBB'.
        var hex = color.ToHex();
        return "#" + hex[..6];
    }
}
