/// Utility class that simplifies handling errors.
///
/// Return a [Result] from a function to indicate success or failure.
///
/// A [Result] is either an [Ok] with a value of type [T]
/// or an [Err] with an [Exception].
///
/// Use [Result.ok] to create a successful result with a value of type [T].
/// Use [Result.error] to create an error result with an [Exception].
///
/// Evaluate the result using a switch statement:
/// ```dart
/// switch (result) {
///   case Ok(): {
///     print(result.value);
///   }
///   case Error(): {
///     print(result.error);
///   }
/// }
/// ```
sealed class Result<T> {
  const Result();

  /// Creates a successful [Result], completed with the specified [value].
  const factory Result.ok(T value) = Ok._;

  /// Creates an error [Result], completed with the specified [error].
  const factory Result.error(Exception error) = Err._;

  bool isOk() {
    return this is Ok;
  }

  T value() {
    Ok<T> ok = this as Ok<T>;
    return ok._value;
  }

  bool isErr() {
    return this is Err;
  }

  Exception error() {
    Err<T> err = this as Err<T>;
    return err._error;
  }
}

/// A successful [Result] with a returned [value].
final class Ok<T> extends Result<T> {
  const Ok._(this._value);

  /// The returned value of this result.
  final T _value;

  @override
  String toString() => 'Result<$T>.ok($_value)';
}

/// An error [Result] with a resulting [error].
final class Err<T> extends Result<T> {
  const Err._(this._error);

  /// The resulting error of this result.
  final Exception _error;

  @override
  String toString() => 'Result<$T>.error($_error)';
}
