Typed Error Dart

abstract base class ViewModel<State, Event> {
  ViewModel(Result<State, Exception> state) {
    if (state is Ok) {
      _state.add(state);
    } else {
      _state.addError(state);
    }
  }

  final _state = BehaviorSubject<Result<State, Exception>>();
  final _event = BehaviorSubject<Event>();

  void emit(Result<State, Exception> state) {
    if (state is Ok) {
      _state.add(state);
    } else {
      _state.addError(state);
    }
  }

  void add(Event event) {
    _event.add(event);
  }

  Future<void> dispose() async {
    await Future.wait(<Future<void>>[
      _event.close(),
      _state.close(),
    ]);
  }

  Stream<Result<State, Exception>> get stream => _state.stream;

  Stream<Event> get event => _event.stream;

  State get state => _state.value.unwrap;
}

extension type const Option<T>._(T? _) {
  bool get isNone => runtimeType == Null && true;

  T get unwrap {
    if (!isNone) {
      return _!;
    }
    throw NullException();
  }

  T unwrapOr(T fallback) {
    if (!isNone) {
      return _!;
    }
    return fallback;
  }

  T unwrapOrElse(Never Function() handleNone) => switch (this) {
        Some<T>() => unwrap,
        None() => handleNone(),
      };

  R match<R>({
    required R Function(T ok) some,
    required Never Function() none,
  }) =>
      switch (this) {
        Some<T>(:final T _value) => some(_value),
        None() => none(),
      };
}

extension type const Some<T>._(T _value) implements Option<T> {
  const Some(T value) : this._(value);
}

extension type const None._(Null _) implements Option<Never> {
  const None() : this._(null);
}

sealed class Result<T, E extends Exception> {
  const Result._();

  bool get isErr => this is Err<Exception>;

  R bind<R>() {
    return this is Ok<T>
        ? (this as Ok<T>)._value as R
        : (this as Err<Exception>)._value as R;
  }
}

final class Ok<T> extends Result<T, Never> {
  const Ok(T value)
      : _value = value,
        super._();

  final T _value;
}

final class Err<E extends Exception> extends Result<Never, E> {
  const Err(E value)
      : _value = value,
        super._();

  final E _value;
}

extension ResultMethods<T> on Result<T, Exception> {
  T get unwrap => switch (this) {
        Ok<T>(:final T _value) => _value,
        Err<Exception>(:final Exception _value) => throw _value,
        _ => throw Unreachable(),
      };

  T unwrapOr(T fallback) => switch (this) {
        Ok<T>(:final T _value) => _value,
        Err<Exception>() => fallback,
        _ => throw Unreachable()
      };

  T unwrapOrElse(T Function(Exception e) handleErr) => switch (this) {
        Ok<T>(:final T _value) => _value,
        Err<Exception>(:final Exception _value) => handleErr(_value),
        _ => throw Unreachable(),
      };

  R match<R>({
    required R Function(T ok) ok,
    required R Function(Exception e) err,
  }) =>
      switch (this) {
        Ok<T>(:final T _value) => ok(_value),
        Err<Exception>(:final Exception _value) => err(_value),
        _ => throw Unreachable(),
      };
}

extension type const FromException(Exception _e) {
  Type get type => _e.runtimeType;

  String get message => _e.toString().split(':').last.trimLeft();
}

extension type const FromErr(Result<Object, Exception> _e) {
  Option<Type> get type {
    if (_e case Err<Exception>(:final Exception _value)) {
      return Some<Type>(FromException(_value).type);
    }
    return const None();
  }

  Option<String> get message {
    if (_e case Err<Exception>(:final Exception _value)) {
      return Some<String>(FromException(_value).message);
    }
    return const None();
  }
}

extension type const TrySync<T, E extends Exception>(T Function() _) {
  Result<T, E> get _wrap {
    try {
      return Ok<T>(_());
    } on Exception catch (e) {
      return Err<E>(e as E);
    }
  }

  T get unwrap => _wrap.unwrap;
  T unwrapOr(T fallback) => _wrap.unwrapOr(fallback);
}

extension type const TryFuture<T, E extends Exception>(Future<T> Function() _) {
  Future<Result<T, E>> get _wrap async {
    try {
      return Ok<T>(await _());
    } on Exception catch (e) {
      return Err<E>(e as E);
    }
  }

  Future<T> get unwrap async => (await _wrap).unwrap;

  Future<T> unwrapOr(T fallback) async => (await _wrap).unwrapOr(fallback);
}

extension type const TryFutures<T, E extends Exception>(Iterable<Future<T>> _) {
  Future<Result<Iterable<T>, E>> _wrap() async {
    Future<Iterable<T>> resolver(Iterable<Future<T>> promises) async {
      final resolved = <Future<T>>[];

      final results = await Future.wait(
        promises.map(
            (p) => p.then((value) => true).catchError((error, stk) => false)),
        eagerError: false,
      );

      for (var i = 0; i < results.length; i++) {
        if (results[i]) {
          resolved.add(promises.elementAt(i));
        }
      }

      return await Future.wait(resolved);
    }

    try {
      return Ok(await resolver(_));
    } on Exception catch (e) {
      return Err<E>(e as E);
    }
  }

  Future<Iterable<T>> get unwrap async => (await _wrap()).unwrap;
  Future<Iterable<T>> unwrapOr(Iterable<T> fallback) async =>
      (await _wrap()).unwrapOr(fallback);
}