chromium/ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model.mm

// Copyright 2024 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/content_suggestions/magic_stack/magic_stack_ranking_model.h"

#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "components/commerce/core/commerce_feature_list.h"
#import "components/commerce/core/shopping_service.h"
#import "components/prefs/pref_service.h"
#import "components/segmentation_platform/embedder/home_modules/constants.h"
#import "components/segmentation_platform/public/constants.h"
#import "components/segmentation_platform/public/features.h"
#import "components/segmentation_platform/public/segmentation_platform_service.h"
#import "ios/chrome/browser/ntp/ui_bundled/home_start_data_source.h"
#import "ios/chrome/browser/ntp_tiles/model/tab_resumption/tab_resumption_prefs.h"
#import "ios/chrome/browser/parcel_tracking/features.h"
#import "ios/chrome/browser/parcel_tracking/parcel_tracking_prefs.h"
#import "ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager_constants.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/utils/first_run_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/most_visited_tiles_config.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/most_visited_tiles_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/shortcuts_config.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/shortcuts_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_constants.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.h"
#import "ios/chrome/browser/ui/content_suggestions/magic_stack/magic_stack_ranking_model_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/parcel_tracking/parcel_tracking_item.h"
#import "ios/chrome/browser/ui/content_suggestions/parcel_tracking/parcel_tracking_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_item.h"
#import "ios/chrome/browser/ui/content_suggestions/price_tracking_promo/price_tracking_promo_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/safety_check_magic_stack_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/safety_check_prefs.h"
#import "ios/chrome/browser/ui/content_suggestions/safety_check/safety_check_state.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_config.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_item_view_data.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/set_up_list_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/set_up_list/utils.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_helper_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_item.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_mediator.h"

@interface MagicStackRankingModel () <MostVisitedTilesMediatorDelegate,
                                      ParcelTrackingMediatorDelegate,
                                      PriceTrackingPromoMediatorDelegate,
                                      SafetyCheckMagicStackMediatorDelegate,
                                      SetUpListMediatorAudience,
                                      ShortcutsMediatorDelegate,
                                      TabResumptionHelperDelegate>
// For testing-only
@property(nonatomic, assign) BOOL hasReceivedMagicStackResponse;
@property(nonatomic, assign) BOOL hasReceivedEphemericalCardResponse;
@end

@implementation MagicStackRankingModel {
  segmentation_platform::SegmentationPlatformService* _segmentationService;
  commerce::ShoppingService* _shoppingService;
  PrefService* _prefService;
  PrefService* _localState;
  // The latest module ranking returned from the SegmentationService.
  NSArray<NSNumber*>* _magicStackOrderFromSegmentation;
  // YES if the module ranking has been received from the SegmentationService.
  BOOL _magicStackOrderFromSegmentationReceived;
  // The latest Magic Stack module order sent up to the consumer. This includes
  // any omissions due to filtering from `_magicStackOrderFromSegmentation` and
  // any additions beyond `_magicStackOrderFromSegmentation` (e.g. Set Up List).
  NSArray<MagicStackModule*>* _latestMagicStackConfigOrder;
  // Module mediators.
  MostVisitedTilesMediator* _mostVisitedTilesMediator;
  SetUpListMediator* _setUpListMediator;
  TabResumptionMediator* _tabResumptionMediator;
  ParcelTrackingMediator* _parcelTrackingMediator;
  PriceTrackingPromoMediator* _priceTrackingPromoMediator;
  ShortcutsMediator* _shortcutsMediator;
  SafetyCheckMagicStackMediator* _safetyCheckMediator;
  base::TimeTicks ranking_fetch_start_time_;
  ContentSuggestionsModuleType _ephemeralCardToShow;
}

- (instancetype)
    initWithSegmentationService:
        (segmentation_platform::SegmentationPlatformService*)segmentationService
                shoppingService:(commerce::ShoppingService*)shoppingService
                    prefService:(PrefService*)prefService
                     localState:(PrefService*)localState
                moduleMediators:(NSArray*)moduleMediators {
  self = [super init];
  if (self) {
    _segmentationService = segmentationService;
    _shoppingService = shoppingService;
    _prefService = prefService;
    _localState = localState;
    _ephemeralCardToShow = ContentSuggestionsModuleType::kInvalid;

    for (id mediator in moduleMediators) {
      if ([mediator isKindOfClass:[MostVisitedTilesMediator class]]) {
        _mostVisitedTilesMediator =
            static_cast<MostVisitedTilesMediator*>(mediator);
        _mostVisitedTilesMediator.delegate = self;
      } else if ([mediator isKindOfClass:[SetUpListMediator class]]) {
        _setUpListMediator = static_cast<SetUpListMediator*>(mediator);
        _setUpListMediator.audience = self;
      } else if ([mediator isKindOfClass:[TabResumptionMediator class]]) {
        _tabResumptionMediator = static_cast<TabResumptionMediator*>(mediator);
        _tabResumptionMediator.delegate = self;
      } else if ([mediator isKindOfClass:[ShortcutsMediator class]]) {
        _shortcutsMediator = static_cast<ShortcutsMediator*>(mediator);
        _shortcutsMediator.delegate = self;
      } else if ([mediator isKindOfClass:[ParcelTrackingMediator class]]) {
        _parcelTrackingMediator =
            static_cast<ParcelTrackingMediator*>(mediator);
        _parcelTrackingMediator.delegate = self;
      } else if ([mediator isKindOfClass:[PriceTrackingPromoMediator class]]) {
        _priceTrackingPromoMediator =
            static_cast<PriceTrackingPromoMediator*>(mediator);
        _priceTrackingPromoMediator.delegate = self;
      } else if ([mediator
                     isKindOfClass:[SafetyCheckMagicStackMediator class]]) {
        _safetyCheckMediator =
            static_cast<SafetyCheckMagicStackMediator*>(mediator);
        _safetyCheckMediator.delegate = self;
      } else {
        // Known module mediators need to be handled.
        NOTREACHED_IN_MIGRATION();
      }
    }
  }
  return self;
}

- (void)disconnect {
  _mostVisitedTilesMediator = nil;
  _setUpListMediator = nil;
  _tabResumptionMediator = nil;
  _parcelTrackingMediator = nil;
  _priceTrackingPromoMediator = nil;
  _shortcutsMediator = nil;
  _safetyCheckMediator = nil;
}

#pragma mark - Public

- (void)fetchLatestMagicStackRanking {
  _magicStackOrderFromSegmentationReceived = NO;
  _magicStackOrderFromSegmentation = nil;
  _latestMagicStackConfigOrder = nil;
  if (base::FeatureList::IsEnabled(
          segmentation_platform::features::
              kSegmentationPlatformEphemeralCardRanker)) {
    _ephemeralCardToShow = ContentSuggestionsModuleType::kInvalid;
    [self fetchEphemeralCardFromSegmentationPlatform];
  }
  [self fetchMagicStackModuleRankingFromSegmentationPlatform];
}

- (void)logMagicStackEngagementForType:(ContentSuggestionsModuleType)type {
  [self.contentSuggestionsMetricsRecorder
      recordMagicStackModuleEngagementForType:type
                                      atIndex:
                                          [self indexForMagicStackModule:type]];
}

#pragma mark - SetUpListMediatorAudience

- (void)removeSetUpList {
  base::UmaHistogramEnumeration(
      kMagicStackModuleDisabledHistogram,
      ContentSuggestionsModuleType::kCompactedSetUpList);
  [self.delegate magicStackRankingModel:self
                          didRemoveItem:_setUpListMediator.setUpListConfigs[0]];
}

- (void)replaceSetUpListWithAllSet:(SetUpListConfig*)allSetConfig {
  [self.delegate magicStackRankingModel:self
                         didReplaceItem:_setUpListMediator.setUpListConfigs[0]
                               withItem:allSetConfig];
}

#pragma mark - SafetyCheckMagicStackMediatorDelegate

- (void)removeSafetyCheckModule {
  if (![self isMagicStackOrderReady]) {
    return;
  }

  base::UmaHistogramEnumeration(kMagicStackModuleDisabledHistogram,
                                ContentSuggestionsModuleType::kSafetyCheck);
  [self.delegate magicStackRankingModel:self
                          didRemoveItem:_safetyCheckMediator.safetyCheckState];
}

#pragma mark - TabResumptionHelperDelegate

- (void)tabResumptionHelperDidReceiveItem {
  CHECK(IsTabResumptionEnabled());
  if (tab_resumption_prefs::IsTabResumptionDisabled(
          IsHomeCustomizationEnabled() ? _prefService : _localState)) {
    return;
  }

  [self showTabResumptionWithItem:_tabResumptionMediator.itemConfig];
}

- (void)tabResumptionHelperDidReconfigureItem {
  if (tab_resumption_prefs::IsTabResumptionDisabled(
          IsHomeCustomizationEnabled() ? _prefService : _localState)) {
    return;
  }
  TabResumptionItem* item = _tabResumptionMediator.itemConfig;
  [self.delegate magicStackRankingModel:self didReconfigureItem:item];
}

- (void)removeTabResumptionModule {
  [self.delegate magicStackRankingModel:self
                          didRemoveItem:_tabResumptionMediator.itemConfig];
}

#pragma mark - ParcelTrackingMediatorDelegate

- (void)newParcelsAvailable {
  MagicStackModule* item = _parcelTrackingMediator.parcelTrackingItemToShow;
  NSArray<MagicStackModule*>* rank = [self latestMagicStackConfigRank];
  NSUInteger index = [rank indexOfObject:item];
  if (index == NSNotFound) {
    return;
  }
  [self.delegate magicStackRankingModel:self didInsertItem:item atIndex:index];
}

- (void)parcelTrackingDisabled {
  base::UmaHistogramEnumeration(kMagicStackModuleDisabledHistogram,
                                ContentSuggestionsModuleType::kParcelTracking);
  [self.delegate
      magicStackRankingModel:self
               didRemoveItem:_parcelTrackingMediator.parcelTrackingItemToShow];
}

- (NSUInteger)indexForMagicStackModule:
    (ContentSuggestionsModuleType)moduleType {
  return [_latestMagicStackConfigOrder
      indexOfObjectPassingTest:^BOOL(MagicStackModule* config, NSUInteger idx,
                                     BOOL* stop) {
        return config.type == moduleType;
      }];
}

#pragma mark - MostVisitedTilesMediatorDelegate

- (void)didReceiveInitialMostVistedTiles {
  if (![self isMagicStackOrderReady]) {
    return;
  }

  NSArray<MagicStackModule*>* rank = [self latestMagicStackConfigRank];
  NSUInteger index =
      [rank indexOfObject:_mostVisitedTilesMediator.mostVisitedConfig];
  [self.delegate
      magicStackRankingModel:self
               didInsertItem:_mostVisitedTilesMediator.mostVisitedConfig
                     atIndex:index];
}

- (void)removeMostVisitedTilesModule {
  if (![self isMagicStackOrderReady]) {
    return;
  }

  [self.delegate
      magicStackRankingModel:self
               didRemoveItem:_mostVisitedTilesMediator.mostVisitedConfig];
}

#pragma mark - Private

// Adds the correct Set Up List module type to the Magic Stack `order`.
- (void)addSetUpListToMagicStackOrder:(NSMutableArray*)order {
  if ([_setUpListMediator allItemsComplete]) {
    [order addObject:@(int(ContentSuggestionsModuleType::kSetUpListAllSet))];
  } else if (set_up_list_utils::ShouldShowCompactedSetUpListModule()) {
    [order addObject:@(int(ContentSuggestionsModuleType::kCompactedSetUpList))];
  } else {
    for (SetUpListItemViewData* model in _setUpListMediator.setUpListItems) {
      [order addObject:@(int(SetUpListModuleTypeForSetUpListType(model.type)))];
    }
  }
}

// Adds the Safety Check module to `order` based on the current Safety Check
// state.
- (void)addSafetyCheckToMagicStackOrder:(NSMutableArray*)order {
  CHECK(IsSafetyCheckMagicStackEnabled());
  [order addObject:@(int(ContentSuggestionsModuleType::kSafetyCheck))];
}

// New subscription observed for user (from another platform). This
// has the potential to boost the ranking of the price trackiing promo.
- (void)newSubscriptionAvailable {
}

// Starts a fetch of the ephemeral card to show from Segmentation.
- (void)fetchEphemeralCardFromSegmentationPlatform {
  segmentation_platform::PredictionOptions options;
  options.on_demand_execution = true;
  auto inputContext =
      base::MakeRefCounted<segmentation_platform::InputContext>();
  inputContext->metadata_args.emplace(
      segmentation_platform::kIsNewUser,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          IsFirstRunRecent(base::Days(14))));
  inputContext->metadata_args.emplace(
      segmentation_platform::kIsSynced,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          _shoppingService->IsShoppingListEligible()));
  __weak MagicStackRankingModel* weakSelf = self;
  _segmentationService->GetClassificationResult(
      segmentation_platform::kEphemeralHomeModuleBackendKey, options,
      inputContext,
      base::BindOnce(
          ^(const segmentation_platform::ClassificationResult& result) {
            weakSelf.hasReceivedEphemericalCardResponse = YES;
            [weakSelf didReceiveEphemeralCardSegmentationResult:result];
          }));
}

// Handles the ephemeral card Segmentation response and adds a card if there is
// one to show.
- (void)didReceiveEphemeralCardSegmentationResult:
    (const segmentation_platform::ClassificationResult&)result {
  if (result.status != segmentation_platform::PredictionStatus::kSucceeded) {
    return;
  }

  MagicStackModule* card;
  for (const std::string& label : result.ordered_labels) {
    if (label == segmentation_platform::kPriceTrackingNotificationPromo) {
      if (base::FeatureList::IsEnabled(commerce::kPriceTrackingPromo)) {
        if (!_shoppingService->IsShoppingListEligible()) {
          base::debug::DumpWithoutCrashing();
          return;
        }
        _ephemeralCardToShow =
            ContentSuggestionsModuleType::kPriceTrackingPromo;
        card = _priceTrackingPromoMediator.priceTrackingPromoItemToShow;
        break;
      }
    }
  }
  if (_ephemeralCardToShow != ContentSuggestionsModuleType::kInvalid) {
    if (!card) {
      base::debug::DumpWithoutCrashing();
      return;
    }
    [self addEphemeralCardToMagicStack:card];
  }
}

// Re-calculates the Magic Stack order and inserts the new ephemeral `card` if
// the Magic Stack ranking has been received.
- (void)addEphemeralCardToMagicStack:(MagicStackModule*)card {
  if (!_magicStackOrderFromSegmentationReceived) {
    return;
  }

  _latestMagicStackConfigOrder = [self latestMagicStackConfigRank];
  [self.delegate magicStackRankingModel:self didInsertItem:card atIndex:0];
}

// Starts a fetch of the Segmentation module ranking.
- (void)fetchMagicStackModuleRankingFromSegmentationPlatform {
  if (!base::FeatureList::IsEnabled(segmentation_platform::features::
                                        kSegmentationPlatformIosModuleRanker)) {
    segmentation_platform::ClassificationResult result(
        segmentation_platform::PredictionStatus::kNotReady);
    self.hasReceivedMagicStackResponse = YES;
    [self didReceiveSegmentationServiceResult:result];
    return;
  }
  auto inputContext =
      base::MakeRefCounted<segmentation_platform::InputContext>();
  if (base::FeatureList::IsEnabled(
          segmentation_platform::features::
              kSegmentationPlatformIosModuleRankerSplitBySurface)) {
    inputContext->metadata_args.emplace(
        segmentation_platform::kIsShowingStartSurface,
        segmentation_platform::processing::ProcessedValue::FromFloat(
            [self.homeStartDataSource isStartSurface]));
  }
  int mvtFreshnessImpressionCount = _localState->GetInteger(
      prefs::kIosMagicStackSegmentationMVTImpressionsSinceFreshness);
  inputContext->metadata_args.emplace(
      segmentation_platform::kMostVisitedTilesFreshness,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          mvtFreshnessImpressionCount));
  int shortcutsFreshnessImpressionCount = _localState->GetInteger(
      prefs::kIosMagicStackSegmentationShortcutsImpressionsSinceFreshness);
  inputContext->metadata_args.emplace(
      segmentation_platform::kShortcutsFreshness,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          shortcutsFreshnessImpressionCount));
  int safetyCheckFreshnessImpressionCount = _localState->GetInteger(
      prefs::kIosMagicStackSegmentationSafetyCheckImpressionsSinceFreshness);
  inputContext->metadata_args.emplace(
      segmentation_platform::kSafetyCheckFreshness,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          safetyCheckFreshnessImpressionCount));
  int tabResumptionFreshnessImpressionCount = _localState->GetInteger(
      prefs::kIosMagicStackSegmentationTabResumptionImpressionsSinceFreshness);
  inputContext->metadata_args.emplace(
      segmentation_platform::kTabResumptionFreshness,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          tabResumptionFreshnessImpressionCount));
  int parcelTrackingFreshnessImpressionCount = _localState->GetInteger(
      prefs::kIosMagicStackSegmentationParcelTrackingImpressionsSinceFreshness);
  inputContext->metadata_args.emplace(
      segmentation_platform::kParcelTrackingFreshness,
      segmentation_platform::processing::ProcessedValue::FromFloat(
          parcelTrackingFreshnessImpressionCount));
  __weak MagicStackRankingModel* weakSelf = self;
  segmentation_platform::PredictionOptions options;

  if (base::FeatureList::IsEnabled(
          kSegmentationPlatformIosModuleRankerCaching)) {
    // Ignores tab resumption freshness since local tab always logs a freshness
    // signal for Start.
    BOOL hasNoFreshnessSignal = shortcutsFreshnessImpressionCount != 0 &&
                                parcelTrackingFreshnessImpressionCount != 0;
    if (IsSafetyCheckMagicStackEnabled()) {
      hasNoFreshnessSignal =
          hasNoFreshnessSignal && safetyCheckFreshnessImpressionCount != 0;
    }
    if (hasNoFreshnessSignal && [self.homeStartDataSource isStartSurface]) {
      options = segmentation_platform::PredictionOptions::ForCached(true);
    } else {
      options = segmentation_platform::PredictionOptions::ForOnDemand(true);
    }
    options.can_update_cache_for_future_requests = true;
  } else {
    options.on_demand_execution = true;
  }
  ranking_fetch_start_time_ = base::TimeTicks::Now();
  _segmentationService->GetClassificationResult(
      segmentation_platform::kIosModuleRankerKey, options, inputContext,
      base::BindOnce(
          ^(const segmentation_platform::ClassificationResult& result) {
            weakSelf.hasReceivedMagicStackResponse = YES;
            [weakSelf didReceiveSegmentationServiceResult:result];
          }));
}

- (void)didReceiveSegmentationServiceResult:
    (const segmentation_platform::ClassificationResult&)result {
  if (result.status != segmentation_platform::PredictionStatus::kSucceeded) {
    return;
  }

  if ([self.homeStartDataSource isStartSurface]) {
    base::UmaHistogramMediumTimes(
        kMagicStackStartSegmentationRankingFetchTimeHistogram,
        base::TimeTicks::Now() - ranking_fetch_start_time_);
  } else {
    base::UmaHistogramMediumTimes(
        kMagicStackNTPSegmentationRankingFetchTimeHistogram,
        base::TimeTicks::Now() - ranking_fetch_start_time_);
  }

  NSMutableArray* magicStackOrder = [NSMutableArray array];
  for (const std::string& label : result.ordered_labels) {
    if (label == segmentation_platform::kMostVisitedTiles) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kMostVisited))];
    } else if (label == segmentation_platform::kShortcuts) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kShortcuts))];
    } else if (label == segmentation_platform::kSafetyCheck) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kSafetyCheck))];
    } else if (label == segmentation_platform::kTabResumption) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kTabResumption))];
    } else if (label == segmentation_platform::kParcelTracking) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kParcelTracking))];
    } else if (label == segmentation_platform::kPriceTrackingPromo) {
      [magicStackOrder
          addObject:@(int(ContentSuggestionsModuleType::kPriceTrackingPromo))];
    }
  }
  _magicStackOrderFromSegmentationReceived = YES;
  _magicStackOrderFromSegmentation = magicStackOrder;
  _latestMagicStackConfigOrder = [self latestMagicStackConfigRank];
  [self.delegate magicStackRankingModel:self
               didGetLatestRankingOrder:_latestMagicStackConfigOrder];
}

- (NSArray<MagicStackModule*>*)latestMagicStackConfigRank {
  NSMutableArray<MagicStackModule*>* magicStackOrder = [NSMutableArray array];
  // Always add Set Up List at the front.
  if ([_setUpListMediator shouldShowSetUpList]) {
    [magicStackOrder addObjectsFromArray:[_setUpListMediator setUpListConfigs]];
  }
  // Currently assume ephemeral cards are always added to the front of the Magic
  // Stack when it can show.
  if (base::FeatureList::IsEnabled(
          segmentation_platform::features::
              kSegmentationPlatformEphemeralCardRanker)) {
    switch (_ephemeralCardToShow) {
      case ContentSuggestionsModuleType::kPriceTrackingPromo:
        if (_priceTrackingPromoMediator) {
          [magicStackOrder addObject:_priceTrackingPromoMediator
                                         .priceTrackingPromoItemToShow];
        }
        break;
      default:
        break;
    }
  }
  for (NSNumber* moduleNumber in _magicStackOrderFromSegmentation) {
    ContentSuggestionsModuleType moduleType =
        (ContentSuggestionsModuleType)[moduleNumber intValue];
    switch (moduleType) {
      case ContentSuggestionsModuleType::kMostVisited:
        if (ShouldPutMostVisitedSitesInMagicStack() &&
            [_mostVisitedTilesMediator.mostVisitedConfig
                    .mostVisitedItems count] > 0) {
          [magicStackOrder
              addObject:_mostVisitedTilesMediator.mostVisitedConfig];
        }
        break;
      case ContentSuggestionsModuleType::kTabResumption:
        if (![self shouldShowTabResumption]) {
          break;
        }
        // If ShouldHideIrrelevantModules() is enabled and it is not ranked as
        // the first two modules, do not add it to the Magic Stack.
        if (ShouldHideIrrelevantModules() && [magicStackOrder count] > 1) {
          break;
        }
        [magicStackOrder addObject:_tabResumptionMediator.itemConfig];
        break;
      case ContentSuggestionsModuleType::kSafetyCheck: {
        // Handles adding Safety Check to Magic Stack. Disables/hides if:
        // - Manually disabled or disabled via preferences.
        // - No current or previous issues, to avoid consistently displaying the
        // "All Safe" state and taking up carousel space for other modules.
        // - Irrelevant modules are hidden and it's not the first ranked module.
        BOOL disabled =
            !IsSafetyCheckMagicStackEnabled() ||
            safety_check_prefs::IsSafetyCheckInMagicStackDisabled(
                IsHomeCustomizationEnabled() ? _prefService : _localState);

        if (disabled) {
          base::UmaHistogramEnumeration(
              kIOSSafetyCheckMagicStackHiddenReason,
              IOSSafetyCheckHiddenReason::kManuallyDisabled);
          break;
        }

        int previousIssuesCount = _localState->GetInteger(
            prefs::kHomeCustomizationMagicStackSafetyCheckIssuesCount);

        int issuesCount =
            [_safetyCheckMediator.safetyCheckState numberOfIssues];

        BOOL hidden = ShouldHideSafetyCheckModuleIfNoIssues() &&
                      (previousIssuesCount == 0) &&
                      (previousIssuesCount == issuesCount);

        if (hidden) {
          base::UmaHistogramEnumeration(kIOSSafetyCheckMagicStackHiddenReason,
                                        IOSSafetyCheckHiddenReason::kNoIssues);
          break;
        }

        // If ShouldHideIrrelevantModules() is enabled and it is not the first
        // ranked module, do not add it to the Magic Stack.
        if (!ShouldHideIrrelevantModules() || [magicStackOrder count] == 0) {
          [magicStackOrder addObject:_safetyCheckMediator.safetyCheckState];
        }

        break;
      }
      case ContentSuggestionsModuleType::kShortcuts:
        [magicStackOrder addObject:_shortcutsMediator.shortcutsConfig];
        break;
      case ContentSuggestionsModuleType::kParcelTracking:
        if (IsIOSParcelTrackingEnabled() &&
            !IsParcelTrackingDisabled(
                IsHomeCustomizationEnabled() ? _prefService : _localState) &&
            _parcelTrackingMediator.parcelTrackingItemToShow) {
          [magicStackOrder
              addObject:_parcelTrackingMediator.parcelTrackingItemToShow];
        }
        break;
      default:
        // These module types should not have been added by the logic
        // receiving the order list from Segmentation.
        NOTREACHED_IN_MIGRATION();
        break;
    }
  }
  return magicStackOrder;
}

// Returns NO if client is expecting the order from Segmentation and it has not
// returned yet.
- (BOOL)isMagicStackOrderReady {
    return _magicStackOrderFromSegmentationReceived;
}

// Shows the tab resumption tile with the given `item` configuration.
- (void)showTabResumptionWithItem:(TabResumptionItem*)item {
  if (tab_resumption_prefs::IsLastOpenedURL(item.tabURL, _prefService)) {
    return;
  }

  if (![self isMagicStackOrderReady]) {
    return;
  }
  NSArray<MagicStackModule*>* rank = [self latestMagicStackConfigRank];
  NSUInteger index = [rank indexOfObject:item];
  [self.delegate magicStackRankingModel:self didInsertItem:item atIndex:index];
}

// Returns YES if the tab resumption module should added into the Magic Stack.
- (BOOL)shouldShowTabResumption {
  return IsTabResumptionEnabled() &&
         !tab_resumption_prefs::IsTabResumptionDisabled(
             IsHomeCustomizationEnabled() ? _prefService : _localState) &&
         _tabResumptionMediator.itemConfig;
}

@end