using System.Text.Json;
using System.Text.Json.Serialization;

namespace Toolkit.Foundation;

public class DictionaryStringObjectJsonConverter :
    JsonConverter<Dictionary<string, object?>>
{
    public override bool CanConvert(Type typeToConvert) =>
        typeToConvert == typeof(Dictionary<string, object>) ||
        typeToConvert == typeof(Dictionary<string, object?>);

    public override Dictionary<string, object?> Read(ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        Dictionary<string, object?> dictionary = [];

        if (reader.TokenType == JsonTokenType.StartObject)
        {
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return dictionary;
                }

                if (reader.TokenType is JsonTokenType.PropertyName)
                {
                    string? propertyName = reader.GetString();
                    if (!string.IsNullOrWhiteSpace(propertyName))
                    {
                        reader.Read();
                        dictionary.Add(propertyName!, ExtractValue(ref reader, options));
                    }
                }
            }
        }

        return dictionary;
    }

    public override void Write(Utf8JsonWriter writer,
        Dictionary<string, object?> value,
        JsonSerializerOptions options) =>
            JsonSerializer.Serialize(writer, (IDictionary<string, object?>)value, options);

    private object? ExtractValue(ref Utf8JsonReader reader,
        JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.String:
                if (reader.TryGetDateTime(out var date))
                {
                    return date;
                }
                return reader.GetString();

            case JsonTokenType.False:
                return false;

            case JsonTokenType.True:
                return true;

            case JsonTokenType.Null:
                return null;

            case JsonTokenType.Number:
                if (reader.TryGetInt64(out var result))
                {
                    return result;
                }
                return reader.GetDecimal();

            case JsonTokenType.StartObject:
                return Read(ref reader, null!, options);

            case JsonTokenType.StartArray:
                List<object?> list = [];
                while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
                {
                    list.Add(ExtractValue(ref reader, options));
                }
                return list;

            default:
                return default;
        }
    }
}