class ServiceConfigInterceptor implements ClientInterceptor {

  @SuppressWarnings("BetaApi") // Verify is stabilized since Guava v24.0
  @Override
  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
    if (retryEnabled) {
      if (nameResolveComplete) {
        final RetryPolicy retryPolicy = getRetryPolicyFromConfig(method);
        final class ImmediateRetryPolicyProvider implements RetryPolicy.Provider {
          @Override
          public RetryPolicy get() {
            return retryPolicy;
          }
        }

        final HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method);
        final class ImmediateHedgingPolicyProvider implements HedgingPolicy.Provider {
          @Override
          public HedgingPolicy get() {
            return hedgingPolicy;
          }
        }

        verify(
            retryPolicy.equals(RetryPolicy.DEFAULT) || hedgingPolicy.equals(HedgingPolicy.DEFAULT),
            "Can not apply both retry and hedging policy for the method '%s'", method);

        callOptions = callOptions
            .withOption(RETRY_POLICY_KEY, new ImmediateRetryPolicyProvider())
            .withOption(HEDGING_POLICY_KEY, new ImmediateHedgingPolicyProvider());
      } else {
        final class DelayedRetryPolicyProvider implements RetryPolicy.Provider {
          /**
           * Returns RetryPolicy.DEFAULT if name resolving is not complete at the moment the method
           * is invoked, otherwise returns the RetryPolicy computed from service config.
           *
           * <p>Note that this method is used no more than once for each call.
           */
          @Override
          public RetryPolicy get() {
            if (!nameResolveComplete) {
              return RetryPolicy.DEFAULT;
            }
            return getRetryPolicyFromConfig(method);
          }
        }

        final class DelayedHedgingPolicyProvider implements HedgingPolicy.Provider {
          /**
           * Returns HedgingPolicy.DEFAULT if name resolving is not complete at the moment the
           * method is invoked, otherwise returns the HedgingPolicy computed from service config.
           *
           * <p>Note that this method is used no more than once for each call.
           */
          @Override
          public HedgingPolicy get() {
            if (!nameResolveComplete) {
              return HedgingPolicy.DEFAULT;
            }
            HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method);
            verify(
                hedgingPolicy.equals(HedgingPolicy.DEFAULT)
                    || getRetryPolicyFromConfig(method).equals(RetryPolicy.DEFAULT),
                "Can not apply both retry and hedging policy for the method '%s'", method);
            return hedgingPolicy;
          }
        }

        callOptions = callOptions
            .withOption(RETRY_POLICY_KEY, new DelayedRetryPolicyProvider())
            .withOption(HEDGING_POLICY_KEY, new DelayedHedgingPolicyProvider());
      }
    }

    MethodInfo info = getMethodInfo(method);
    if (info == null) {
      return next.newCall(method, callOptions);
    }

    if (info.timeoutNanos != null) {
      Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS);
      Deadline existingDeadline = callOptions.getDeadline();
      // If the new deadline is sooner than the existing deadline, swap them.
      if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) {
        callOptions = callOptions.withDeadline(newDeadline);
      }
    }
    if (info.waitForReady != null) {
      callOptions =
          info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady();
    }
    if (info.maxInboundMessageSize != null) {
      Integer existingLimit = callOptions.getMaxInboundMessageSize();
      if (existingLimit != null) {
        callOptions = callOptions.withMaxInboundMessageSize(
            Math.min(existingLimit, info.maxInboundMessageSize));
      } else {
        callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize);
      }
    }
    if (info.maxOutboundMessageSize != null) {
      Integer existingLimit = callOptions.getMaxOutboundMessageSize();
      if (existingLimit != null) {
        callOptions = callOptions.withMaxOutboundMessageSize(
            Math.min(existingLimit, info.maxOutboundMessageSize));
      } else {
        callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize);
      }
    }

    return next.newCall(method, callOptions);
  }

    ServiceConfigInterceptor(
      boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit);

}