Primitives/Optional.cs
using System;
using System.Text.Json.Serialization;

namespace ExtendedBox.Primitives;

public readonly struct Optional<T>
{
    public static readonly Optional<T> Empty = new(default!, false);

    public T Value { get; }
    public bool HasValue { get; }


    [JsonConstructor]
    private Optional(T value, bool hasValue)
    {
        Value = hasValue ? value : default!;
        HasValue = hasValue;
    }

    public Optional(T value) : this(value, true)
    {
    }

    public static Optional<T> FromNullable<V>(V? value) where V : struct, T
    {
        if(value is null)
            return Empty;
        return new(value.Value);
    }

    public static Optional<T> FromNullable(T? value)
    {
        if(value is null)
            return Empty;
        return new(value);
    }

    public T GetValueOrDefault(T defaultValue = default!) => HasValue ? Value : defaultValue;

    public static explicit operator T(Optional<T> optional)
    {
        if(!optional.HasValue)
            throw new InvalidCastException($"Given {optional.GetType().Name} has not value.");

        return optional.Value;
    }

    public static implicit operator Optional<T>(T value) => new(value);

    public static bool operator ==(Optional<T> a, Optional<T> b) => a.Equals(b);
    public static bool operator !=(Optional<T> a, Optional<T> b) => !a.Equals(b);
    public static bool operator ==(Optional<T> a, T b) => a.Equals(b);
    public static bool operator !=(Optional<T> a, T b) => !a.Equals(b);
    public static bool operator ==(T a, Optional<T> b) => b.Equals(a);
    public static bool operator !=(T a, Optional<T> b) => !b.Equals(a);

    public bool Equals(T other)
    {
        if(!HasValue)
            return false;

        return Equals(Value, other);
    }
    public bool Equals(Optional<T> other)
    {
        if(HasValue != other.HasValue)
            return false;

        if(!HasValue)
            return true;

        return Equals(other.Value);
    }
    public override bool Equals(object? other)
    {
        if(other is Optional<T> otherOptional)
            return Equals(otherOptional);

        if(!HasValue)
            return false;

        if(other is T || other is null)
            return Equals(Value, other);

        return false;
    }
    public override int GetHashCode() => HashCode.Combine(HasValue, Value);

    public override string ToString() => HasValue ? (Value?.ToString() ?? "null") : "Empty";
}