// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

// ignore_for_file: avoid_positional_boolean_parameters

part of 'either_base.dart';

/// {@template result_exception}
/// [ResultException] is sometimes threw by [Result] objects.
///
/// ```dart
/// throw ResultException('Emergency failure!');
/// ```
/// {@endtemplate}
class ResultException extends _EitherBaseException {
  /// {@macro result_exception}
  const ResultException(super.message);

  @override
  String toString() => 'ResultException: $message';
}

/// {@template result}
/// [Result] type is coming from functional languages where exceptions are
/// (rightfully) considered a side-effect, and therefore not appropriate to
/// pass domain errors. Mind the difference between different kinds of errors:
/// Some of them belong to domain, others don't.
///
/// *E.g. null reference exception or index out of bounds
/// are not related to domain - they rather indicate a defect.*
///
/// Either is defined as a generic type with two branches
/// - success with [T]
/// - failure with [E]
///
/// It can appear in two forms, where
/// it contains an object of [Ok], or where it contains an object
/// of [Err]. It cannot appear in both states at once, or in none of them.
/// Therefore, if one possesses an [Result] instance, it either contains a
/// successfully produced result, or contains an error object.
/// {@endtemplate}
abstract class Result<T, E> extends _EitherBase<T, E> {
  /// {@macro result}
  const Result._();

  /// Represents the left side of [Result] class.
  bool get isOk => _isLeft;

  /// Represents the right side of [Result] class.
  bool get isErr => _isRight;

  /// Get nullable [Ok] value, and discarding the error, if any.
  T? get ok => _left;

  /// Get nullable [Err] value, and discarding the success value, if any.
  E? get err => _right;

  /// Get [U] value, may throw an exception.
  U unwrap<U>() => _unwrap<U>();

  /// Get **async** [U] value, may throw an exception.
  Future<U> unwrapAsync<U>() => _unwrapAsync<U>();

  /// Fold [Ok] and [Err] into the value of one type
  U fold<U>(
    U Function(T value) valueTransformer,
    U Function(E error) errorTransformer,
  ) =>
      _fold(
        (left) => valueTransformer(left),
        (right) => errorTransformer(right),
      );

  /// Fold [Ok] and [Err] **asynchronously** into the value of one type
  Future<U> foldAsync<U>(
    Future<U> Function(T value) valueTransformer,
    Future<U> Function(E error) errorTransformer,
  ) =>
      _foldAsync(
        (left) => valueTransformer(left),
        (right) => errorTransformer(right),
      );

  /// Swap [Ok] and [Err]
  Result<E, T> swap() => fold(Err.new, Ok.new);

  /// Returns [res] if the [Result] is [Ok], otherwise returns
  /// the [Err] value of this.
  Result<U, E> and<U>(Result<U, E> res) => _and<U>(res) as Result<U, E>;

  /// Returns [res] if the [Result] is [Err], otherwise returns
  /// the [Ok] value of this.
  Result<T, F> or<F>(Result<T, F> res) => _or<F>(res) as Result<T, F>;

  /// Returns true if the result is an [Ok] or [Err] value containing
  /// the given value/error.
  bool contains<U>(U x) => _contains(x);

  /// Returns the contained [Ok] value. Throw [ResultException] on [Err] with
  /// its content.
  T expect(String msg) => _expect(msg);

  /// Returns the contained [Err] value. Throw [ResultException] on [Ok] with
  /// its content.
  E expectErr(String msg) => _expectErr(msg);

  /// Maps a [Result<T, E>] to [Result<U, E>] by applying a function to a
  /// contained [Ok] value, leaving an [Err] value untouched.
  Result<U, E> map<U>(U Function(T value) mapper) =>
      _map(mapper) as Result<U, E>;

  /// Maps a [Result<T, E>] to [Result<U, E>] by applying an **async** function
  /// to a contained [Ok] value, leaving an [Err] value untouched.
  Future<Result<U, E>> mapAsync<U>(Future<U> Function(T value) mapper) =>
      _mapAsync(mapper) as Future<Result<U, E>>;

  /// Maps a [Result<T, E>] to [Result<T, F>] by applying a function to a
  /// contained [Err] value, leaving an [Ok] value untouched.
  Result<T, F> mapErr<F>(F Function(E error) mapper) =>
      _mapErr(mapper) as Result<T, F>;

  /// Maps a [Result<T, E>] to [Result<U, E>] by applying an **async** function
  /// to a contained [Err] value, leaving an [Ok] value untouched.
  Future<Result<T, F>> mapErrAsync<F>(Future<F> Function(E error) mapper) =>
      _mapErrAsync(mapper) as Future<Result<T, F>>;

  /// Transforms a [Result<T, E>] to [Result<U, F>] by applying functions to
  /// contained [Ok] and [Err] values.
  Result<U, F> either<U, F>(
    U Function(T value) valueTransformer,
    F Function(E error) errorTransformer,
  ) =>
      _either<U, F>(valueTransformer, errorTransformer) as Result<U, F>;

  /// Transforms a [Result<T, E>] to [Result<U, F>] by applying **async**
  /// functions to contained [Ok] and [Err] values.
  Future<Result<U, F>> eitherAsync<U, F>(
    Future<U> Function(T value) valueTransformer,
    Future<F> Function(E error) errorTransformer,
  ) =>
      _eitherAsync<U, F>(valueTransformer, errorTransformer)
          as Future<Result<U, F>>;

  /// Constructs a new [Result] from a function that might throw
  static Result<T, E> tryCatch<T, E, Error extends Object>(
    T Function() tryFn,
    E Function(Error error) onError,
  ) {
    try {
      return Ok(tryFn());
    } on Error catch (e) {
      return Err(onError(e));
    }
  }

  /// Constructs a new [Result] from an **async** function that might throw
  static Future<Result<T, E>> tryCatchAsync<T, E, Error extends Object>(
    Future<T> Function() tryFn,
    E Function(Error error) onError,
  ) async {
    try {
      return Ok(await tryFn());
    } on Error catch (e) {
      return Err(onError(e));
    }
  }

  /// If the condition is satify then return [value] in
  /// [Ok] else [error] in [Err]
  static Result<T, E> conditional<T, E>(
    bool test,
    T value,
    E error,
  ) =>
      test ? Ok(value) : Err(error);

  /// If the condition is satify then return *command* [value]
  /// in [Ok] else [error] in [Err]
  static Result<T, E> conditionalLazy<T, E>(
    bool test,
    T Function() value,
    E Function() error,
  ) =>
      test ? Ok(value()) : Err(error());
}

/// {@template ok}
/// Contains the success value of a [Result]
///
/// {@macro result}
/// {@endtemplate}
class Ok<T, E> extends Result<T, E> with _Left<T, E> {

  /// {@macro ok}
  const Ok(this.value) : super._();
  final T value;

  @override
  U _fold<U>(U Function(T left) fnL, U Function(E right) fnR) => fnL(value);

  @override
  Future<U> _foldAsync<U>(
    Future<U> Function(T left) fnL,
    Future<U> Function(E right) fnR,
  ) =>
      fnL(value);

  @override
  Result<U, E> _and<U>(_EitherBase<U, E> res) => res as Result<U, E>;

  @override
  Result<T, F> _or<F>(_EitherBase<T, F> res) => this as Result<T, F>;

  @override
  bool _contains<U>(U x) => value == x;

  @override
  T _expect(String msg) => value;

  @override
  E _expectErr(String msg) => throw ResultException('$msg: $value');

  @override
  Result<U, E> _map<U>(U Function(T value) mapper) => Ok(mapper(value));

  @override
  Future<Result<U, E>> _mapAsync<U>(Future<U> Function(T value) mapper) =>
      mapper(value).then(Ok.new);

  @override
  Result<T, F> _mapErr<F>(F Function(E error) mapper) => Ok(value);

  @override
  Future<Result<T, F>> _mapErrAsync<F>(Future<F> Function(E error) mapper) =>
      Future.value(Ok(value));

  @override
  Result<U, F> _either<U, F>(
    U Function(T value) fnL,
    F Function(E error) fnR,
  ) =>
      Ok(fnL(value));

  @override
  Future<Result<U, F>> _eitherAsync<U, F>(
    Future<U> Function(T value) fnL,
    Future<F> Function(E error) fnR,
  ) =>
      fnL(value).then(Ok.new);
}

/// {@template err}
/// Contains the error value of a [Result]
///
/// {@macro result}
/// {@endtemplate}
class Err<T, E> extends Result<T, E> with _Right<T, E> {

  /// {@macro err}
  const Err(this.error) : super._();
  final E error;

  @override
  U _fold<U>(U Function(T left) fnL, U Function(E right) fnR) => fnR(error);

  @override
  Future<U> _foldAsync<U>(
    Future<U> Function(T left) fnL,
    Future<U> Function(E right) fnR,
  ) =>
      fnR(error);

  @override
  Result<U, E> _and<U>(_EitherBase<U, E> res) => this as Result<U, E>;
  @override
  Result<T, F> _or<F>(_EitherBase<T, F> res) => res as Result<T, F>;

  @override
  bool _contains<U>(U x) => error == x;

  @override
  T _expect(String msg) => throw ResultException('$msg: $error');

  @override
  E _expectErr(String msg) => error;

  @override
  Result<U, E> _map<U>(U Function(T value) mapper) => Err(error);

  @override
  Future<Result<U, E>> _mapAsync<U>(Future<U> Function(T value) mapper) =>
      Future.value(Err(error));

  @override
  Result<T, F> _mapErr<F>(F Function(E error) mapper) => Err(mapper(error));

  @override
  Future<Result<T, F>> _mapErrAsync<F>(Future<F> Function(E error) mapper) =>
      mapper(error).then(Err.new);

  @override
  Result<U, F> _either<U, F>(
    U Function(T value) fnL,
    F Function(E error) fnR,
  ) =>
      Err(fnR(error));

  @override
  Future<Result<U, F>> _eitherAsync<U, F>(
    Future<U> Function(T value) fnL,
    Future<F> Function(E error) fnR,
  ) =>
      fnR(error).then(Err.new);
}
