// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Net.Http.Headers;

namespace TypeAgent.KnowPro;

public class Facet
{
    [JsonPropertyName("name")]
    public string Name { get; set; }

    [JsonPropertyName("value")]
    public IFacetValue Value { get; set; }

    public override string ToString()
    {
        return $"{this.Name}= \"{this.Value}\"";
    }

    public bool Match(Facet y)
    {
        ArgumentVerify.ThrowIfNull(y, nameof(y));

        if (!Name.Equals(y.Name, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        if (Value is Quantity qx)
        {
            return y.Value is Quantity qy && qx.Amount == qy.Amount && qx.Units == qy.Units;
        }
        else if (Value is Quantifier qf)
        {
            return y.Value is Quantifier qz && qf.Amount == qz.Amount && qf.Units == qz.Units;
        }
        else
        {
            return Value == y.Value;
        }
    }

    // TODO: equality operators (including all IFacenValues)
    //public static bool operator ==(Facet x, Facet y)
    //{
    //    if (x == null && y == null)
    //    {
    //        return true;
    //    }
    //    else if (x == null || y == null)
    //    {
    //        return false;
    //    }

    //    if (!x.Name.Equals(y.Name, StringComparison.OrdinalIgnoreCase))
    //    {
    //        return false;
    //    }

    //    return x.Value == y.Value;
    //}

    //public static bool operator !=(Facet x, Facet y)
    //{
    //    return !(x == y);
    //}
}

public interface IFacetValue
{
    [JsonIgnore]
    FacetValueType ValueType { get; }
}

public enum FacetValueType
{
    String,
    Number,
    Boolean,
    Quantity,
    Quantifier
}

public class StringFacetValue : IFacetValue
{
    public string Value { get; }

    [JsonConstructor]
    public StringFacetValue(string value)
    {
        Value = value;
    }

    [JsonIgnore]
    public FacetValueType ValueType => FacetValueType.String;

    public override string ToString() => Value;
}

public class NumberFacetValue : IFacetValue
{
    public double Value { get; }

    [JsonConstructor]
    public NumberFacetValue(double value)
    {
        Value = value;
    }

    [JsonIgnore]
    public FacetValueType ValueType => FacetValueType.Number;

    public override string ToString() => Value.ToString("g");
}

public class BooleanFacetValue : IFacetValue
{
    public bool Value { get; }

    [JsonConstructor]
    public BooleanFacetValue(bool value)
    {
        Value = value;
    }

    [JsonIgnore]
    public FacetValueType ValueType => FacetValueType.Boolean;

    public override string ToString() => Value.ToString();
}

public readonly struct Quantity : IFacetValue
{
    [JsonPropertyName("amount")]
    public double Amount { get; }

    [JsonPropertyName("units")]
    public string Units { get; }

    [JsonConstructor]
    public Quantity(double amount, string units)
    {
        Amount = amount;
        Units = units;
    }

    [JsonIgnore]
    public FacetValueType ValueType => FacetValueType.Quantity;

    public override string ToString() => $"{Amount} {Units}";
}
public readonly struct Quantifier : IFacetValue
{
    [JsonPropertyName("amount")]
    public string Amount { get; }

    [JsonPropertyName("units")]
    public string Units { get; }

    [JsonConstructor]
    public Quantifier(string amount, string units)
    {
        Amount = amount;
        Units = units;
    }

    [JsonIgnore]
    public FacetValueType ValueType => FacetValueType.Quantifier;

    public override string ToString() => $"{Amount} {Units}";
}



public class FacetValueJsonConverter : JsonConverter<IFacetValue>
{
    public override IFacetValue? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
        {
            return null;
        }

        switch (reader.TokenType)
        {
            default:
                break;

            case JsonTokenType.String:
                return new StringFacetValue(reader.GetString() ?? "");

            case JsonTokenType.Number:
                return new NumberFacetValue(reader.GetDouble());
            case JsonTokenType.True:
            case JsonTokenType.False:
                return new BooleanFacetValue(reader.GetBoolean());
            case JsonTokenType.StartObject:
                // Try to parse as Quantity
                using (var doc = JsonDocument.ParseValue(ref reader))
                {
                    var root = doc.RootElement;
                    if (root.TryGetProperty("amount", out var amountProp) &&
                        root.TryGetProperty("units", out var unitsProp) &&
                        amountProp.ValueKind == JsonValueKind.Number &&
                        unitsProp.ValueKind == JsonValueKind.String)
                    {
                        return new Quantity(amountProp.GetDouble(), unitsProp.GetString()!);
                    }
                    else if (root.TryGetProperty("amount", out var amountStringProp) &&
                        root.TryGetProperty("units", out var unitsProp2) &&
                        amountStringProp.ValueKind == JsonValueKind.String && unitsProp2.ValueKind == JsonValueKind.String)
                    {
                        return new Quantifier(amountStringProp.GetString(), unitsProp2.GetString()!);
                    }
                }
                break;
        }
        return null;
    }

    public override void Write(Utf8JsonWriter writer, IFacetValue value, JsonSerializerOptions options)
    {
        switch (value)
        {
            case StringFacetValue s:
                writer.WriteStringValue(s.Value);
                break;
            case NumberFacetValue n:
                writer.WriteNumberValue(n.Value);
                break;
            case BooleanFacetValue b:
                writer.WriteBooleanValue(b.Value);
                break;
            case Quantity q:
                writer.WriteStartObject();
                writer.WriteNumber("amount", q.Amount);
                writer.WriteString("units", q.Units);
                writer.WriteEndObject();
                break;
            case Quantifier qq:
                writer.WriteStartObject();
                writer.WriteString("amount", qq.Amount);
                writer.WriteString("units", qq.Units);
                writer.WriteEndObject();
                break;
            default:
                throw new JsonException("Unknown IFacetValue type.");
        }
    }
}
