chromium/ios/chrome/browser/ui/promos_manager/promos_manager_coordinator.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/promos_manager/promos_manager_coordinator.h"

#import <Foundation/Foundation.h>

#import <map>
#import <optional>

#import "base/check.h"
#import "base/containers/small_map.h"
#import "base/debug/dump_without_crashing.h"
#import "base/metrics/histogram_functions.h"
#import "base/notreached.h"
#import "base/strings/sys_string_conversions.h"
#import "components/crash/core/common/crash_key.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/app/tests_hook.h"
#import "ios/chrome/browser/app_store_rating/ui_bundled/app_store_rating_display_handler.h"
#import "ios/chrome/browser/app_store_rating/ui_bundled/features.h"
#import "ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_display_handler.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/default_promo/ui_bundled/all_tabs_default_browser_promo_view_provider.h"
#import "ios/chrome/browser/default_promo/ui_bundled/made_for_ios_default_browser_promo_view_provider.h"
#import "ios/chrome/browser/default_promo/ui_bundled/post_default_abandonment/features.h"
#import "ios/chrome/browser/default_promo/ui_bundled/post_default_abandonment/post_default_abandonment_promo_provider.h"
#import "ios/chrome/browser/default_promo/ui_bundled/post_restore/post_restore_default_browser_promo_provider.h"
#import "ios/chrome/browser/default_promo/ui_bundled/promo_handler/default_browser_promo_display_handler.h"
#import "ios/chrome/browser/default_promo/ui_bundled/promo_handler/default_browser_remind_me_later_promo_display_handler.h"
#import "ios/chrome/browser/default_promo/ui_bundled/stay_safe_default_browser_promo_view_provider.h"
#import "ios/chrome/browser/docking_promo/ui/docking_promo_display_handler.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/promos_manager/model/features.h"
#import "ios/chrome/browser/promos_manager/model/promo_config.h"
#import "ios/chrome/browser/promos_manager/model/promos_manager.h"
#import "ios/chrome/browser/promos_manager/model/promos_manager_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
#import "ios/chrome/browser/shared/public/commands/docking_promo_commands.h"
#import "ios/chrome/browser/shared/public/commands/promos_manager_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/post_restore_signin/post_restore_signin_provider.h"
#import "ios/chrome/browser/ui/promos_manager/bannered_promo_view_provider.h"
#import "ios/chrome/browser/ui/promos_manager/promos_manager_coordinator+Testing.h"
#import "ios/chrome/browser/ui/promos_manager/promos_manager_mediator.h"
#import "ios/chrome/browser/ui/promos_manager/standard_promo_alert_provider.h"
#import "ios/chrome/browser/ui/promos_manager/standard_promo_display_handler.h"
#import "ios/chrome/browser/ui/promos_manager/standard_promo_view_provider.h"
#import "ios/chrome/browser/ui/promos_manager/utils.h"
#import "ios/chrome/browser/ui/whats_new/promo/whats_new_promo_display_handler.h"
#import "ios/chrome/browser/ui/whats_new/whats_new_util.h"
#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h"
#import "ios/chrome/common/ui/promo_style/promo_style_view_controller.h"
#import "ios/chrome/common/ui/promo_style/promo_style_view_controller_delegate.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/signin/choice_api.h"
#import "ui/base/l10n/l10n_util_mac.h"

@interface PromosManagerCoordinator () <
    ConfirmationAlertActionHandler,
    UIAdaptivePresentationControllerDelegate,
    PromoStyleViewControllerDelegate> {
  // Promos that conform to the StandardPromoDisplayHandler protocol.
  base::small_map<
      std::map<promos_manager::Promo, id<StandardPromoDisplayHandler>>>
      _displayHandlerPromos;

  // Promos that conform to the StandardPromoViewProvider protocol.
  base::small_map<
      std::map<promos_manager::Promo, id<StandardPromoViewProvider>>>
      _viewProviderPromos;

  // Promos that conform to the BanneredPromoViewProvider protocol.
  base::small_map<
      std::map<promos_manager::Promo, id<BanneredPromoViewProvider>>>
      _banneredViewProviderPromos;

  // Promos that conform to the StandardPromoAlertProvider protocol.
  base::small_map<
      std::map<promos_manager::Promo, id<StandardPromoAlertProvider>>>
      _alertProviderPromos;

  // The currently displayed promo data, if any.
  std::optional<PromoDisplayData> _currentPromoData;

  // The handler for the CredentialProviderPromoCommands.
  id<CredentialProviderPromoCommands> _credentialProviderPromoCommandHandler;

  // The handler for the DockingPromoCommands.
  id<DockingPromoCommands> _dockingPromoCommandHandler;
}

// A mediator that observes when it's a good time to display a promo.
@property(nonatomic, strong) PromosManagerMediator* mediator;

// The current StandardPromoViewProvider, if any.
@property(nonatomic, weak) id<StandardPromoViewProvider> provider;

// The current BanneredPromoViewProvider, if any.
@property(nonatomic, weak) id<BanneredPromoViewProvider> banneredProvider;

// The current ConfirmationAlertViewController, if any.
@property(nonatomic, strong) ConfirmationAlertViewController* viewController;

// The current PromoStyleViewController, if any.
@property(nonatomic, strong) PromoStyleViewController* banneredViewController;

@end

@implementation PromosManagerCoordinator

#pragma mark - Initialization

- (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                   browser:(Browser*)browser
            credentialProviderPromoHandler:(id<CredentialProviderPromoCommands>)
                                               credentialProviderPromoHandler
                       dockingPromoHandler:
                           (id<DockingPromoCommands>)dockingPromoHandler {
  DCHECK(ShouldPromoManagerDisplayPromos());
  if ((self = [super initWithBaseViewController:viewController
                                        browser:browser])) {
    _credentialProviderPromoCommandHandler = credentialProviderPromoHandler;
    _dockingPromoCommandHandler = dockingPromoHandler;

    [self registerPromos];

    BOOL promosExist = _displayHandlerPromos.size() > 0 ||
                       _viewProviderPromos.size() > 0 ||
                       _banneredViewProviderPromos.size() > 0 ||
                       _alertProviderPromos.size() > 0;

    if (promosExist) {
      // Don't create PromosManagerMediator unless promos exist that are
      // registered with PromosManagerCoordinator via `registerPromos`.
      PromosManager* promosManager =
          PromosManagerFactory::GetForBrowserState(browser->GetBrowserState());
      _mediator = [[PromosManagerMediator alloc]
          initWithPromosManager:promosManager
                   promoConfigs:[self promoConfigs]];
    }
  }

  return self;
}

#pragma mark - Public

- (void)start {
  [self displayPromoIfAvailable:YES];
}

- (void)stop {
  self.mediator = nil;
  [self dismissViewControllers];
}

- (void)displayPromoIfAvailable {
  [self displayPromoIfAvailable:NO];
}

// Display a promo if one is available, with special behavior if this is the
// first time this coordinator has shown a promo.
- (void)displayPromoIfAvailable:(BOOL)isFirstShownPromo {
  // Wait to present a promo until the feature engagement tracker database
  // is fully initialized.
  __weak __typeof(self) weakSelf = self;
  void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
    if (!successfullyLoaded) {
      return;
    }
    [weakSelf displayPromoCallback:isFirstShownPromo];
  };

  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserState(
          self.browser->GetBrowserState());
  tracker->AddOnInitializedCallback(base::BindOnce(onInitializedBlock));
}

- (void)displayPromoCallback:(BOOL)isFirstShownPromo {
  // Check if UI is no longer available before proceeding. It is possible that
  // while tracker is being initialized the UI can change and become not
  // available.
  if (!IsUIAvailableForPromo(self.browser->GetSceneState())) {
    return;
  }

  // If there's already a displayed promo, skip.
  if (_currentPromoData.has_value()) {
    return;
  }

  std::optional<PromoDisplayData> nextPromoForDisplay =
      [self.mediator nextPromoForDisplay:isFirstShownPromo];

  if (nextPromoForDisplay.has_value()) {
    [self displayPromo:nextPromoForDisplay.value()];
  }
}

- (void)promoWasDismissed {
  if (_currentPromoData.has_value() && !_currentPromoData.value().was_forced) {
    PromoConfigsSet configs = [self promoConfigs];
    auto it = configs.find(_currentPromoData.value().promo);
    if (it == configs.end() || !it->feature_engagement_feature) {
      return;
    }

    feature_engagement::Tracker* tracker =
        feature_engagement::TrackerFactory::GetForBrowserState(
            self.browser->GetBrowserState());
    tracker->Dismissed(*it->feature_engagement_feature);
  }
  _currentPromoData = std::nullopt;
}

- (void)displayPromo:(PromoDisplayData)promoData {
  if (tests_hook::DisablePromoManagerFullScreenPromos()) {
    return;
  }

  promos_manager::Promo promo = promoData.promo;
  _currentPromoData = promoData;

  auto handler_it = _displayHandlerPromos.find(promo);
  auto provider_it = _viewProviderPromos.find(promo);
  auto bannered_provider_it = _banneredViewProviderPromos.find(promo);
  auto alert_provider_it = _alertProviderPromos.find(promo);

  id<PromosManagerCommands> promosManagerCommandsHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), PromosManagerCommands);

  if (handler_it != _displayHandlerPromos.end()) {
    id<StandardPromoDisplayHandler> handler = handler_it->second;

    if ([handler respondsToSelector:@selector(setHandler:)])
      handler.handler = promosManagerCommandsHandler;

    [handler handleDisplay];

    [self.mediator deregisterAfterDisplay:handler.config.identifier];

    base::UmaHistogramEnumeration("IOS.PromosManager.Promo", promo);
    base::UmaHistogramEnumeration("IOS.PromosManager.Promo.Type",
                                  promos_manager::IOSPromosManagerPromoType::
                                      kStandardPromoDisplayHandler);

    if ([handler respondsToSelector:@selector(promoWasDisplayed)]) {
      [handler promoWasDisplayed];
    }
  } else if (provider_it != _viewProviderPromos.end()) {
    id<StandardPromoViewProvider> provider = provider_it->second;

    if ([provider respondsToSelector:@selector(setHandler:)])
      provider.handler = promosManagerCommandsHandler;

    self.viewController = [provider viewController];
    self.viewController.presentationController.delegate = self;
    self.viewController.actionHandler = self;

    self.provider = provider;

    [self.baseViewController presentViewController:self.viewController
                                          animated:YES
                                        completion:nil];

    [self.mediator deregisterAfterDisplay:provider.config.identifier];

    base::UmaHistogramEnumeration("IOS.PromosManager.Promo", promo);
    base::UmaHistogramEnumeration(
        "IOS.PromosManager.Promo.Type",
        promos_manager::IOSPromosManagerPromoType::kStandardPromoViewProvider);

    if ([provider respondsToSelector:@selector(promoWasDisplayed)]) {
      [provider promoWasDisplayed];
    }
  } else if (bannered_provider_it != _banneredViewProviderPromos.end()) {
    id<BanneredPromoViewProvider> banneredProvider =
        bannered_provider_it->second;

    if ([banneredProvider respondsToSelector:@selector(setHandler:)])
      banneredProvider.handler = promosManagerCommandsHandler;

    self.banneredViewController = [banneredProvider viewController];

    self.banneredViewController.presentationController.delegate = self;
    self.banneredViewController.delegate = self;
    self.banneredProvider = banneredProvider;

    [self.baseViewController presentViewController:self.banneredViewController
                                          animated:YES
                                        completion:nil];

    [self.mediator deregisterAfterDisplay:banneredProvider.config.identifier];

    base::UmaHistogramEnumeration("IOS.PromosManager.Promo", promo);
    base::UmaHistogramEnumeration(
        "IOS.PromosManager.Promo.Type",
        promos_manager::IOSPromosManagerPromoType::kBanneredPromoViewProvider);

    if ([banneredProvider respondsToSelector:@selector(promoWasDisplayed)]) {
      [banneredProvider promoWasDisplayed];
    }
  } else if (alert_provider_it != _alertProviderPromos.end()) {
    id<StandardPromoAlertProvider> alertProvider = alert_provider_it->second;

    if ([alertProvider respondsToSelector:@selector(setHandler:)])
      alertProvider.handler = promosManagerCommandsHandler;

    DCHECK([alertProvider.title length] != 0);
    DCHECK([alertProvider.message length] != 0);
    // The "Default Action" should always be implemented by feature
    DCHECK([alertProvider
        respondsToSelector:@selector(standardPromoAlertDefaultAction)]);

    UIAlertController* alert = [UIAlertController
        alertControllerWithTitle:alertProvider.title
                         message:alertProvider.message
                  preferredStyle:UIAlertControllerStyleAlert];

    NSString* defaultActionButtonText =
        [alertProvider respondsToSelector:@selector(defaultActionButtonText)]
            ? alertProvider.defaultActionButtonText
            : l10n_util::GetNSString(
                  IDS_IOS_PROMOS_MANAGER_ALERT_PROMO_DEFAULT_PRIMARY_BUTTON_TEXT);
    NSString* cancelActionButtonText =
        [alertProvider respondsToSelector:@selector(cancelActionButtonText)]
            ? alertProvider.cancelActionButtonText
            : l10n_util::GetNSString(
                  IDS_IOS_PROMOS_MANAGER_ALERT_PROMO_DEFAULT_CANCEL_BUTTON_TEXT);

    UIAlertAction* defaultAction = [UIAlertAction
        actionWithTitle:defaultActionButtonText
                  style:UIAlertActionStyleDefault
                handler:^(UIAlertAction* action) {
                  if ([alertProvider respondsToSelector:@selector
                                     (standardPromoAlertDefaultAction)])
                    [alertProvider standardPromoAlertDefaultAction];

                  [self dismissViewControllers];
                }];

    UIAlertAction* cancelAction = [UIAlertAction
        actionWithTitle:cancelActionButtonText
                  style:UIAlertActionStyleCancel
                handler:^(UIAlertAction* action) {
                  if ([alertProvider respondsToSelector:@selector
                                     (standardPromoAlertCancelAction)]) {
                    [alertProvider standardPromoAlertCancelAction];
                  }
                  [self dismissViewControllers];
                }];

    [alert addAction:defaultAction];
    [alert addAction:cancelAction];
    alert.preferredAction = defaultAction;

    [self.baseViewController presentViewController:alert
                                          animated:YES
                                        completion:nil];

    [self.mediator deregisterAfterDisplay:alertProvider.config.identifier];

    base::UmaHistogramEnumeration("IOS.PromosManager.Promo", promo);
    base::UmaHistogramEnumeration(
        "IOS.PromosManager.Promo.Type",
        promos_manager::IOSPromosManagerPromoType::kStandardPromoAlertProvider);

    if ([alertProvider respondsToSelector:@selector(promoWasDisplayed)]) {
      [alertProvider promoWasDisplayed];
    }
  } else {
    // Deregister the promo in edge cases:
    //
    // 1. When promos are forced for display (via Experimental Settings toggle)
    // but not properly enabled (via chrome://flags).
    //
    // 2. When the promo's flag is disabled but was registered before and hasn't
    // been displayed yet.
    //
    // These are niche edge cases that almost exclusively occur during local,
    // manual testing.
    std::optional<promos_manager::Promo> maybeForcedPromo =
        promos_manager::PromoForName(base::SysNSStringToUTF8(
            experimental_flags::GetForcedPromoToDisplay()));

    if (maybeForcedPromo.has_value()) {
      promos_manager::Promo forcedPromo = maybeForcedPromo.value();

      if ([self isPromoUnregistered:forcedPromo]) {
        base::UmaHistogramEnumeration(
            "IOS.PromosManager.Promo.ForcedDisplayFailure", forcedPromo);
      }
    } else {
      base::UmaHistogramEnumeration("IOS.PromosManager.Promo.DisplayFailure",
                                    promo);

      [self.mediator deregisterPromo:promo];
    }
  }
}

#pragma mark - PromoStyleViewControllerDelegate

// Invoked when the primary action button is tapped.
- (void)didTapPrimaryActionButton {
  DCHECK(self.banneredProvider);

  if (![self.banneredProvider
          respondsToSelector:@selector(standardPromoPrimaryAction)])
    return;

  [self.banneredProvider standardPromoPrimaryAction];
}

// Invoked when the secondary action button is tapped.
- (void)didTapSecondaryActionButton {
  DCHECK(self.banneredProvider);

  // Sometimes the secondary action button for a PromoStyleViewController is
  // used as the dismiss action button.
  if ([self.banneredProvider
          respondsToSelector:@selector(standardPromoDismissAction)]) {
    [self.banneredProvider standardPromoDismissAction];
    [self dismissViewControllers];
  } else if ([self.banneredProvider
                 respondsToSelector:@selector(standardPromoSecondaryAction)]) {
    [self.banneredProvider standardPromoSecondaryAction];
  }
}

// Invoked when the tertiary action button is tapped.
- (void)didTapTertiaryActionButton {
  DCHECK(self.banneredProvider);

  if (![self.banneredProvider
          respondsToSelector:@selector(standardPromoTertiaryAction)])
    return;

  [self.banneredProvider standardPromoTertiaryAction];
}

// Invoked when the top left question mark button is tapped.
- (void)didTapLearnMoreButton {
  DCHECK(self.banneredProvider);

  if (![self.banneredProvider
          respondsToSelector:@selector(standardPromoLearnMoreAction)])
    return;

  [self.banneredProvider standardPromoLearnMoreAction];
}

// Invoked when a link in the disclaimer is tapped.
- (void)didTapURLInDisclaimer:(NSURL*)URL {
  // TODO(crbug.com/40238885): Complete `didTapURLInDisclaimer` to bring users
  // to Settings page.
}

#pragma mark - ConfirmationAlertActionHandler

- (void)confirmationAlertPrimaryAction {
  DCHECK(self.provider);

  if (![self.provider respondsToSelector:@selector(standardPromoPrimaryAction)])
    return;

  [self.provider standardPromoPrimaryAction];
}

- (void)confirmationAlertSecondaryAction {
  DCHECK(self.provider);

  if (![self.provider
          respondsToSelector:@selector(standardPromoSecondaryAction)])
    return;

  [self.provider standardPromoSecondaryAction];
}

- (void)confirmationAlertTertiaryAction {
  DCHECK(self.provider);

  if (![self.provider
          respondsToSelector:@selector(standardPromoTertiaryAction)])
    return;

  [self.provider standardPromoTertiaryAction];
}

- (void)confirmationAlertLearnMoreAction {
  DCHECK(self.provider);

  if (![self.provider
          respondsToSelector:@selector(standardPromoLearnMoreAction)])
    return;

  [self.provider standardPromoLearnMoreAction];
}

- (void)confirmationAlertDismissAction {
  DCHECK(self.provider || self.banneredProvider);

  if ([self.provider
          respondsToSelector:@selector(standardPromoDismissAction)]) {
    [self.provider standardPromoDismissAction];
  } else if ([self.banneredProvider
                 respondsToSelector:@selector(standardPromoDismissAction)]) {
    [self.banneredProvider standardPromoDismissAction];
  }

  [self dismissViewControllers];
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (void)presentationControllerDidDismiss:
    (UIPresentationController*)presentationController {
  DCHECK(self.provider || self.banneredProvider);

  if ([self.provider respondsToSelector:@selector(standardPromoDismissSwipe)]) {
    [self.provider standardPromoDismissSwipe];
    [self dismissViewControllers];
  } else if ([self.banneredProvider
                 respondsToSelector:@selector(standardPromoDismissSwipe)]) {
    [self.banneredProvider standardPromoDismissSwipe];
    [self dismissViewControllers];
  } else {
    [self confirmationAlertDismissAction];
  }
}

#pragma mark - Private

- (void)dismissViewControllers {
  if (self.viewController) {
    [self.viewController.presentingViewController
        dismissViewControllerAnimated:YES
                           completion:nil];
    self.viewController = nil;
  }

  if (self.banneredViewController) {
    [self.banneredViewController.presentingViewController
        dismissViewControllerAnimated:YES
                           completion:nil];
    self.banneredViewController = nil;
  }

  [self promoWasDismissed];
}

- (void)registerStandardPromoDisplayHandlerPromos {
  // App Store rating promo handler.
  if (IsAppStoreRatingEnabled()) {
    _displayHandlerPromos[promos_manager::Promo::AppStoreRating] =
        [[AppStoreRatingDisplayHandler alloc] init];
  }

  // What's New promo handler.
  _displayHandlerPromos[promos_manager::Promo::WhatsNew] =
      [[WhatsNewPromoDisplayHandler alloc]
          initWithPromosManager:PromosManagerFactory::GetForBrowserState(
                                    self.browser->GetBrowserState())];

  // Credentials provider promo handler.
  _displayHandlerPromos[promos_manager::Promo::CredentialProviderExtension] =
      [[CredentialProviderPromoDisplayHandler alloc]
          initWithHandler:_credentialProviderPromoCommandHandler];

  // Docking promo handler.
  _displayHandlerPromos[promos_manager::Promo::DockingPromo] =
      [[DockingPromoDisplayHandler alloc]
                   initWithHandler:_dockingPromoCommandHandler
          showRemindMeLaterVersion:NO];
  _displayHandlerPromos[promos_manager::Promo::DockingPromoRemindMeLater] =
      [[DockingPromoDisplayHandler alloc]
                   initWithHandler:_dockingPromoCommandHandler
          showRemindMeLaterVersion:YES];

  // Default browser promo handler.
  _displayHandlerPromos[promos_manager::Promo::DefaultBrowser] =
      [[DefaultBrowserPromoDisplayHandler alloc] init];
  _displayHandlerPromos[promos_manager::Promo::DefaultBrowserRemindMeLater] =
      [[DefaultBrowserRemindMeLaterPromoDisplayHandler alloc] init];
}

- (void)registerStandardPromoViewProviderPromos {
  _viewProviderPromos[promos_manager::Promo::AllTabsDefaultBrowser] =
      [[AllTabsDefaultBrowserPromoViewProvider alloc] init];
  _viewProviderPromos[promos_manager::Promo::MadeForIOSDefaultBrowser] =
      [[MadeForIOSDefaultBrowserPromoViewProvider alloc] init];
  _viewProviderPromos[promos_manager::Promo::StaySafeDefaultBrowser] =
      [[StaySafeDefaultBrowserPromoViewProvider alloc] init];
}

- (void)registerBanneredPromoViewProviderPromos {
  // None yet.
}

- (void)registerStandardPromoAlertProviderPromos {
  // Post-restore sign-in promo handler.
  _alertProviderPromos[promos_manager::Promo::PostRestoreSignInAlert] =
      [[PostRestoreSignInProvider alloc] initForBrowser:self.browser];

    _alertProviderPromos
        [promos_manager::Promo::PostRestoreDefaultBrowserAlert] =
            [[PostRestoreDefaultBrowserPromoProvider alloc] init];

  // Post-default browser abandonment promo handler.
  if (IsPostDefaultAbandonmentPromoEnabled()) {
    _alertProviderPromos[promos_manager::Promo::PostDefaultAbandonment] =
        [[PostDefaultBrowserAbandonmentPromoProvider alloc] init];
  }
}

- (void)registerPromos {
  [self registerStandardPromoDisplayHandlerPromos];
  [self registerStandardPromoViewProviderPromos];
  [self registerBanneredPromoViewProviderPromos];
  [self registerStandardPromoAlertProviderPromos];
}

- (PromoConfigsSet)promoConfigs {
  PromoConfigsSet result;

  for (auto const& [promo, handler] : _displayHandlerPromos)
    result.emplace(handler.config);

  for (auto const& [promo, provider] : _viewProviderPromos)
    result.emplace(provider.config);

  for (auto const& [promo, banneredProvider] : _banneredViewProviderPromos)
    result.emplace(banneredProvider.config);

  for (auto const& [promo, alertProvider] : _alertProviderPromos)
    result.emplace(alertProvider.config);

  return result;
}

// Checks if `promo` is properly registered within this coordinator.
- (BOOL)isPromoUnregistered:(promos_manager::Promo)promo {
  auto handler_it = _displayHandlerPromos.find(promo);
  auto provider_it = _viewProviderPromos.find(promo);
  auto bannered_provider_it = _banneredViewProviderPromos.find(promo);
  auto alert_provider_it = _alertProviderPromos.find(promo);

  return handler_it == _displayHandlerPromos.end() &&
         provider_it == _viewProviderPromos.end() &&
         bannered_provider_it == _banneredViewProviderPromos.end() &&
         alert_provider_it == _alertProviderPromos.end();
}

@end