using System;

namespace TweetLib.Utils.Data {
	/// <summary>
	/// Represents a result which either has a value of type <typeparamref name="T"/>, or an <see cref="Exception"/>.
	/// </summary>
	public sealed class Result<T> {
		/// <summary>
		/// Determines whether the <see cref="Result{T}"/> has a value.
		/// </summary>
		public bool HasValue => exception == null;

		/// <summary>
		/// Returns the value, or throws <see cref="InvalidOperationException"/> if no value is present.
		/// </summary>
		public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");

		/// <summary>
		/// Returns the <see cref="Exception"/>, or throws <see cref="InvalidOperationException"/> if this <see cref="Result{T}"/> has a value.
		/// </summary>
		public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");

		private readonly T value;
		private readonly Exception? exception;

		/// <summary>
		/// Initializes a <see cref="Result{T}"/> with a value.
		/// </summary>
		public Result(T value) {
			this.value = value;
			this.exception = null;
		}

		/// <summary>
		/// Initializes a <see cref="Result{T}"/> with an exception.
		/// </summary>
		public Result(Exception exception) {
			this.value = default!;
			this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
		}

		/// <summary>
		/// Executes one of the two provided <see cref="Action{T}"/> parameters depending on the state of the <see cref="Result{T}"/>.
		/// </summary>
		/// <param name="onSuccess">Executed if this <see cref="Result{T}"/> has a value.</param>
		/// <param name="onException">Executed if this <see cref="Result{T}"/> has an exception.</param>
		public void Handle(Action<T> onSuccess, Action<Exception> onException) {
			if (HasValue) {
				onSuccess(value);
			}
			else {
				onException(exception!);
			}
		}

		/// <summary>
		/// If this <see cref="Result{T}"/> has a value, applies the <paramref name="mapper"/> function to it and returns a new <see cref="Result{T}"/> with the resulting value.
		/// If this <see cref="Result{T}"/> has an exception, returns a <see cref="Result{T}"/> with the same exception but with type <typeparamref name="R"/>.
		/// </summary>
		public Result<R> Select<R>(Func<T, R> mapper) {
			return HasValue ? new Result<R>(mapper(value)) : new Result<R>(exception!);
		}
	}
}