class RetryOrchestrator {

  public static void maybeRetry(final CoreContext ctx, final Request<? extends Response> request,
                                final RetryReason reason) {
    if (request.completed()) {
      return;
    }

    if (reason.alwaysRetry()) {
      retryWithDuration(ctx, request, controlledBackoff(request.context().retryAttempts()), reason);
      return;
    }

    request.retryStrategy().shouldRetry(request, reason).whenComplete((retryAction, throwable) -> {
      if (throwable != null) {
        ctx.environment().eventBus().publish(
          new RequestNotRetriedEvent(Event.Severity.INFO, request.getClass(), request.context(), reason, throwable)
        );
      }

      Optional<Duration> duration = retryAction.duration();
      if (duration.isPresent()) {
        final Duration cappedDuration = capDuration(duration.get(), request);
        retryWithDuration(ctx, request, cappedDuration, reason);
      } else {
        // unmonitored request's severity is downgraded to debug to not spam the info-level logs
        Event.Severity severity = request instanceof UnmonitoredRequest ? Event.Severity.DEBUG : Event.Severity.INFO;
        ctx.environment().eventBus().publish(
          new RequestNotRetriedEvent(severity, request.getClass(), request.context(), reason, null)
        );
        request.cancel(CancellationReason.noMoreRetries(reason));
      }
    });
  }

}