import 'dart:async';

typedef FutureCallback<T> = Future<T> Function();

typedef IntervalCallback<T> = Future<T> Function(int times);

typedef CancelCallback<T> = void Function();

typedef FutureCompleter<T> = void Function(Completer<T> completer);

typedef Predicate<T> = bool Function(T? value);

class AsyncUtil {
  AsyncUtil._();

  static CancelableFuture<T> retryWithExponentialBackoff<T>(
      FutureCallback<T> callback, int maxRetry,
      {Predicate<dynamic>? predicate}) {
    const Duration initialInterval = Duration(seconds: 1);
    int retryCount = 0;
    Timer? timer;

    void attempt(Completer<T> completer) {
      callback().then((value) {
        if (!completer.isCompleted) {
          completer.complete(value);
        }
      }).catchError((error) {
        if (retryCount < maxRetry && (predicate == null || predicate(error))) {
          retryCount++;
          Duration nextInterval = initialInterval * (1 << (retryCount - 1));
          timer = Timer(nextInterval, () => attempt(completer));
        } else {
          if (!completer.isCompleted) {
            completer.completeError(error);
          }
        }
      });
    }

    return CancelableFuture<T>((completer) {
      attempt(completer);
    }, () {
      timer?.cancel();
    });
  }

  static CancelableFuture<T> retry<T>(
      FutureCallback<T> callback, Duration interval,
      {Duration? timeout, int? maxRetry, Predicate<dynamic>? predicate}) {
    int retryCount = 0;
    Timer? timer;
    Timer? timeoutTimer;

    void attempt(Completer<T> completer) {
      callback().then((value) {
        if (!completer.isCompleted) {
          completer.complete(value);
        }
      }).catchError((error) {
        if ((maxRetry == null || maxRetry <= 0 || retryCount < maxRetry) &&
            (predicate == null || predicate(error))) {
          retryCount++;
          timer = Timer(interval, () => attempt(completer));
        } else {
          if (!completer.isCompleted) {
            completer.completeError(error);
          }
        }
      });
    }

    return CancelableFuture<T>((completer) {
      if (timeout != null) {
        timeoutTimer = Timer(timeout, () {
          if (!completer.isCompleted) {
            completer.completeError(TimeoutException('Operation timed out'));
          }
        });
      }
      attempt(completer);
    }, () {
      timer?.cancel();
      timeoutTimer?.cancel();
    });
  }

  static CancelableFuture<T> delay<T>(
      FutureCallback<T> callback, Duration interval) {
    Timer? timer;

    return CancelableFuture<T>((completer) {
      timer = Timer(interval, () {
        callback().then((value) {
          if (!completer.isCompleted) {
            completer.complete(value);
          }
        }).catchError((error) {
          if (!completer.isCompleted) {
            completer.completeError(error);
          }
        });
      });
    }, () {
      timer?.cancel();
    });
  }

  static StreamController<T> interval<T>(
      IntervalCallback<T> callback, Duration interval, int times,
      {Duration? delay}) {
    Timer? timer;
    StreamController<T> controller = StreamController<T>(onCancel: () {
      timer?.cancel();
    });
    int count = 0;
    void tick() {
      callback(count).then((value) {
        controller.add(value);
        count++;
        if (count < times) {
          timer = Timer(interval, tick);
        } else {
          controller.close();
        }
      }).catchError((error) {
        controller.addError(error);
        controller.close();
      });
    }

    if (delay != null && delay > Duration.zero) {
      timer = Timer(delay, tick);
    } else {
      tick();
    }
    return controller;
  }
}

abstract interface class Cancelable {
  void cancel();
}

class CancelableFuture<T> implements Future<T>, Cancelable {
  final Completer<T> _completer = Completer<T>();

  final CancelCallback? _cancelable;

  CancelableFuture(FutureCompleter<T> completer, this._cancelable) {
    completer.call(_completer);
  }

  @override
  void cancel() {
    _cancelable?.call();
    if (!_completer.isCompleted) {
      _completer.completeError(CancelledError());
    }
  }

  @override
  Stream<T> asStream() => _completer.future.asStream();

  @override
  Future<T> catchError(Function onError, {bool Function(Object error)? test}) =>
      _completer.future.catchError(onError, test: test);

  @override
  Future<R> then<R>(FutureOr<R> Function(T value) onValue,
      {Function? onError}) =>
      _completer.future.then(onValue, onError: onError);

  @override
  Future<T> timeout(Duration timeLimit, {FutureOr<T> Function()? onTimeout}) =>
      _completer.future.timeout(timeLimit, onTimeout: onTimeout);

  @override
  Future<T> whenComplete(FutureOr<void> Function() action) =>
      _completer.future.whenComplete(action);
}

class CancelledError extends Error {
  @override
  String toString() {
    return 'Operation was cancelled';
  }
}

extension CancelableFutureExtension<T> on Future<T> {
  CancelableFuture<T> asCancelable(
      FutureCompleter completer, CancelCallback? cancelable) {
    CancelableFuture<T> cancelableFuture =
    CancelableFuture(completer, cancelable);
    then((value) {
      if (!cancelableFuture._completer.isCompleted) {
        cancelableFuture._completer.complete(value);
      }
    }).catchError((error) {
      if (!cancelableFuture._completer.isCompleted) {
        cancelableFuture._completer.completeError(error);
      }
    });
    return cancelableFuture;
  }
}
