namespace InnovEnergy.Lib.Utils;


// TODO: discriminated union
public abstract record Result<T> 
{
    public sealed record Success(T      Value) : Result<T>;
    public sealed record Failure(String Error) : Result<T>;

    public Boolean IsError => this is Failure;
    public Boolean IsOk    => this is Success;
    
    public static implicit operator Result<T>(T t)      => new Success(t);
    public static implicit operator Result<T>(String e) => new Failure(e);
}

public abstract record Result<T, E>
{
    public sealed record Success(T Value) : Result<T, E>;
    public sealed record Failure(E Error) : Result<T, E>;

    public Boolean IsError => this is Failure;
    public Boolean IsOk    => this is Success;
    
    public static implicit operator Result<T, E>(T t) => new Success(t);
    public static implicit operator Result<T, E>(E e) => new Failure(e);
    
    public R Map<R>(Func<T, R> onSuccess, Func<E, R> onFailure) => this switch
    {
        Success s => onSuccess(s.Value),
        Failure f => onFailure(f.Error),
        _         => throw new ArgumentOutOfRangeException()
    };

    public Result<R, E> OnSuccess<R>(Func<T, R> onOk) => this switch
    {
        Success s => onOk(s.Value),
        Failure f => f.Error,
        _         => throw new ArgumentOutOfRangeException()
    };

    public Result<T, R> OnFailure<R>(Func<E, R> onError) => this switch
    {
        Success s => s.Value,
        Failure f => onError(f.Error),
        _         => throw new ArgumentOutOfRangeException()
    };
}

public static class Result
{
    public static Result<T, Exception> Try<T>(Func<T> func)
    {
        try
        {
            return func();
        }
        catch (Exception e)
        {
            return e;
        }
    }

    public static String? GetError<T>(this Result<T> r) 
    {
        return r is Result<T>.Failure err 
             ? err.Error 
             : null;
    }
}

public static class ResultClass
{
    public static T? GetValue<T>(this Result<T> r) where T : class
    {
        return r is Result<T>.Success ok 
             ? ok.Value 
             : null;
    }

    public static E? GetError<T, E>(this Result<T, E> r) where E : class
    {
        return r is Result<T, E>.Failure err
             ? err.Error
             : null;
    }
}

public static class ResultStruct
{
    public static T? GetValue<T>(this Result<T> r) where T : struct
    {
        return r is Result<T>.Success ok 
             ? ok.Value 
             : null;
    }
    
    public static E? GetError<T, E>(this Result<T, E> r) where E : struct
    {
        return r is Result<T,E>.Failure err 
             ? err.Error 
             : null;
    }
}