namespace ZB.MOM.WW.ScadaBridge.Commons.Types;

public sealed class Result<T>
{
    private readonly T? _value;
    private readonly string? _error;

    private Result(T value)
    {
        _value = value;
        _error = null;
        IsSuccess = true;
    }

    private Result(string error)
    {
        // A failed Result must always carry a usable message — Result is the
        // system-wide error-handling type, and consumers log/display Error directly.
        ArgumentException.ThrowIfNullOrWhiteSpace(error);
        _value = default;
        _error = error;
        IsSuccess = false;
    }

    /// <summary>True when the result represents a successful outcome.</summary>
    public bool IsSuccess { get; }

    /// <summary>True when the result represents a failure outcome.</summary>
    public bool IsFailure => !IsSuccess;

    /// <summary>The success value; throws <see cref="InvalidOperationException"/> when <see cref="IsFailure"/> is true.</summary>
    public T Value => IsSuccess
        ? _value!
        : throw new InvalidOperationException("Cannot access Value on a failed Result. Error: " + _error);

    /// <summary>The error message; throws <see cref="InvalidOperationException"/> when <see cref="IsSuccess"/> is true.</summary>
    public string Error => IsFailure
        ? _error!
        : throw new InvalidOperationException("Cannot access Error on a successful Result.");

    /// <summary>Creates a successful result carrying the given value.</summary>
    /// <param name="value">The success value.</param>
    /// <returns>A successful <see cref="Result{T}"/>.</returns>
    public static Result<T> Success(T value) => new(value);

    /// <summary>
    /// Creates a failed result carrying the given error message.
    /// </summary>
    /// <param name="error">Non-blank error message describing the failure.</param>
    /// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
    /// <exception cref="ArgumentException"><paramref name="error"/> is empty or whitespace.</exception>
    /// <returns>A failed <see cref="Result{T}"/> carrying the error message.</returns>
    public static Result<T> Failure(string error) => new(error);

    /// <summary>Pattern-matches the result, invoking either the success or failure delegate.</summary>
    /// <typeparam name="TResult">The return type of both delegates.</typeparam>
    /// <param name="onSuccess">Delegate invoked with the value when the result is successful.</param>
    /// <param name="onFailure">Delegate invoked with the error message when the result is a failure.</param>
    /// <returns>The value returned by the invoked delegate.</returns>
    public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure) =>
        IsSuccess ? onSuccess(_value!) : onFailure(_error!);
}
