chromium/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm

// Copyright 2018 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/ntp/ui_bundled/new_tab_page_coordinator.h"

#import <MaterialComponents/MaterialSnackbar.h>

#import "base/feature_list.h"
#import "base/metrics/field_trial_params.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/time/time.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/feed/core/v2/public/common_enums.h"
#import "components/feed/core/v2/public/ios/pref_names.h"
#import "components/feed/feed_feature_list.h"
#import "components/policy/policy_constants.h"
#import "components/pref_registry/pref_registry_syncable.h"
#import "components/prefs/pref_service.h"
#import "components/search/search.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/signin/public/identity_manager/tribool.h"
#import "components/supervised_user/core/common/features.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/browser/context_menu/ui_bundled/link_preview/link_preview_coordinator.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_observer_bridge.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.h"
#import "ios/chrome/browser/discover_feed/model/feed_constants.h"
#import "ios/chrome/browser/discover_feed/model/feed_model_configuration.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/follow/model/follow_browser_agent.h"
#import "ios/chrome/browser/follow/model/followed_web_site.h"
#import "ios/chrome/browser/follow/model/followed_web_site_state.h"
#import "ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.h"
#import "ios/chrome/browser/home_customization/coordinator/home_customization_delegate.h"
#import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_state.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_constants.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
#import "ios/chrome/browser/ntp/shared/metrics/home_metrics.h"
#import "ios/chrome/browser/ntp/shared/metrics/new_tab_page_metrics_constants.h"
#import "ios/chrome/browser/ntp/shared/metrics/new_tab_page_metrics_recorder.h"
#import "ios/chrome/browser/ntp/ui_bundled/discover_feed_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/discover_feed_manage_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/discover_feed_preview_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_control_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_header_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_management/feed_management_coordinator.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_menu_coordinator.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_sign_in_promo_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_top_section/feed_top_section_coordinator.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_wrapper_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/home_start_data_source.h"
#import "ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/logo_vendor.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_component_factory_protocol.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_content_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_controller_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator+Testing.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_follow_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_commands.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_metrics_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.h"
#import "ios/chrome/browser/overscroll_actions/ui_bundled/overscroll_actions_controller.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/help_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_commands.h"
#import "ios/chrome/browser/shared/public/commands/omnibox_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/snackbar_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/authentication_service_observer_bridge.h"
#import "ios/chrome/browser/signin/model/capabilities_types.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity_manager.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_capabilities_observer_bridge.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/authentication/account_menu/account_menu_coordinator.h"
#import "ios/chrome/browser/ui/authentication/account_menu/account_menu_coordinator_delegate.h"
#import "ios/chrome/browser/ui/authentication/enterprise/enterprise_utils.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.h"
#import "ios/chrome/browser/ui/sharing/sharing_coordinator.h"
#import "ios/chrome/browser/ui/sharing/sharing_params.h"
#import "ios/chrome/browser/ui/toolbar/public/fakebox_focuser.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/ui_utils/ui_utils_api.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state_observer_bridge.h"
#import "ui/base/l10n/l10n_util_mac.h"

@interface NewTabPageCoordinator () <AccountMenuCoordinatorDelegate,
                                     AppStateObserver,
                                     AuthenticationServiceObserving,
                                     BooleanObserver,
                                     ContentSuggestionsDelegate,
                                     DiscoverFeedManageDelegate,
                                     DiscoverFeedObserverBridgeDelegate,
                                     DiscoverFeedPreviewDelegate,
                                     FeedControlDelegate,
                                     FeedMenuCoordinatorDelegate,
                                     FeedSignInPromoDelegate,
                                     FeedWrapperViewControllerDelegate,
                                     HomeCustomizationDelegate,
                                     HomeStartDataSource,
                                     IdentityManagerObserverBridgeDelegate,
                                     NewTabPageContentDelegate,
                                     NewTabPageDelegate,
                                     NewTabPageFollowDelegate,
                                     NewTabPageHeaderCommands,
                                     NewTabPageMetricsDelegate,
                                     OverscrollActionsControllerDelegate,
                                     SceneStateObserver,
                                     SupervisedUserCapabilitiesObserving> {
  // Observes changes in the IdentityManager.
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityObserverBridge;

  // Observes changes in the DiscoverFeed.
  std::unique_ptr<DiscoverFeedObserverBridge> _discoverFeedObserverBridge;

  // Observer for auth service status changes.
  std::unique_ptr<AuthenticationServiceObserverBridge>
      _authServiceObserverBridge;

  // Observer to track changes to supervision-related capabilities.
  std::unique_ptr<supervised_user::SupervisedUserCapabilitiesObserverBridge>
      _supervisedUserCapabilitiesObserverBridge;
}

// Coordinator for the ContentSuggestions.
@property(nonatomic, strong)
    ContentSuggestionsCoordinator* contentSuggestionsCoordinator;

// View controller for the regular NTP.
@property(nonatomic, strong) NewTabPageViewController* NTPViewController;

// Mediator owned by this coordinator.
@property(nonatomic, strong) NewTabPageMediator* NTPMediator;

// View controller wrapping the feed.
@property(nonatomic, strong)
    FeedWrapperViewController* feedWrapperViewController;

// View controller for the incognito NTP.
@property(nonatomic, strong) IncognitoViewController* incognitoViewController;

// The timetick of the last time the NTP was displayed.
@property(nonatomic, assign) base::TimeTicks didAppearTime;

// Tracks the visibility of the NTP to report NTP usage metrics.
// True if the NTP view is currently displayed to the user.
// Redefined to readwrite.
@property(nonatomic, assign, readwrite) BOOL visible;

// The ViewController displayed by this Coordinator. This is the returned
// ViewController and will contain the `containedViewController` (Which can
// change depending on Feed visibility).
@property(nonatomic, strong) UIViewController* containerViewController;

// The coordinator contained ViewController.
@property(nonatomic, strong) UIViewController* containedViewController;

// PrefService used by this Coordinator.
@property(nonatomic, assign) PrefService* prefService;

// Whether the feed is expanded or collapsed. Collapsed
// means the feed header is shown, but not any of the feed content.
@property(nonatomic, strong) PrefBackedBoolean* feedExpandedPref;

// The view controller representing the selected feed, such as the Discover or
// Following feed.
@property(nonatomic, weak) UIViewController* feedViewController;

// The Coordinator to display previews for Discover feed websites. It also
// handles the actions related to them.
@property(nonatomic, strong) LinkPreviewCoordinator* linkPreviewCoordinator;

// The view controller representing the NTP feed header.
@property(nonatomic, strong) FeedHeaderViewController* feedHeaderViewController;

// Coordinator for handling the feed menu.
@property(nonatomic, strong) FeedMenuCoordinator* feedMenuCoordinator;

// Authentication Service for the user's signed-in state.
@property(nonatomic, assign) AuthenticationService* authService;

// TemplateURL used to get the search engine.
@property(nonatomic, assign) TemplateURLService* templateURLService;

// DiscoverFeed Service to display the Feed.
@property(nonatomic, assign) DiscoverFeedService* discoverFeedService;

// Metrics recorder for actions relating to the feed.
@property(nonatomic, weak) FeedMetricsRecorder* feedMetricsRecorder;

// The header view controller containing the fake omnibox and logo.
@property(nonatomic, strong)
    NewTabPageHeaderViewController* headerViewController;

// The coordinator for handling feed management.
@property(nonatomic, strong)
    FeedManagementCoordinator* feedManagementCoordinator;

// Coordinator for Feed top section.
@property(nonatomic, strong)
    FeedTopSectionCoordinator* feedTopSectionCoordinator;

// Currently selected feed. Redefined to readwrite.
@property(nonatomic, assign, readwrite) FeedType selectedFeed;

// The Webstate associated with this coordinator.
@property(nonatomic, assign) web::WebState* webState;

// Returns `YES` if the coordinator is started.
@property(nonatomic, assign) BOOL started;

// Contains a factory which can generate NTP components which are initialized
// on `start`.
@property(nonatomic, strong) id<NewTabPageComponentFactoryProtocol>
    componentFactory;

// Recorder for new tab page metrics.
@property(nonatomic, strong) NewTabPageMetricsRecorder* NTPMetricsRecorder;

// Logo vendor to display the doodle on the NTP.
@property(nonatomic, strong) id<LogoVendor> logoVendor;

@end

@implementation NewTabPageCoordinator {
  // Coordinator in charge of handling sharing use cases.
  SharingCoordinator* _sharingCoordinator;
  // Coordinator in charge of fast account menu.
  AccountMenuCoordinator* _accountMenuCoordinator;
  // Coordinator for presenting the Home customization menu.
  HomeCustomizationCoordinator* _customizationCoordinator;
}

// Synthesize NewTabPageConfiguring properties.
@synthesize shouldScrollIntoFeed = _shouldScrollIntoFeed;
@synthesize baseViewController = _baseViewController;

#pragma mark - ChromeCoordinator

- (instancetype)initWithBrowser:(Browser*)browser
               componentFactory:
                   (id<NewTabPageComponentFactoryProtocol>)componentFactory {
  DCHECK(browser);
  self = [super initWithBaseViewController:nil browser:browser];
  if (self) {
    _componentFactory = componentFactory;
    _containerViewController = [[UIViewController alloc] init];
  }
  return self;
}

- (void)start {
  if (self.started) {
    return;
  }

  DCHECK(self.browser);
  DCHECK(self.toolbarDelegate);
  DCHECK(!self.contentSuggestionsCoordinator);

  self.webState = self.browser->GetWebStateList()->GetActiveWebState();
  DCHECK(self.webState);
  DCHECK(NewTabPageTabHelper::FromWebState(self.webState)->IsActive());

  // Start observing SceneState changes.
  SceneState* sceneState = self.browser->GetSceneState();
  [sceneState addObserver:self];

  // Configures incognito NTP if user is in incognito mode.
  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    DCHECK(!self.incognitoViewController);
    UrlLoadingBrowserAgent* URLLoader =
        UrlLoadingBrowserAgent::FromBrowser(self.browser);
    self.incognitoViewController =
        [[IncognitoViewController alloc] initWithUrlLoader:URLLoader];
    self.started = YES;
    return;
  }

  // NOTE: anything that executes below WILL NOT execute for OffTheRecord
  // browsers!

  self.selectedFeed = NewTabPageTabHelper::FromWebState(self.webState)
                          ->GetNTPState()
                          .selectedFeed;

  [self initializeServices];
  [self initializeNTPComponents];
  [self startObservers];

  // Do not focus on omnibox for voice over if there are other screens to
  // show.
  AppState* appState = sceneState.appState;
  [appState addObserver:self];
  if (appState.initStage < InitStageFinal) {
    self.NTPViewController.focusAccessibilityOmniboxWhenViewAppears = NO;
  }

  // Update the feed if the account is subject to parental controls.
  if (base::FeatureList::IsEnabled(
          supervised_user::
              kReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS)) {
    signin::IdentityManager* identityManager =
        IdentityManagerFactory::GetForBrowserState(
            self.browser->GetBrowserState());
    signin::Tribool capability =
        supervised_user::IsPrimaryAccountSubjectToParentalControls(
            identityManager);
    [self
        updateFeedWithIsSupervisedUser:(capability == signin::Tribool::kTrue)];
  } else {
    // Update asynchronously using system capabilities.
    [self updateFeedVisibilityForSupervision];
  }

  [self configureNTPMediator];
  if (self.NTPMediator.feedHeaderVisible) {
    [self configureFeedAndHeader];
  }
  [self configureHeaderViewController];
  [self configureContentSuggestionsCoordinator];
  [self configureFeedMetricsRecorder];
  [self configureNTPViewController];

  self.started = YES;
}

- (void)stop {
  if (!self.started) {
    return;
  }

  _webState = nullptr;

  SceneState* sceneState = self.browser->GetSceneState();
  [sceneState removeObserver:self];

  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    self.incognitoViewController = nil;
    self.started = NO;
    return;
  }

  // NOTE: anything that executes below WILL NOT execute for OffTheRecord
  // browsers!

  [sceneState.appState removeObserver:self];

  [self.feedManagementCoordinator stop];
  self.feedManagementCoordinator = nil;
  [self.contentSuggestionsCoordinator stop];
  self.contentSuggestionsCoordinator = nil;
  self.headerViewController = nil;
  // Remove before nil to ensure View Hierarchy doesn't hold last strong
  // reference.
  [self.containedViewController willMoveToParentViewController:nil];
  [self.containedViewController.view removeFromSuperview];
  [self.containedViewController removeFromParentViewController];
  self.containedViewController = nil;
  [self.NTPViewController invalidate];
  self.NTPViewController = nil;
  self.feedHeaderViewController.NTPDelegate = nil;
  self.feedHeaderViewController = nil;
  [self.feedTopSectionCoordinator stop];
  self.feedTopSectionCoordinator = nil;

  self.NTPMetricsRecorder = nil;

  [self.linkPreviewCoordinator stop];
  self.linkPreviewCoordinator = nil;

  self.authService = nil;
  self.templateURLService = nil;
  self.prefService = nil;

  [self.NTPMediator shutdown];
  self.NTPMediator = nil;

  if (self.feedViewController) {
    self.discoverFeedService->RemoveFeedViewController(self.feedViewController);
  }
  self.feedWrapperViewController = nil;
  self.feedViewController = nil;
  self.feedMetricsRecorder.followDelegate = nil;
  self.feedMetricsRecorder.NTPMetricsDelegate = nil;
  self.feedMetricsRecorder = nil;

  [self.feedExpandedPref setObserver:nil];
  self.feedExpandedPref = nil;

  [self.feedMenuCoordinator stop];
  self.feedMenuCoordinator = nil;

  _supervisedUserCapabilitiesObserverBridge.reset();
  _discoverFeedObserverBridge.reset();
  _identityObserverBridge.reset();
  _authServiceObserverBridge.reset();

  [_sharingCoordinator stop];
  _sharingCoordinator = nil;

  [_customizationCoordinator stop];
  _customizationCoordinator = nil;

  [self stopAccountMenuCoordinator];

  self.started = NO;
}

#pragma mark - Public

- (void)stopIfNeeded {
  WebStateList* webStateList = self.browser->GetWebStateList();
  for (int i = 0; i < webStateList->count(); i++) {
    NewTabPageTabHelper* iterNtpHelper =
        NewTabPageTabHelper::FromWebState(webStateList->GetWebStateAt(i));
    if (iterNtpHelper->IsActive()) {
      return;
    }
  }
  // No active NTPs were found.
  [self stop];
}

- (BOOL)isNTPActiveForCurrentWebState {
  if (!self.webState) {
    return NO;
  }
  NewTabPageTabHelper* NTPHelper =
      NewTabPageTabHelper::FromWebState(self.webState);
  return NTPHelper && NTPHelper->IsActive();
}

- (BOOL)isScrolledToTop {
  return [self.NTPViewController isNTPScrolledToTop];
}

- (void)willUpdateSnapshot {
  if (self.contentSuggestionsCoordinator.started) {
    [self.NTPViewController willUpdateSnapshot];
  }
}

- (void)focusFakebox {
  if (IsHomeCustomizationEnabled()) {
    [self dismissCustomizationMenu];
  }
  [self.NTPViewController focusOmnibox];
}

- (void)reload {
  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    return;
  }
  [self.contentSuggestionsCoordinator refresh];
  // Call this before RefreshFeed() to ensure some NTP state configs are reset
  // before callbacks in repsonse to a feed refresh are called, ensuring the NTP
  // returns to a state at the top of the surface upon refresh.
  [self.NTPViewController resetStateUponReload];
  self.discoverFeedService->RefreshFeed(
      FeedRefreshTrigger::kForegroundUserTriggered);
}

- (void)locationBarDidBecomeFirstResponder {
  [self.NTPViewController omniboxDidBecomeFirstResponder];
}

- (void)locationBarWillResignFirstResponder {
  [self.NTPViewController omniboxWillResignFirstResponder];
}

- (void)locationBarDidResignFirstResponder {
  [self.NTPViewController omniboxDidResignFirstResponder];
}

- (void)constrainNamedGuideForFeedIPH {
  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    return;
  }
  UIView* viewToConstrain =
      IsHomeCustomizationEnabled()
          ? [self.headerViewController customizationMenuButton]
          : self.feedHeaderViewController.managementButton;
  [LayoutGuideCenterForBrowser(self.browser) referenceView:viewToConstrain
                                                 underName:kFeedIPHNamedGuide];
}

- (void)updateFollowingFeedHasUnseenContent:(BOOL)hasUnseenContent {
  if (![self isFollowingFeedAvailable] ||
      !IsDotEnabledForNewFollowedContent()) {
    return;
  }
  if ([self doesFollowingFeedHaveContent]) {
    [self.feedHeaderViewController
        updateFollowingDotForUnseenContent:hasUnseenContent];
  }
}

- (void)handleFeedModelOfType:(FeedType)feedType
                didEndUpdates:(FeedLayoutUpdateType)updateType {
  DCHECK(self.NTPViewController);
  if (!self.feedViewController) {
    return;
  }
  // When the visible feed has been updated, recalculate the minimum NTP height.
  if (feedType == self.selectedFeed) {
    [self.NTPViewController feedLayoutDidEndUpdatesWithType:updateType];
  }
}

- (void)didNavigateToNTPInWebState:(web::WebState*)webState {
  CHECK(self.started);
  self.webState = webState;
  [self restoreNTPState];
  [self updateNTPIsVisible:YES];
  [self updateStartForVisibilityChange:YES];
  [self.toolbarDelegate didNavigateToNTPOnActiveWebState];
}

- (void)didNavigateAwayFromNTP {
  [self cancelOmniboxEdit];
  if (IsHomeCustomizationEnabled()) {
    [self dismissCustomizationMenu];
  }
  [self saveNTPState];
  [self updateNTPIsVisible:NO];
  [self updateStartForVisibilityChange:NO];
  self.webState = nullptr;
}

- (BOOL)isFakeboxPinned {
  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    return YES;
  }
  return self.NTPViewController.isFakeboxPinned;
}

#pragma mark - Setters

- (void)setSelectedFeed:(FeedType)selectedFeed {
  if (_selectedFeed == selectedFeed) {
    return;
  }
  // Updates the NTP state with the newly selected feed.
  [self saveNTPState];

  // Tell Metrics Recorder the feed has changed.
  [self.feedMetricsRecorder recordFeedTypeChangedFromFeed:_selectedFeed];
  _selectedFeed = selectedFeed;
}

#pragma mark - Initializers

// Gets all NTP services from the browser state.
- (void)initializeServices {
  self.authService = AuthenticationServiceFactory::GetForBrowserState(
      self.browser->GetBrowserState());
  self.templateURLService = ios::TemplateURLServiceFactory::GetForBrowserState(
      self.browser->GetBrowserState());
  self.discoverFeedService = DiscoverFeedServiceFactory::GetForBrowserState(
      self.browser->GetBrowserState());
  self.prefService =
      ChromeBrowserState::FromBrowserState(self.browser->GetBrowserState())
          ->GetPrefs();
}

// Starts all NTP observers.
- (void)startObservers {
  DCHECK(self.prefService);
  DCHECK(self.headerViewController);

  self.feedExpandedPref = [[PrefBackedBoolean alloc]
      initWithPrefService:self.prefService
                 prefName:feed::prefs::kArticlesListVisible];
  // Observer is necessary for multiwindow NTPs to remain in sync.
  [self.feedExpandedPref setObserver:self];

  // Start observing IdentityManager.
  signin::IdentityManager* identityManager =
      IdentityManagerFactory::GetForBrowserState(
          self.browser->GetBrowserState());
  _identityObserverBridge =
      std::make_unique<signin::IdentityManagerObserverBridge>(identityManager,
                                                              self);

  // Start observing supervised user capabilities.
  _supervisedUserCapabilitiesObserverBridge = std::make_unique<
      supervised_user::SupervisedUserCapabilitiesObserverBridge>(
      identityManager, self);

  // Start observing DiscoverFeedService.
  _discoverFeedObserverBridge = std::make_unique<DiscoverFeedObserverBridge>(
      self, self.discoverFeedService);

  // Start observing Authentication service.
  _authServiceObserverBridge =
      std::make_unique<AuthenticationServiceObserverBridge>(self.authService,
                                                            self);
}

// Creates all the NTP components.
- (void)initializeNTPComponents {
  Browser* browser = self.browser;
  id<NewTabPageComponentFactoryProtocol> componentFactory =
      self.componentFactory;
  self.logoVendor = ios::provider::CreateLogoVendor(browser, self.webState);
  self.NTPViewController = [componentFactory NTPViewController];
  self.headerViewController =
      [componentFactory headerViewControllerForBrowser:browser];
  self.NTPMediator =
      [componentFactory NTPMediatorForBrowser:browser
                     identityDiscImageUpdater:self.headerViewController];
  self.contentSuggestionsCoordinator =
      [componentFactory contentSuggestionsCoordinatorForBrowser:browser];
  self.feedMetricsRecorder =
      [componentFactory feedMetricsRecorderForBrowser:browser];
  self.NTPMetricsRecorder = [[NewTabPageMetricsRecorder alloc] init];
}

#pragma mark - Configurators

// Creates and configures the feed and feed header based on user prefs.
- (void)configureFeedAndHeader {
  CHECK(self.NTPMediator.feedHeaderVisible);
  CHECK(self.NTPViewController);

  if (!self.feedHeaderViewController) {
    BOOL followingDotVisible = NO;
    if (IsDotEnabledForNewFollowedContent() && IsWebChannelsEnabled()) {
      // Only show the dot if the user follows available publishers.
      followingDotVisible =
          [self doesFollowingFeedHaveContent] &&
          self.discoverFeedService->GetFollowingFeedHasUnseenContent() &&
          self.selectedFeed != FeedTypeFollowing;
    }

    self.feedHeaderViewController = [self.componentFactory
        feedHeaderViewControllerWithFollowingDotVisible:followingDotVisible];
    self.feedMenuCoordinator = [[FeedMenuCoordinator alloc]
        initWithBaseViewController:self.NTPViewController
                           browser:self.browser];
    self.feedMenuCoordinator.delegate = self;
    [self.feedMenuCoordinator start];
    self.feedHeaderViewController.feedMenuHandler = HandlerForProtocol(
        self.browser->GetCommandDispatcher(), FeedMenuCommands);
  }

  self.feedHeaderViewController.feedControlDelegate = self;
  self.feedHeaderViewController.NTPDelegate = self;
  self.feedHeaderViewController.feedMetricsRecorder = self.feedMetricsRecorder;
  if (!IsFollowUIUpdateEnabled()) {
    self.feedHeaderViewController.followingFeedSortType =
        self.followingFeedSortType;
  }
  self.NTPViewController.feedHeaderViewController =
      self.feedHeaderViewController;

  // Requests feeds here if the correct flags and prefs are enabled.
  if ([self shouldFeedBeVisible]) {
    if ([self isFollowingFeedAvailable] &&
        self.selectedFeed == FeedTypeFollowing) {
      self.feedViewController = [self.componentFactory
              followingFeedForBrowser:self.browser
          viewControllerConfiguration:[self feedViewControllerConfiguration]
                             sortType:self.followingFeedSortType];
    } else {
      self.feedViewController = [self.componentFactory
               discoverFeedForBrowser:self.browser
          viewControllerConfiguration:[self feedViewControllerConfiguration]];
    }
  }

  // Feed top section visibility is based on feed visibility, so this should
  // always be below the block that sets `feedViewController`.
  if ([self isFeedVisible]) {
    self.feedTopSectionCoordinator = [self createFeedTopSectionCoordinator];
  }
}

// Configures `self.headerViewController`.
- (void)configureHeaderViewController {
  DCHECK(self.headerViewController);
  DCHECK(self.NTPMediator);
  DCHECK(self.NTPMetricsRecorder);

  self.headerViewController.isGoogleDefaultSearchEngine =
      [self isGoogleDefaultSearchEngine];
  // TODO(crbug.com/40670043): Use HandlerForProtocol after commands protocol
  // clean up.
  self.headerViewController.dispatcher =
      static_cast<id<ApplicationCommands, BrowserCoordinatorCommands,
                     OmniboxCommands, FakeboxFocuser, LensCommands>>(
          self.browser->GetCommandDispatcher());
  self.headerViewController.commandHandler = self;
  self.headerViewController.delegate = self.NTPViewController;
  self.headerViewController.layoutGuideCenter =
      LayoutGuideCenterForBrowser(self.browser);
  self.headerViewController.toolbarDelegate = self.toolbarDelegate;
  self.headerViewController.baseViewController = self.baseViewController;
  self.headerViewController.NTPMetricsRecorder = self.NTPMetricsRecorder;
  [self.headerViewController setLogoVendor:self.logoVendor];
}

// Configures `self.contentSuggestionsCoordiantor`.
- (void)configureContentSuggestionsCoordinator {
  self.contentSuggestionsCoordinator.webState = self.webState;
  self.contentSuggestionsCoordinator.delegate = self;
  self.contentSuggestionsCoordinator.NTPMetricsDelegate = self;
  self.contentSuggestionsCoordinator.homeStartDataSource = self;
  [self.contentSuggestionsCoordinator start];
}

// Configures `self.NTPMediator`.
- (void)configureNTPMediator {
  NewTabPageMediator* NTPMediator = self.NTPMediator;
  DCHECK(NTPMediator);
  NTPMediator.feedControlDelegate = self;
  NTPMediator.NTPContentDelegate = self;
  NTPMediator.headerConsumer = self.headerViewController;
  NTPMediator.consumer = self.NTPViewController;
  [NTPMediator setUp];
}

// Configures `self.feedMetricsRecorder`.
- (void)configureFeedMetricsRecorder {
  CHECK(self.webState);
  self.feedMetricsRecorder.NTPState =
      NewTabPageTabHelper::FromWebState(self.webState)->GetNTPState();
  self.feedMetricsRecorder.followDelegate = self;
  self.feedMetricsRecorder.NTPMetricsDelegate = self;
}

// Configures `self.NTPViewController` and sets it up as the main ViewController
// managed by this Coordinator.
- (void)configureNTPViewController {
  DCHECK(self.NTPViewController);

  self.NTPViewController.magicStackCollectionView =
      self.contentSuggestionsCoordinator.magicStackCollectionView;
  self.NTPViewController.contentSuggestionsViewController =
      self.contentSuggestionsCoordinator.viewController;

  self.NTPViewController.feedVisible = [self isFeedVisible];

  self.feedWrapperViewController = [self.componentFactory
      feedWrapperViewControllerWithDelegate:self
                         feedViewController:self.feedViewController];

  if ([self isFeedVisible]) {
    self.NTPViewController.feedTopSectionViewController =
        self.feedTopSectionCoordinator.viewController;
  }

  self.NTPViewController.feedWrapperViewController =
      self.feedWrapperViewController;
  self.NTPViewController.overscrollDelegate = self;
  self.NTPViewController.NTPContentDelegate = self;

  self.NTPViewController.headerViewController = self.headerViewController;

  [self configureMainViewControllerUsing:self.NTPViewController];
  self.NTPViewController.feedMetricsRecorder = self.feedMetricsRecorder;
  self.NTPViewController.helpHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(), HelpCommands);
  self.NTPViewController.mutator = self.NTPMediator;
}

// Configures the main ViewController managed by this Coordinator.
- (void)configureMainViewControllerUsing:
    (UIViewController*)containedViewController {
  [containedViewController
      willMoveToParentViewController:self.containerViewController];
  [self.containerViewController addChildViewController:containedViewController];
  [self.containerViewController.view addSubview:containedViewController.view];
  [containedViewController
      didMoveToParentViewController:self.containerViewController];

  containedViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
  AddSameConstraints(containedViewController.view,
                     self.containerViewController.view);

  self.containedViewController = containedViewController;
}

#pragma mark - Properties

- (UIViewController*)viewController {
  DCHECK(self.started);
  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
    return self.incognitoViewController;
  } else {
    return self.containerViewController;
  }
}

#pragma mark - NewTabPageConfiguring

- (void)selectFeedType:(FeedType)feedType {
  if (!self.NTPViewController.viewDidAppear ||
      ![self isFollowingFeedAvailable]) {
    self.selectedFeed = feedType;
    return;
  }
  [self handleFeedSelected:feedType];
}

#pragma mark - NewTabPageHeaderCommands

- (void)updateForHeaderSizeChange {
  [self.NTPViewController updateHeightAboveFeed];
}

- (void)fakeboxTapped {
  [self focusFakebox];
}

- (void)identityDiscWasTapped:(UIView*)identityDisc {
  if (IsHomeCustomizationEnabled()) {
    [self dismissCustomizationMenu];
  }
  [self.NTPMetricsRecorder recordIdentityDiscTapped];
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);

  BOOL isSignedIn =
      self.authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
  if (![self isSignInAllowed]) {
    [handler showSettingsFromViewController:self.baseViewController];
  } else if (isSignedIn) {
    if (base::FeatureList::IsEnabled(kIdentityDiscAccountMenu)) {
      _accountMenuCoordinator = [[AccountMenuCoordinator alloc]
          initWithBaseViewController:self.baseViewController
                             browser:self.browser];
      _accountMenuCoordinator.delegate = self;
      _accountMenuCoordinator.anchorView = identityDisc;
      // TODO(crbug.com/336719423): Record signin metrics based on the selected
      // action from the account switcher.
      [_accountMenuCoordinator start];
    } else {
      [handler showSettingsFromViewController:self.baseViewController];
    }
  } else {
    ShowSigninCommand* const showSigninCommand = [[ShowSigninCommand alloc]
        initWithOperation:AuthenticationOperation::kSheetSigninAndHistorySync
              accessPoint:signin_metrics::AccessPoint::
                              ACCESS_POINT_NTP_SIGNED_OUT_ICON];
    [handler showSignin:showSigninCommand
        baseViewController:self.baseViewController];
  }
}

- (void)customizationMenuWasTapped:(UIView*)customizationMenu {
  if (_customizationCoordinator) {
    // The menu is already opened, so tapping an entrypoint again should close
    // it.
    [self dismissCustomizationMenu];
    return;
  }

  if (self.prefService->GetInteger(
          prefs::kNTPHomeCustomizationNewBadgeImpressionCount) <=
      kCustomizationNewBadgeMaxImpressionCount) {
    base::RecordAction(
        base::UserMetricsAction(kNTPCustomizationNewBadgeTappedAction));
    // Set the new badge impression count to `INT_MAX` to ensure it isn't shown
    // again, even if we increase the max impression count.
    self.prefService->SetInteger(
        prefs::kNTPHomeCustomizationNewBadgeImpressionCount, INT_MAX);

    [self.headerViewController hideBadgeOnCustomizationMenu];
  }

  [self.NTPMetricsRecorder recordHomeCustomizationMenuOpenedFromEntrypoint:
                               HomeCustomizationEntrypoint::kMain];

  [self openCustomizationMenuAtPage:CustomizationMenuPage::kMain animated:YES];
}

#pragma mark - FeedMenuCoordinatorDelegate

- (void)didSelectFeedMenuItem:(FeedMenuItemType)item {
  switch (item) {
    case FeedMenuItemType::kTurnOff:
      [self setFeedVisibleFromHeader:NO];
      break;
    case FeedMenuItemType::kTurnOn:
      [self setFeedVisibleFromHeader:YES];
      break;
    case FeedMenuItemType::kManage:
      [self handleFeedManageTapped];
      break;
    case FeedMenuItemType::kManageActivity:
      [self.NTPMediator handleNavigateToActivity];
      break;
    case FeedMenuItemType::kManageFollowing:
      [self.NTPMediator handleNavigateToFollowing];
      break;
    case FeedMenuItemType::kLearnMore:
      [self.NTPMediator handleFeedLearnMoreTapped];
      break;
  }
}

#pragma mark - DiscoverFeedManageDelegate

- (void)didTapDiscoverFeedManage {
  [self handleFeedManageTapped];
}

#pragma mark - DiscoverFeedPreviewDelegate

- (UIViewController*)discoverFeedPreviewWithURL:(const GURL)URL {
  std::string referrerURL = base::GetFieldTrialParamValueByFeature(
      kOverrideFeedSettings, kFeedSettingDiscoverReferrerParameter);
  if (referrerURL.empty()) {
    referrerURL = kDefaultDiscoverReferrer;
  }

  self.linkPreviewCoordinator =
      [[LinkPreviewCoordinator alloc] initWithBrowser:self.browser URL:URL];
  self.linkPreviewCoordinator.referrer =
      web::Referrer(GURL(referrerURL), web::ReferrerPolicyDefault);
  [self.linkPreviewCoordinator start];
  return [self.linkPreviewCoordinator linkPreviewViewController];
}

- (void)didTapDiscoverFeedPreview {
  DCHECK(self.linkPreviewCoordinator);
  [self.linkPreviewCoordinator handlePreviewAction];
  [self.linkPreviewCoordinator stop];
  self.linkPreviewCoordinator = nil;
}

#pragma mark - FeedControlDelegate

- (FollowingFeedSortType)followingFeedSortType {
  // TODO(crbug.com/40858105): Add a DCHECK to make sure the coordinator isn't
  // stopped when we check this. That would require us to use the NTPHelper to
  // get this information.
  return (FollowingFeedSortType)self.prefService->GetInteger(
      prefs::kNTPFollowingFeedSortType);
}

- (void)handleFeedSelected:(FeedType)feedType {
  DCHECK([self isFollowingFeedAvailable]);

  if (self.selectedFeed == feedType) {
    return;
  }
  self.selectedFeed = feedType;

  // Saves scroll position before changing feed.
  CGFloat scrollPosition = [self.NTPViewController scrollPosition];

  if (feedType == FeedTypeFollowing && IsDotEnabledForNewFollowedContent()) {
    // Clears dot and notifies service that the Following feed content has
    // been seen.
    [self.feedHeaderViewController updateFollowingDotForUnseenContent:NO];
    self.discoverFeedService->SetFollowingFeedContentSeen();
  }

  [self handleChangeInModules];

  // Scroll position resets when changing the feed, so we set it back to what it
  // was.
  [self.NTPViewController setContentOffsetToTopOfFeedOrLess:scrollPosition];
}

- (void)handleSortTypeForFollowingFeed:(FollowingFeedSortType)sortType {
  DCHECK([self isFollowingFeedAvailable]);

  if (self.feedHeaderViewController.followingFeedSortType == sortType) {
    return;
  }

  // Save the scroll position before changing sort type.
  CGFloat scrollPosition = [self.NTPViewController scrollPosition];

  [self.feedMetricsRecorder recordFollowingFeedSortTypeSelected:sortType];
  self.prefService->SetInteger(prefs::kNTPFollowingFeedSortType, sortType);
  self.prefService->SetBoolean(prefs::kDefaultFollowingFeedSortTypeChanged,
                               true);
  self.discoverFeedService->SetFollowingFeedSortType(sortType);
  self.feedHeaderViewController.followingFeedSortType = sortType;

  [self handleChangeInModules];

  // Scroll position resets when changing the feed, so we set it back to what it
  // was.
  [self.NTPViewController setContentOffsetToTopOfFeedOrLess:scrollPosition];

  // Updates the NTP state for the newly selected sort type.
  [self saveNTPState];
}

- (BOOL)shouldFeedBeVisible {
  return self.NTPMediator.feedHeaderVisible &&
         ([self.feedExpandedPref value] || IsHomeCustomizationEnabled());
}

- (BOOL)isFollowingFeedAvailable {
  return IsWebChannelsEnabled() && self.authService &&
         self.authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
}

- (NSUInteger)lastVisibleFeedCardIndex {
  return [self.feedWrapperViewController lastVisibleFeedCardIndex];
}

- (void)setFeedAndHeaderVisibility:(BOOL)visible {
  if (!self.NTPViewController.viewLoaded) {
    return;
  }
  [self handleChangeInModules];
  [self.NTPViewController setContentOffsetToTop];
}

- (void)updateFeedForDefaultSearchEngineChanged {
  if (!self.NTPViewController.viewLoaded) {
    return;
  }
  [self.feedHeaderViewController updateForDefaultSearchEngineChanged];
  [self updateFeedLayout];
  [self cancelOmniboxEdit];
  [self.NTPViewController setContentOffsetToTop];
}

#pragma mark - ContentSuggestionsDelegate

- (void)contentSuggestionsWasUpdated {
  [self.NTPViewController updateHeightAboveFeed];
}

- (void)shareURL:(const GURL&)URL
           title:(NSString*)title
        fromView:(UIView*)view {
  SharingParams* params =
      [[SharingParams alloc] initWithURL:URL
                                   title:title
                                scenario:SharingScenario::MostVisitedEntry];
  _sharingCoordinator = [[SharingCoordinator alloc]
      initWithBaseViewController:self.NTPViewController
                         browser:self.browser
                          params:params
                      originView:view];
  [_sharingCoordinator start];
}

- (void)openMagicStackCustomizationMenu {
  if (_customizationCoordinator) {
    // The menu is already opened, so tapping an entrypoint again should close
    // it.
    [self dismissCustomizationMenu];
    return;
  }

  [self.NTPMetricsRecorder recordHomeCustomizationMenuOpenedFromEntrypoint:
                               HomeCustomizationEntrypoint::kMagicStack];

  [self openCustomizationMenuAtPage:CustomizationMenuPage::kMagicStack
                           animated:NO];
}

#pragma mark - FeedSignInPromoDelegate

- (void)showSignInPromoUI {
  // Both possible flows (sign-in only and sign-in + sync) involve sign-in. So
  // they shouldn't be offered if sign-in is disallowed.
  if (![self isSignInAllowed]) {
    [self showSignInDisableMessage];
    [self.feedMetricsRecorder recordShowSignInRelatedUIWithType:
                                  feed::FeedSignInUI::kShowSignInDisableToast];
    return;
  }

  BOOL hasUserIdentities =
      ChromeAccountManagerServiceFactory::GetForBrowserState(
          self.browser->GetBrowserState())
          ->HasIdentities();
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  ShowSigninCommand* command = [[ShowSigninCommand alloc]
      initWithOperation:AuthenticationOperation::kSigninOnly
            accessPoint:signin_metrics::AccessPoint::
                            ACCESS_POINT_NTP_FEED_CARD_MENU_PROMO];
  [handler showSignin:command baseViewController:self.NTPViewController];
  [self.feedMetricsRecorder recordShowSignInRelatedUIWithType:
                                feed::FeedSignInUI::kShowSignInOnlyFlow];
  [self.feedMetricsRecorder recordShowSignInOnlyUIWithUserId:hasUserIdentities];
  signin_metrics::RecordSigninUserActionForAccessPoint(
      signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_CARD_MENU_PROMO);
}

- (void)showSignInUI {
  // Both possible flows (sign-in only and sign-in + sync) involve sign-in. So
  // they shouldn't be offered if sign-in is disallowed.
  if (![self isSignInAllowed]) {
    [self showSignInDisableMessage];
    [self.feedMetricsRecorder recordShowSyncnRelatedUIWithType:
                                  feed::FeedSyncPromo::kShowDisableToast];
    return;
  }

  ChromeBrowserState* browserState = self.browser->GetBrowserState();
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  // If there are 0 identities, kInstantSignin requires less taps.
  auto operation =
      ChromeAccountManagerServiceFactory::GetForBrowserState(browserState)
              ->HasIdentities()
          ? AuthenticationOperation::kSigninOnly
          : AuthenticationOperation::kInstantSignin;
  ShowSigninCommand* command = [[ShowSigninCommand alloc]
      initWithOperation:operation
            accessPoint:signin_metrics::AccessPoint::
                            ACCESS_POINT_NTP_FEED_BOTTOM_PROMO];
  [handler showSignin:command baseViewController:self.NTPViewController];
  // TODO(crbug.com/40066051): Strictly speaking this should record a bucket
  // other than kShowSyncFlow. But I don't think we care too much about this
  // particular histogram, just rename the bucket after launch.
  [self.feedMetricsRecorder
      recordShowSyncnRelatedUIWithType:feed::FeedSyncPromo::kShowSyncFlow];
  signin_metrics::RecordSigninUserActionForAccessPoint(
      signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_BOTTOM_PROMO);
}

#pragma mark - FeedWrapperViewControllerDelegate

- (void)updateTheme {
  self.discoverFeedService->UpdateTheme();
}

#pragma mark - NewTabPageContentDelegate

- (BOOL)isContentHeaderSticky {
  return [self isFollowingFeedAvailable] &&
         self.NTPMediator.feedHeaderVisible &&
         !IsStickyHeaderDisabledForFollowingFeed();
}

- (void)signinPromoHasChangedVisibility:(BOOL)visible {
  [self.feedTopSectionCoordinator signinPromoHasChangedVisibility:visible];
}

- (void)cancelOmniboxEdit {
  id<OmniboxCommands> omniboxCommandHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(), OmniboxCommands);
  [omniboxCommandHandler cancelOmniboxEdit];
}

- (void)onFakeboxBlur {
  id<FakeboxFocuser> fakeboxFocuserHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(), FakeboxFocuser);
  [fakeboxFocuserHandler onFakeboxBlur];
}

- (void)focusOmnibox {
  id<FakeboxFocuser> fakeboxFocuserHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(), FakeboxFocuser);
  [fakeboxFocuserHandler focusOmniboxFromFakeboxPinned:[self isFakeboxPinned]];
}

- (void)refreshNTPContent {
  self.discoverFeedService->RefreshFeed(
      FeedRefreshTrigger::kForegroundFeedVisibleOther);
}

- (void)updateForSelectedFeed:(FeedType)selectedFeed {
  [self selectFeedType:selectedFeed];
  if (!IsFollowUIUpdateEnabled()) {
    // Reassign the sort type in case it changed in another tab.
    self.feedHeaderViewController.followingFeedSortType =
        self.followingFeedSortType;
  }
  // Update the header so that it's synced with the currently selected
  // feed, which could have been changed when a new web state was
  // inserted.
  [self.feedHeaderViewController updateForSelectedFeed];
  self.feedMetricsRecorder.followDelegate = self;
}

- (void)updateModuleVisibility {
  [_customizationCoordinator updateMenuData];
  [self handleChangeInModules];
  [self cancelOmniboxEdit];
  [self setContentOffsetToTop];
  [self.feedHeaderViewController updateForFeedVisibilityChanged];
}

#pragma mark - NewTabPageDelegate

- (void)updateFeedLayout {
  // If this coordinator has not finished [self start], the below will start
  // viewDidLoad before the UI is ready, failing DCHECKS.
  if (!self.started) {
    return;
  }
  // TODO(crbug.com/40252945): Investigate why this order is correct. Intuition
  // would be that the layout update should happen before telling UIKit to
  // relayout.
  [self.containedViewController.view setNeedsLayout];
  [self.containedViewController.view layoutIfNeeded];
  [self.NTPViewController updateNTPLayout];
}

- (void)setContentOffsetToTop {
  [self.NTPViewController setContentOffsetToTop];
}

- (BOOL)isGoogleDefaultSearchEngine {
  return search::DefaultSearchProviderIsGoogle(self.templateURLService);
}

- (BOOL)isStartSurface {
  // The web state is nil if the NTP is in another tab. In this case, it is
  // never a start surface.
  if (!self.webState) {
    return NO;
  }
  NewTabPageTabHelper* NTPHelper =
      NewTabPageTabHelper::FromWebState(self.webState);
  return NTPHelper && NTPHelper->ShouldShowStartSurface();
}

- (void)handleFeedTopSectionClosed {
  [self.NTPViewController updateScrollPositionForFeedTopSectionClosed];
}

- (BOOL)isSignInAllowed {
  AuthenticationService::ServiceStatus statusService =
      self.authService->GetServiceStatus();
  switch (statusService) {
    case AuthenticationService::ServiceStatus::SigninDisabledByPolicy:
    case AuthenticationService::ServiceStatus::SigninDisabledByInternal:
    case AuthenticationService::ServiceStatus::SigninDisabledByUser: {
      return NO;
    }
    case AuthenticationService::ServiceStatus::SigninForcedByPolicy:
    case AuthenticationService::ServiceStatus::SigninAllowed: {
      break;
    }
  }
  return YES;
}

#pragma mark - NewTabPageFollowDelegate

- (NSUInteger)followedPublisherCount {
  return self.followedWebSites.count;
}

- (BOOL)doesFollowingFeedHaveContent {
  for (FollowedWebSite* web_site in self.followedWebSites) {
    if (web_site.state == FollowedWebSiteStateStateActive) {
      return YES;
    }
  }

  return NO;
}

- (NSArray<FollowedWebSite*>*)followedWebSites {
  FollowBrowserAgent* followBrowserAgent =
      FollowBrowserAgent::FromBrowser(self.browser);

  // Return an empty list if the BrowserAgent is null (which can happen
  // if e.g. the Browser is off-the-record).
  if (!followBrowserAgent)
    return @[];

  return followBrowserAgent->GetFollowedWebSites();
}

#pragma mark - NewTabPageMetricsDelegate

- (void)recentTabTileOpenedAtIndex:(NSUInteger)index {
  RecordMagicStackClick(ContentSuggestionsModuleType::kTabResumption,
                        [self isStartSurface]);
  RecordHomeAction(IOSHomeActionType::kReturnToRecentTab,
                   [self isStartSurface]);
  RecordMagicStackTabResumptionClick(true, [self isStartSurface], index);
}

- (void)distantTabResumptionOpenedAtIndex:(NSUInteger)index {
  RecordMagicStackClick(ContentSuggestionsModuleType::kTabResumption,
                        [self isStartSurface]);
  RecordHomeAction(IOSHomeActionType::kOpenDistantTabResumption,
                   [self isStartSurface]);
  RecordMagicStackTabResumptionClick(false, [self isStartSurface], index);
}

- (void)recentTabTileDisplayedAtIndex:(NSUInteger)index {
  LogTabResumptionImpression(true, [self isStartSurface], index);
}

- (void)distantTabResumptionDisplayedAtIndex:(NSUInteger)index {
  LogTabResumptionImpression(false, [self isStartSurface], index);
}

- (void)feedArticleOpened {
  RecordHomeAction(IOSHomeActionType::kFeedCard, [self isStartSurface]);
}

- (void)mostVisitedTileOpened {
  RecordHomeAction(IOSHomeActionType::kMostVisitedTile, [self isStartSurface]);
}

- (void)shortcutTileOpened {
  RecordMagicStackClick(ContentSuggestionsModuleType::kShortcuts,
                        [self isStartSurface]);
  RecordHomeAction(IOSHomeActionType::kShortcuts, [self isStartSurface]);
}

- (void)setUpListItemOpened {
  RecordHomeAction(IOSHomeActionType::kSetUpList, [self isStartSurface]);
}

- (void)safetyCheckOpened {
  RecordMagicStackClick(ContentSuggestionsModuleType::kSafetyCheck,
                        [self isStartSurface]);
  RecordHomeAction(IOSHomeActionType::kSafetyCheck, [self isStartSurface]);
}

- (void)parcelTrackingOpened {
  RecordMagicStackClick(ContentSuggestionsModuleType::kParcelTracking,
                        [self isStartSurface]);
  RecordHomeAction(IOSHomeActionType::kParcelTracking, [self isStartSurface]);
}

#pragma mark - OverscrollActionsControllerDelegate

- (void)overscrollActionNewTab:(OverscrollActionsController*)controller {
  id<ApplicationCommands> applicationCommandsHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  [applicationCommandsHandler openURLInNewTab:[OpenNewTabCommand command]];
  [self.NTPMetricsRecorder
      recordOverscrollActionForType:OverscrollActionType::kOpenedNewTab];
}

- (void)overscrollActionCloseTab:(OverscrollActionsController*)controller {
  id<BrowserCoordinatorCommands> browserCoordinatorCommandsHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(),
                         BrowserCoordinatorCommands);
  [browserCoordinatorCommandsHandler closeCurrentTab];
  [self.NTPMetricsRecorder
      recordOverscrollActionForType:OverscrollActionType::kCloseTab];
}

- (void)overscrollActionRefresh:(OverscrollActionsController*)controller {
  [self reload];
  [self.NTPMetricsRecorder
      recordOverscrollActionForType:OverscrollActionType::kPullToRefresh];
}

- (BOOL)shouldAllowOverscrollActionsForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  return !IsHomeCustomizationEnabled() || !_customizationCoordinator;
}

- (UIView*)toolbarSnapshotViewForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  return nil;
}

- (UIView*)headerViewForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  return self.feedWrapperViewController.view;
}

- (CGFloat)headerInsetForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  return [self.NTPViewController heightAboveFeed];
}

- (CGFloat)headerHeightForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  CGFloat height = [self.headerViewController toolBarView].bounds.size.height;
  CGFloat topInset = self.feedWrapperViewController.view.safeAreaInsets.top;
  return height + topInset;
}

- (CGFloat)initialContentOffsetForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  return -[self headerInsetForOverscrollActionsController:controller];
}

- (FullscreenController*)fullscreenControllerForOverscrollActionsController:
    (OverscrollActionsController*)controller {
  // Fullscreen isn't supported here.
  return nullptr;
}

#pragma mark - AppStateObserver

- (void)appState:(AppState*)appState
    didTransitionFromInitStage:(InitStage)previousInitStage {
  if (previousInitStage == InitStageFirstRun) {
    self.NTPViewController.focusAccessibilityOmniboxWhenViewAppears = YES;
    [self.headerViewController focusAccessibilityOnOmnibox];

    [appState removeObserver:self];
  }
}

#pragma mark - BooleanObserver

- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
  // Observes changes in feed visibility pref.
  [self updateModuleVisibility];
}

#pragma mark - DiscoverFeedObserverBridge

- (void)discoverFeedModelWasCreated {
  if (self.NTPViewController.viewDidAppear) {
    [self handleChangeInModules];

    if (IsWebChannelsEnabled()) {
      [self.feedHeaderViewController updateForFollowingFeedVisibilityChanged];
      [self updateFeedLayout];
    }
    [self.NTPViewController setContentOffsetToTop];
  }
}

#pragma mark - IdentityManagerObserverBridgeDelegate

// TODO(crbug.com/346756363): Remove this method as it is replaced with
// `onIsSubjectToParentalControlsCapabilityChanged`.
- (void)onPrimaryAccountChanged:
    (const signin::PrimaryAccountChangeEvent&)event {
  // An account change may trigger after the coordinator has been stopped.
  // In this case do not process the event.
  if (!self.started) {
    return;
  }
  switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
    case signin::PrimaryAccountChangeEvent::Type::kSet:
    case signin::PrimaryAccountChangeEvent::Type::kCleared: {
      [self.contentSuggestionsCoordinator refresh];
      [self updateFeedVisibilityForSupervision];
      break;
    }
    case signin::PrimaryAccountChangeEvent::Type::kNone:
      break;
  }
}

#pragma mark - AuthenticationServiceObserving

- (void)onServiceStatusChanged {
  switch (self.authService->GetServiceStatus()) {
    case AuthenticationService::ServiceStatus::SigninForcedByPolicy:
    case AuthenticationService::ServiceStatus::SigninAllowed:
      break;
    case AuthenticationService::ServiceStatus::SigninDisabledByUser:
    case AuthenticationService::ServiceStatus::SigninDisabledByPolicy:
    case AuthenticationService::ServiceStatus::SigninDisabledByInternal:
      // If sign-in becomes disabled, the sign-in promo must be disabled too.
      // TODO(crbug.com/40280872): The sign-in promo should just be hidden
      // instead of resetting the hierarchy.
      [self handleChangeInModules];
      [self setContentOffsetToTop];
  }
}

#pragma mark - SupervisedUserCapabilitiesObserving

- (void)onIsSubjectToParentalControlsCapabilityChanged:
    (supervised_user::CapabilityUpdateState)capabilityUpdateState {
  if (base::FeatureList::IsEnabled(
          supervised_user::
              kReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS)) {
    BOOL isSubjectToParentalControl =
        (capabilityUpdateState ==
         supervised_user::CapabilityUpdateState::kSetToTrue);
    [self updateFeedWithIsSupervisedUser:isSubjectToParentalControl];
  }
}

#pragma mark - SceneStateObserver

- (void)sceneState:(SceneState*)sceneState
    transitionedToActivationLevel:(SceneActivationLevel)level {
  // `SceneActivationLevelForegroundInactive` is called both when foregrounding
  // and backgrounding, and is thus not used as an indicator to trigger
  // visibility.
  if (self.webState && !self.visible &&
      level == SceneActivationLevelForegroundActive) {
    [self updateNTPIsVisible:YES];
  } else if (self.visible && level < SceneActivationLevelForegroundInactive) {
    [self updateNTPIsVisible:NO];
  }
}

#pragma mark - Private

// Stops the account switcher.
- (void)stopAccountMenuCoordinator {
  [_accountMenuCoordinator stop];
  _accountMenuCoordinator.delegate = nil;
  _accountMenuCoordinator = nil;
}

// Updates the feed visibility or content based on the supervision state
// of the account defined in `value`.
- (void)updateFeedWithIsSupervisedUser:(BOOL)value {
  // This may be called asynchronously after the NTP has
  // been stopped and the object has been stopped. Ignore
  // the invocation.
  PrefService* prefService = self.prefService;
  if (!prefService) {
    return;
  }

  prefService->SetBoolean(prefs::kNTPContentSuggestionsForSupervisedUserEnabled,
                          !value);
}

- (void)updateStartForVisibilityChange:(BOOL)visible {
  if (visible && NewTabPageTabHelper::FromWebState(self.webState)
                     ->ShouldShowStartSurface()) {
    DiscoverFeedServiceFactory::GetForBrowserState(
        self.browser->GetBrowserState())
        ->SetIsShownOnStartSurface(true);
  }
  if (!visible && NewTabPageTabHelper::FromWebState(self.webState)
                      ->ShouldShowStartSurface()) {
    // This means the NTP going away was showing Start. Reset configuration
    // since it should not show Start after disappearing.
    NewTabPageTabHelper::FromWebState(self.webState)
        ->SetShowStartSurface(false);
  }
}

// Updates the NTP to take into account a change in module visibility
- (void)handleChangeInModules {
  DCHECK(self.NTPViewController);

  [self.NTPViewController resetViewHierarchy];

  if (self.feedViewController) {
    self.discoverFeedService->RemoveFeedViewController(self.feedViewController);
  }

  [self.feedTopSectionCoordinator stop];

  self.NTPViewController.feedWrapperViewController = nil;
  self.NTPViewController.feedTopSectionViewController = nil;
  self.feedWrapperViewController = nil;
  self.feedViewController = nil;
  self.feedTopSectionCoordinator = nil;

  // Fetches feed header and conditionally fetches feed. Feed can only be
  // visible if feed header is visible.
  if (self.NTPMediator.feedHeaderVisible) {
    [self configureFeedAndHeader];
  } else {
    self.NTPViewController.feedHeaderViewController = nil;
    self.feedHeaderViewController = nil;
  }

  if ([self isFeedVisible]) {
    self.NTPViewController.feedTopSectionViewController =
        self.feedTopSectionCoordinator.viewController;
  }

  self.NTPViewController.feedVisible = [self isFeedVisible];

  self.feedWrapperViewController = [self.componentFactory
      feedWrapperViewControllerWithDelegate:self
                         feedViewController:self.feedViewController];

  self.NTPViewController.feedWrapperViewController =
      self.feedWrapperViewController;

  [self.NTPViewController layoutContentInParentCollectionView];

  [self updateFeedLayout];
}

// Returns `YES` if the feed is currently visible on the NTP.
- (BOOL)isFeedVisible {
  return [self shouldFeedBeVisible] && self.feedViewController;
}

// Creates, configures and returns a feed view controller configuration.
- (DiscoverFeedViewControllerConfiguration*)feedViewControllerConfiguration {
  DiscoverFeedViewControllerConfiguration* viewControllerConfig =
      [[DiscoverFeedViewControllerConfiguration alloc] init];
  viewControllerConfig.browser = self.browser;
  viewControllerConfig.scrollDelegate = self.NTPViewController;
  viewControllerConfig.previewDelegate = self;
  viewControllerConfig.manageDelegate = self;
  viewControllerConfig.signInPromoDelegate = self;

  return viewControllerConfig;
}

// Updates the visibility of the content suggestions on the NTP if the account
// is subject to parental controls.
// TODO(crbug.com/346756363): Remove this method as we deprecate getting
// supervision status from SystemIdentityManager.
- (void)updateFeedVisibilityForSupervision {
  if (!base::FeatureList::IsEnabled(
          supervised_user::
              kReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS)) {
    DCHECK(self.prefService);
    DCHECK(self.authService);

    id<SystemIdentity> identity =
        self.authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
    if (!identity) {
      [self updateFeedWithIsSupervisedUser:NO];
      return;
    }

    using CapabilityResult = SystemIdentityCapabilityResult;

    __weak NewTabPageCoordinator* weakSelf = self;
    GetApplicationContext()
        ->GetSystemIdentityManager()
        ->IsSubjectToParentalControls(
            identity, base::BindOnce(^(CapabilityResult result) {
              const bool isSupervisedUser = result == CapabilityResult::kTrue;
              [weakSelf updateFeedWithIsSupervisedUser:isSupervisedUser];
            }));
  }
}

// Toggles feed visibility between hidden or expanded using the feed header
// menu. A hidden feed will continue to show the header, with a modified label.
// TODO(crbug.com/1304382): Modify this comment when Web Channels is launched.
- (void)setFeedVisibleFromHeader:(BOOL)visible {
  [self.feedExpandedPref setValue:visible];
  [self.feedMetricsRecorder recordDiscoverFeedVisibilityChanged:visible];
  [self updateModuleVisibility];
}

// Configures and returns the feed top section coordinator.
- (FeedTopSectionCoordinator*)createFeedTopSectionCoordinator {
  DCHECK(self.NTPViewController);
  FeedTopSectionCoordinator* feedTopSectionCoordinator =
      [[FeedTopSectionCoordinator alloc]
          initWithBaseViewController:self.NTPViewController
                             browser:self.browser];
  feedTopSectionCoordinator.NTPDelegate = self;
  [feedTopSectionCoordinator start];
  return feedTopSectionCoordinator;
}

// Handles the feed management button being tapped.
- (void)handleFeedManageTapped {
  [self.feedMetricsRecorder recordHeaderMenuManageTapped];
  [self.feedManagementCoordinator stop];
  self.feedManagementCoordinator = nil;

  self.feedManagementCoordinator = [[FeedManagementCoordinator alloc]
      initWithBaseViewController:self.NTPViewController
                         browser:self.browser];
  self.feedManagementCoordinator.navigationDelegate = self.NTPMediator;
  self.feedManagementCoordinator.feedMetricsRecorder = self.feedMetricsRecorder;
  [self.feedManagementCoordinator start];
}

// Private setter for the `webState` property.
- (void)setWebState:(web::WebState*)webState {
  if (_webState == webState) {
    return;
  }

  _webState = webState;
  self.contentSuggestionsCoordinator.webState = _webState;
  [self.logoVendor setWebState:_webState];
}

// Called when the NTP changes visibility, either when the user navigates to
// or away from the NTP, or when the active WebState changes.
- (void)updateNTPIsVisible:(BOOL)visible {
  if (visible == self.visible) {
    return;
  }

  CHECK(self.webState);

  self.visible = visible;
  self.NTPViewController.NTPVisible = visible;

  if (!self.browser->GetBrowserState()->IsOffTheRecord()) {
    if (visible) {
      self.didAppearTime = base::TimeTicks::Now();

      if (IsHomeCustomizationEnabled()) {
        [self.NTPMetricsRecorder
            recordCustomizationState:[self currentCustomizationState]];

        PrefService* prefService = self.prefService;
        BOOL safetyCheckEnabled = prefService->GetBoolean(
            prefs::kHomeCustomizationMagicStackSafetyCheckEnabled);
        BOOL setUpListEnabled = prefService->GetBoolean(
            prefs::kHomeCustomizationMagicStackSetUpListEnabled);
        BOOL tabResumptionEnabled = prefService->GetBoolean(
            prefs::kHomeCustomizationMagicStackTabResumptionEnabled);
        BOOL parcelTrackingEnabled = prefService->GetBoolean(
            prefs::kHomeCustomizationMagicStackParcelTrackingEnabled);
        [self.NTPMetricsRecorder
            recordMagicStackCustomizationStateWithSetUpList:setUpListEnabled
                                                safetyCheck:safetyCheckEnabled

                                              tabResumption:tabResumptionEnabled
                                             parcelTracking:
                                                 parcelTrackingEnabled];
      }

      // TODO(crbug.com/350990359): Deprecate IOS.NTP.Impression when Home
      // Customization launches.
      if (self.NTPMediator.feedHeaderVisible) {
        if ([self.feedExpandedPref value] || IsHomeCustomizationEnabled()) {
          [self.NTPMetricsRecorder
              recordHomeImpression:IOSNTPImpressionType::kFeedVisible
                    isStartSurface:[self isStartSurface]];
        } else {
          [self.NTPMetricsRecorder
              recordHomeImpression:IOSNTPImpressionType::kFeedCollapsed
                    isStartSurface:[self isStartSurface]];
        }
      } else {
        [self.NTPMetricsRecorder
            recordHomeImpression:IOSNTPImpressionType::kFeedDisabled
                  isStartSurface:[self isStartSurface]];
      }
    } else {
      if (!self.didAppearTime.is_null()) {
        [self.NTPMetricsRecorder
            recordTimeSpentInHome:(base::TimeTicks::Now() - self.didAppearTime)
                   isStartSurface:[self isStartSurface]];
        self.didAppearTime = base::TimeTicks();
      }
    }
    // Check if feed is visible before reporting NTP visibility as the feed
    // needs to be visible in order to use for metrics.
    // TODO(crbug.com/40871863) Move isFeedVisible check to the metrics recorder
    if ([self isFeedVisible]) {
      [self.feedMetricsRecorder recordNTPDidChangeVisibility:visible];
    }
  }
}

// Returns whether the user policies allow them to sync.
- (BOOL)isSyncAllowedByPolicy {
  return !SyncServiceFactory::GetForBrowserState(
              self.browser->GetBrowserState())
              ->HasDisableReason(
                  syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
}

// Shows sign-in disabled snackbar message.
- (void)showSignInDisableMessage {
  id<SnackbarCommands> handler =
      static_cast<id<SnackbarCommands>>(self.browser->GetCommandDispatcher());
  MDCSnackbarMessage* message = CreateSnackbarMessage(l10n_util::GetNSString(
      IDS_IOS_NTP_FEED_SIGNIN_PROMO_DISABLE_SNACKBAR_MESSAGE));

  [handler showSnackbarMessage:message];
}

// Saves the state of the NTP associated with `self.webState`.
- (void)saveNTPState {
  [self.NTPMediator saveNTPStateForWebState:self.webState];
}

// Restores the saved state of the NTP associated with `self.webState` if
// necessary.
- (void)restoreNTPState {
  [self.NTPMediator restoreNTPStateForWebState:self.webState];
}

// Opens the Home customization menu at a specific `page`.
- (void)openCustomizationMenuAtPage:(CustomizationMenuPage)page
                           animated:(BOOL)animated {
  _customizationCoordinator = [[HomeCustomizationCoordinator alloc]
      initWithBaseViewController:self.NTPViewController
                         browser:self.browser];
  _customizationCoordinator.delegate = self;
  [_customizationCoordinator start];
  [_customizationCoordinator presentCustomizationMenuPage:page];
  feature_engagement::TrackerFactory::GetForBrowserState(
      self.browser->GetBrowserState())
      ->NotifyEvent(feature_engagement::events::kHomeCustomizationMenuUsed);
}

// Returns the current customization state represnting the visibility of NTP
// components.
- (IOSNTPImpressionCustomizationState)currentCustomizationState {
  CHECK(IsHomeCustomizationEnabled());
  PrefService* prefService = self.prefService;
  BOOL MVTEnabled =
      prefService->GetBoolean(prefs::kHomeCustomizationMostVisitedEnabled);
  BOOL magicStackEnabled =
      prefService->GetBoolean(prefs::kHomeCustomizationMagicStackEnabled);
  BOOL feedEnabled = prefService->GetBoolean(prefs::kArticlesForYouEnabled);

  // All components enabled/disabled.
  if (MVTEnabled && magicStackEnabled && feedEnabled) {
    return IOSNTPImpressionCustomizationState::kAllEnabled;
  }
  if (!MVTEnabled && !magicStackEnabled && !feedEnabled) {
    return IOSNTPImpressionCustomizationState::kAllDisabled;
  }

  // 2 components enabled.
  if (MVTEnabled && magicStackEnabled && !feedEnabled) {
    return IOSNTPImpressionCustomizationState::kMVTAndMagicStackEnabled;
  }
  if (MVTEnabled && !magicStackEnabled && feedEnabled) {
    return IOSNTPImpressionCustomizationState::kMVTAndFeedEnabled;
  }
  if (!MVTEnabled && magicStackEnabled && feedEnabled) {
    return IOSNTPImpressionCustomizationState::kMagicStackAndFeedEnabled;
  }

  // 1 component enabled.
  if (MVTEnabled && !magicStackEnabled && !feedEnabled) {
    return IOSNTPImpressionCustomizationState::kMVTEnabled;
  }
  if (!MVTEnabled && magicStackEnabled && !feedEnabled) {
    return IOSNTPImpressionCustomizationState::kMagicStackEnabled;
  }
  if (!MVTEnabled && !magicStackEnabled && feedEnabled) {
    return IOSNTPImpressionCustomizationState::kFeedEnabled;
  }

  NOTREACHED_NORETURN();
}

#pragma mark - AccountMenuCoordinatorDelegate

- (void)acountMenuCoordinatorShouldStop:(AccountMenuCoordinator*)coordinator {
  CHECK_EQ(coordinator, _accountMenuCoordinator);
  [self stopAccountMenuCoordinator];
}

#pragma mark - HomeCustomizationDelegate

- (void)dismissCustomizationMenu {
  [self.NTPViewController dismissViewControllerAnimated:YES completion:nil];
  [_customizationCoordinator stop];
  _customizationCoordinator = nil;
}

@end