chromium/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm

// Copyright 2021 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/popup_menu/overflow_menu/overflow_menu_mediator.h"

#import "base/ios/ios_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/bookmarks/browser/bookmark_model.h"
#import "components/bookmarks/common/bookmark_pref_names.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper_observer_bridge.h"
#import "components/password_manager/core/browser/manage_passwords_referrer.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/prefs/pref_service.h"
#import "components/profile_metrics/browser_profile_type.h"
#import "components/reading_list/core/reading_list_model.h"
#import "components/reading_list/ios/reading_list_model_bridge_observer.h"
#import "components/supervised_user/core/common/features.h"
#import "components/supervised_user/core/common/supervised_user_constants.h"
#import "components/sync/service/sync_service.h"
#import "components/translate/core/browser/translate_manager.h"
#import "components/translate/core/browser/translate_prefs.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_model_bridge_observer.h"
#import "ios/chrome/browser/commerce/model/push_notification/push_notification_feature.h"
#import "ios/chrome/browser/default_browser/model/default_browser_interest_signals.h"
#import "ios/chrome/browser/find_in_page/model/abstract_find_tab_helper.h"
#import "ios/chrome/browser/follow/model/follow_browser_agent.h"
#import "ios/chrome/browser/follow/model/follow_menu_updater.h"
#import "ios/chrome/browser/follow/model/follow_tab_helper.h"
#import "ios/chrome/browser/follow/model/follow_util.h"
#import "ios/chrome/browser/intents/intents_donation_helper.h"
#import "ios/chrome/browser/iph_for_new_chrome_user/model/tab_based_iph_browser_agent.h"
#import "ios/chrome/browser/lens_overlay/coordinator/lens_overlay_availability.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
#import "ios/chrome/browser/overlays/model/public/overlay_presenter.h"
#import "ios/chrome/browser/overlays/model/public/overlay_presenter_observer_bridge.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request.h"
#import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/policy/ui_bundled/user_policy_util.h"
#import "ios/chrome/browser/reading_list/model/offline_url_utils.h"
#import "ios/chrome/browser/settings/model/sync/utils/identity_error_util.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/shared/public/commands/activity_service_commands.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/find_in_page_commands.h"
#import "ios/chrome/browser/shared/public/commands/help_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_overlay_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/overflow_menu_customization_commands.h"
#import "ios/chrome/browser/shared/public/commands/page_info_commands.h"
#import "ios/chrome/browser/shared/public/commands/popup_menu_commands.h"
#import "ios/chrome/browser/shared/public/commands/price_notifications_commands.h"
#import "ios/chrome/browser/shared/public/commands/quick_delete_commands.h"
#import "ios/chrome/browser/shared/public/commands/reading_list_add_command.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/text_zoom_commands.h"
#import "ios/chrome/browser/shared/public/commands/whats_new_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_capabilities.h"
#import "ios/chrome/browser/translate/model/chrome_ios_translate_client.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history/constants.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history/destination_usage_history.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_constants.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_metrics.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_orderer.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_swift.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_utils.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/features.h"
#import "ios/chrome/browser/ui/sharing/sharing_params.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h"
#import "ios/chrome/browser/ui/whats_new/whats_new_util.h"
#import "ios/chrome/browser/web/model/font_size/font_size_tab_helper.h"
#import "ios/chrome/browser/web/model/web_navigation_browser_agent.h"
#import "ios/chrome/browser/window_activities/model/window_activity_helpers.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_api.h"
#import "ios/web/common/user_agent.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_observer_bridge.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

using base::RecordAction;
using base::UmaHistogramEnumeration;
using base::UserMetricsAction;
using experimental_flags::IsSpotlightDebuggingEnabled;

namespace {

// Approximate number of visible page actions by default.
const unsigned int kDefaultVisiblePageActionCount = 3u;

// Struct used to count and store the number of active WhatsNew badges,
// as the FET does not support showing multiple badges for the same FET feature
// at the same time.
struct WhatsNewActiveMenusData : public base::SupportsUserData::Data {
  // The number of active menus.
  int activeMenus = 0;

  // Key to use for this type in SupportsUserData
  static constexpr char key[] = "WhatsNewActiveMenusData";
};

typedef void (^Handler)(void);

OverflowMenuFooter* CreateOverflowMenuManagedFooter(
    int nameID,
    int linkID,
    NSString* accessibilityIdentifier,
    NSString* imageName,
    Handler handler) {
  NSString* name = l10n_util::GetNSString(nameID);
  NSString* link = l10n_util::GetNSString(linkID);
  return [[OverflowMenuFooter alloc] initWithName:name
                                             link:link
                                            image:[UIImage imageNamed:imageName]
                          accessibilityIdentifier:accessibilityIdentifier
                                          handler:handler];
}

}  // namespace

@interface OverflowMenuMediator () <BookmarkModelBridgeObserver,
                                    CRWWebStateObserver,
                                    FollowMenuUpdater,
                                    IOSLanguageDetectionTabHelperObserving,
                                    OverflowMenuDestinationProvider,
                                    OverlayPresenterObserving,
                                    PrefObserverDelegate,
                                    ReadingListModelBridgeObserver,
                                    WebStateListObserving> {
  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;

  // Observer for the content area overlay events
  std::unique_ptr<OverlayPresenterObserver> _overlayPresenterObserver;

  // Bridge to register for bookmark changes.
  std::unique_ptr<BookmarkModelBridge> _bookmarkModelBridge;

  // Bridge to register for reading list model changes.
  std::unique_ptr<ReadingListModelBridge> _readingListModelBridge;

  // Bridge to get notified of the language detection event.
  std::unique_ptr<language::IOSLanguageDetectionTabHelperObserverBridge>
      _iOSLanguageDetectionTabHelperObserverBridge;

  // Pref observer to track changes to prefs.
  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
  // Registrar for pref changes notifications.
  std::unique_ptr<PrefChangeRegistrar> _prefChangeRegistrar;
}

// The current web state.
@property(nonatomic, assign) web::WebState* webState;

// Whether or not the menu has been dismissed. Sometimes, the menu takes some
// time to dismiss after requesting dismissal, leading to errors were menu
// options are selected twice or at the wrong times (see crbug.com/1500367)
@property(nonatomic, assign) BOOL menuHasBeenDismissed;

// Whether an overlay is currently presented over the web content area.
@property(nonatomic, assign) BOOL webContentAreaShowingOverlay;

// Whether the web content is currently being blocked.
@property(nonatomic, assign) BOOL contentBlocked;

@property(nonatomic, strong) OverflowMenuDestination* bookmarksDestination;
@property(nonatomic, strong) OverflowMenuDestination* downloadsDestination;
@property(nonatomic, strong) OverflowMenuDestination* historyDestination;
@property(nonatomic, strong) OverflowMenuDestination* passwordsDestination;
@property(nonatomic, strong)
    OverflowMenuDestination* priceNotificationsDestination;
@property(nonatomic, strong) OverflowMenuDestination* readingListDestination;
@property(nonatomic, strong) OverflowMenuDestination* recentTabsDestination;
@property(nonatomic, strong) OverflowMenuDestination* settingsDestination;
@property(nonatomic, strong) OverflowMenuDestination* siteInfoDestination;
@property(nonatomic, strong) OverflowMenuDestination* whatsNewDestination;
@property(nonatomic, strong)
    OverflowMenuDestination* spotlightDebuggerDestination;

@property(nonatomic, strong) OverflowMenuActionGroup* appActionsGroup;
@property(nonatomic, strong) OverflowMenuActionGroup* pageActionsGroup;
@property(nonatomic, strong) OverflowMenuActionGroup* helpActionsGroup;
@property(nonatomic, strong) OverflowMenuActionGroup* editActionsGroup;

@property(nonatomic, strong) OverflowMenuAction* reloadAction;
@property(nonatomic, strong) OverflowMenuAction* stopLoadAction;
@property(nonatomic, strong) OverflowMenuAction* openTabAction;
@property(nonatomic, strong) OverflowMenuAction* openIncognitoTabAction;
@property(nonatomic, strong) OverflowMenuAction* openNewWindowAction;

@property(nonatomic, strong) OverflowMenuAction* clearBrowsingDataAction;
@property(nonatomic, strong) OverflowMenuAction* followAction;
@property(nonatomic, strong) OverflowMenuAction* addBookmarkAction;
@property(nonatomic, strong) OverflowMenuAction* editBookmarkAction;
@property(nonatomic, strong) OverflowMenuAction* readLaterAction;
@property(nonatomic, strong) OverflowMenuAction* translateAction;
@property(nonatomic, strong) OverflowMenuAction* requestDesktopAction;
@property(nonatomic, strong) OverflowMenuAction* requestMobileAction;
@property(nonatomic, strong) OverflowMenuAction* findInPageAction;
@property(nonatomic, strong) OverflowMenuAction* textZoomAction;

@property(nonatomic, strong) OverflowMenuAction* reportIssueAction;
@property(nonatomic, strong) OverflowMenuAction* helpAction;
@property(nonatomic, strong) OverflowMenuAction* shareChromeAction;

@property(nonatomic, strong) OverflowMenuAction* editActionsAction;
@property(nonatomic, strong) OverflowMenuAction* lensOverlayAction;

@end

@implementation OverflowMenuMediator

- (instancetype)init {
  self = [super init];
  if (self) {
    _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
    _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
    _overlayPresenterObserver =
        std::make_unique<OverlayPresenterObserverBridge>(self);
  }
  return self;
}

- (void)disconnect {
  // Remove the model link so the other deallocations don't update the model
  // and thus the UI as the UI is dismissing.
  self.model = nil;
  self.menuOrderer = nil;

  self.webContentAreaOverlayPresenter = nullptr;

  if (_engagementTracker) {
    if (self.readingListDestination.badge != BadgeTypeNone) {
      _engagementTracker->Dismissed(
          feature_engagement::kIPHBadgedReadingListFeature);
    }

    if (self.whatsNewDestination.badge != BadgeTypeNone) {
      // Check if this is the last active menu with WhatsNew badge and dismiss
      // the FET if so.
      WhatsNewActiveMenusData* data = static_cast<WhatsNewActiveMenusData*>(
          _engagementTracker->GetUserData(WhatsNewActiveMenusData::key));
      if (data) {
        data->activeMenus--;
        if (data->activeMenus <= 0) {
          _engagementTracker->Dismissed(
              feature_engagement::kIPHWhatsNewUpdatedFeature);
          _engagementTracker->RemoveUserData(WhatsNewActiveMenusData::key);
        }
      } else {
        _engagementTracker->Dismissed(
            feature_engagement::kIPHWhatsNewUpdatedFeature);
      }
    }

    _engagementTracker = nullptr;
  }

  self.followBrowserAgent = nullptr;

  self.webState = nullptr;
  self.webStateList = nullptr;

  self.bookmarkModel = nullptr;
  self.readingListModel = nullptr;
  self.browserStatePrefs = nullptr;
  self.localStatePrefs = nullptr;

  self.syncService = nullptr;
}

#pragma mark - Property getters/setters

- (void)setModel:(OverflowMenuModel*)model {
  _model = model;
  if (_model) {
    [self initializeModel];
    [self updateModelItemsState];
    // Any state that is required for re-ordering the menu overall (e.g. badges)
    // must be ready by this point. After this, the only order-based changes
    // that will be observed are those that show/hide whole destinations.
    [_menuOrderer reorderDestinationsForInitialMenu];
    [self updateModel];
  }
}

- (void)setMenuOrderer:(OverflowMenuOrderer*)menuOrderer {
  if (self.menuOrderer.destinationProvider == self) {
    self.menuOrderer.destinationProvider = nil;
  }
  if (self.menuOrderer.actionProvider == self) {
    self.menuOrderer.actionProvider = nil;
  }
  _menuOrderer = menuOrderer;
  self.menuOrderer.destinationProvider = self;
  self.menuOrderer.actionProvider = self;

  [self updateModel];
}

- (void)setIsIncognito:(BOOL)isIncognito {
  _isIncognito = isIncognito;
  [self updateModel];
}

- (void)setWebState:(web::WebState*)webState {
  if (_webState) {
    _webState->RemoveObserver(_webStateObserver.get());

    if (self.followAction) {
      FollowTabHelper* followTabHelper =
          FollowTabHelper::FromWebState(_webState);
      if (followTabHelper) {
        followTabHelper->RemoveFollowMenuUpdater();
      }
    }
  }

  _webState = webState;

  if (_webState) {
    [self updateModel];
    _webState->AddObserver(_webStateObserver.get());

    // Observe the language::IOSLanguageDetectionTabHelper for `_webState`.
    _iOSLanguageDetectionTabHelperObserverBridge =
        std::make_unique<language::IOSLanguageDetectionTabHelperObserverBridge>(
            language::IOSLanguageDetectionTabHelper::FromWebState(_webState),
            self);

    FollowTabHelper* followTabHelper = FollowTabHelper::FromWebState(_webState);
    if (followTabHelper) {
      followTabHelper->SetFollowMenuUpdater(self);
    }
  }
}

- (void)setWebStateList:(WebStateList*)webStateList {
  if (_webStateList) {
    _webStateList->RemoveObserver(_webStateListObserver.get());

    _iOSLanguageDetectionTabHelperObserverBridge.reset();
  }

  _webStateList = webStateList;

  self.webState = (_webStateList) ? _webStateList->GetActiveWebState() : nil;

  if (_webStateList) {
    _webStateList->AddObserver(_webStateListObserver.get());
  }
}

- (void)setBookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel {
  _bookmarkModelBridge.reset();

  _bookmarkModel = bookmarkModel;

  if (bookmarkModel) {
    _bookmarkModelBridge =
        std::make_unique<BookmarkModelBridge>(self, bookmarkModel);
  }

  [self updateModel];
}

- (void)setReadingListModel:(ReadingListModel*)readingListModel {
  _readingListModelBridge.reset();

  _readingListModel = readingListModel;

  if (readingListModel) {
    _readingListModelBridge =
        std::make_unique<ReadingListModelBridge>(self, readingListModel);
  }

  [self updateModel];
}

- (void)setBrowserStatePrefs:(PrefService*)browserStatePrefs {
  _prefObserverBridge.reset();
  _prefChangeRegistrar.reset();

  _browserStatePrefs = browserStatePrefs;

  if (_browserStatePrefs) {
    _prefChangeRegistrar = std::make_unique<PrefChangeRegistrar>();
    _prefChangeRegistrar->Init(browserStatePrefs);
    _prefObserverBridge.reset(new PrefObserverBridge(self));
    _prefObserverBridge->ObserveChangesForPreference(
        bookmarks::prefs::kEditBookmarksEnabled, _prefChangeRegistrar.get());
  }
}

- (void)setEngagementTracker:(feature_engagement::Tracker*)engagementTracker {
  _engagementTracker = engagementTracker;

  if (!engagementTracker) {
    return;
  }

  [self updateModel];
}

- (void)setWebContentAreaOverlayPresenter:
    (OverlayPresenter*)webContentAreaOverlayPresenter {
  if (_webContentAreaOverlayPresenter) {
    _webContentAreaOverlayPresenter->RemoveObserver(
        _overlayPresenterObserver.get());
    self.webContentAreaShowingOverlay = NO;
  }

  _webContentAreaOverlayPresenter = webContentAreaOverlayPresenter;

  if (_webContentAreaOverlayPresenter) {
    _webContentAreaOverlayPresenter->AddObserver(
        _overlayPresenterObserver.get());
    self.webContentAreaShowingOverlay =
        _webContentAreaOverlayPresenter->IsShowingOverlayUI();
  }
}

- (void)setWebContentAreaShowingOverlay:(BOOL)webContentAreaShowingOverlay {
  if (_webContentAreaShowingOverlay == webContentAreaShowingOverlay)
    return;
  _webContentAreaShowingOverlay = webContentAreaShowingOverlay;
  [self updateModel];
}

- (void)setSyncService:(syncer::SyncService*)syncService {
  _syncService = syncService;

  if (!syncService) {
    return;
  }

  [self updateModel];
}

#pragma mark - Model Creation

- (void)initializeModel {
  __weak __typeof(self) weakSelf = self;

  // Bookmarks destination.
  self.bookmarksDestination = [self newBookmarksDestination];

  // Downloads destination.
  self.downloadsDestination = [self newDownloadsDestination];

  // History destination.
  self.historyDestination = [self newHistoryDestination];

  // Passwords destination.
  self.passwordsDestination = [self newPasswordsDestination];

  // Price Tracking destination.
  self.priceNotificationsDestination = [self newPriceNotificationsDestination];

  // Reading List destination.
  self.readingListDestination = [self newReadingListDestination];

  // Recent Tabs destination.
  self.recentTabsDestination = [self newRecentTabsDestination];

  // Settings destination.
  self.settingsDestination = [self newSettingsDestination];

  self.spotlightDebuggerDestination = [self newSpotlightDebuggerDestination];

  // WhatsNew destination.
  self.whatsNewDestination = [self newWhatsNewDestination];

  // Site Info destination.
  self.siteInfoDestination = [self newSiteInfoDestination];

  [self logTranslateAvailability];

  self.reloadAction =
      [self createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_RELOAD
                                    actionType:overflow_menu::ActionType::Reload
                                    symbolName:kArrowClockWiseSymbol
                                  systemSymbol:NO
                              monochromeSymbol:NO
                               accessibilityID:kToolsMenuReload
                                  hideItemText:nil
                                       handler:^{
                                         [weakSelf reload];
                                       }];

  self.stopLoadAction =
      [self createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_STOP
                                    actionType:overflow_menu::ActionType::Reload
                                    symbolName:kXMarkSymbol
                                  systemSymbol:YES
                              monochromeSymbol:NO
                               accessibilityID:kToolsMenuStop
                                  hideItemText:nil
                                       handler:^{
                                         [weakSelf stopLoading];
                                       }];

  self.openTabAction =
      [self createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_NEW_TAB
                                    actionType:overflow_menu::ActionType::NewTab
                                    symbolName:kPlusInCircleSymbol
                                  systemSymbol:YES
                              monochromeSymbol:NO
                               accessibilityID:kToolsMenuNewTabId
                                  hideItemText:nil
                                       handler:^{
                                         [weakSelf openTab];
                                       }];

  self.openIncognitoTabAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB
                              actionType:overflow_menu::ActionType::
                                             NewIncognitoTab
                              symbolName:kIncognitoSymbol
                            systemSymbol:NO
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuNewIncognitoTabId
                            hideItemText:nil
                                 handler:^{
                                   [weakSelf openIncognitoTab];
                                 }];

  self.openNewWindowAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_NEW_WINDOW
                              actionType:overflow_menu::ActionType::NewWindow
                              symbolName:kNewWindowActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuNewWindowId
                            hideItemText:nil
                                 handler:^{
                                   [weakSelf openNewWindow];
                                 }];

  self.clearBrowsingDataAction = [self newClearBrowsingDataAction];

  if (GetFollowActionState(self.webState) != FollowActionStateHidden) {
    OverflowMenuAction* action = [self newFollowAction];

    action.enabled = NO;
    self.followAction = action;
  }

  self.addBookmarkAction = [self newAddBookmarkAction];

  NSString* editBookmarkHideItemText =
      l10n_util::GetNSString(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_EDIT_BOOKMARK);
  self.editBookmarkAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_EDIT_BOOKMARK
                              actionType:overflow_menu::ActionType::Bookmark
                              symbolName:kEditActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuEditBookmark
                            hideItemText:editBookmarkHideItemText
                                 handler:^{
                                   [weakSelf addOrEditBookmark];
                                 }];

  self.readLaterAction = [self newReadLaterAction];

  self.translateAction = [self newTranslateAction];

  self.requestDesktopAction = [self newRequestDesktopAction];

  NSString* requestMobileHideItemText =
      l10n_util::GetNSString(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_MOBILE_SITE);
  self.requestMobileAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_REQUEST_MOBILE_SITE
                              actionType:overflow_menu::ActionType::DesktopSite
                              symbolName:kIPhoneSymbol
                            systemSymbol:YES
                        monochromeSymbol:YES
                         accessibilityID:kToolsMenuRequestMobileId
                            hideItemText:requestMobileHideItemText
                                 handler:^{
                                   [weakSelf requestMobileSite];
                                 }];

  self.findInPageAction = [self newFindInPageAction];

  self.textZoomAction = [self newTextZoomAction];

  self.reportIssueAction =
      [self createOverflowMenuActionWithNameID:IDS_IOS_OPTIONS_REPORT_AN_ISSUE
                                    actionType:overflow_menu::ActionType::
                                                   ReportAnIssue
                                    symbolName:kWarningSymbol
                                  systemSymbol:YES
                              monochromeSymbol:NO
                               accessibilityID:kToolsMenuReportAnIssueId
                                  hideItemText:nil
                                       handler:^{
                                         [weakSelf reportAnIssue];
                                       }];

  self.helpAction =
      [self createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_HELP_MOBILE
                                    actionType:overflow_menu::ActionType::Help
                                    symbolName:kHelpSymbol
                                  systemSymbol:YES
                              monochromeSymbol:NO
                               accessibilityID:kToolsMenuHelpId
                                  hideItemText:nil
                                       handler:^{
                                         [weakSelf openHelp];
                                       }];

  self.shareChromeAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_OVERFLOW_MENU_SHARE_CHROME
                              actionType:overflow_menu::ActionType::ShareChrome
                              symbolName:kShareSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuShareChromeId
                            hideItemText:nil
                                 handler:^{
                                   [weakSelf shareChromeApp];
                                 }];

  self.editActionsAction = [self
      createOverflowMenuActionWithNameID:IDS_IOS_OVERFLOW_MENU_EDIT_ACTIONS
                              actionType:overflow_menu::ActionType::EditActions
                              symbolName:nil
                            systemSymbol:NO
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuEditActionsId
                            hideItemText:nil
                                 handler:^{
                                   [weakSelf beginCustomization];
                                 }];
  if (IsLensOverlayAvailable()) {
    self.lensOverlayAction = [self openLensOverlayAction];
  }
  self.editActionsAction.automaticallyUnhighlight = NO;
  self.editActionsAction.useButtonStyling = YES;

  // The app actions vary based on page state, so they are set in
  // `-updateModel`.
  self.appActionsGroup =
      [[OverflowMenuActionGroup alloc] initWithGroupName:@"app_actions"
                                                 actions:@[]
                                                  footer:nil];

  // The page actions vary based on page state, so they are set in
  // `-updateModel`.
  self.pageActionsGroup =
      [[OverflowMenuActionGroup alloc] initWithGroupName:@"page_actions"
                                                 actions:@[]
                                                  footer:nil];
  self.menuOrderer.pageActionsGroup = self.pageActionsGroup;

  // Footer and actions vary based on state, so they're set in -updateModel.
  self.helpActionsGroup =
      [[OverflowMenuActionGroup alloc] initWithGroupName:@"help_actions"
                                                 actions:@[]
                                                  footer:nil];

  self.editActionsGroup = [[OverflowMenuActionGroup alloc]
      initWithGroupName:@"edit_actions"
                actions:@[ self.editActionsAction ]
                 footer:nil];

  NSMutableArray* actionGroups = [[NSMutableArray alloc] init];
  [actionGroups
      addObjectsFromArray:@[ self.appActionsGroup, self.pageActionsGroup ]];
  if (IsOverflowMenuCustomizationEnabled()) {
    [actionGroups addObject:self.editActionsGroup];
  }
  [actionGroups addObject:self.helpActionsGroup];

  self.model.actionGroups = actionGroups;
}

- (OverflowMenuAction*)newFollowAction {
  return [self
      createOverflowMenuActionWithName:l10n_util::GetNSString(
                                           IDS_IOS_TOOLS_MENU_CUSTOMIZE_FOLLOW)
                            actionType:overflow_menu::ActionType::Follow
                            symbolName:kPlusSymbol
                          systemSymbol:YES
                      monochromeSymbol:NO
                       accessibilityID:kToolsMenuFollow
                          hideItemText:
                              l10n_util::GetNSStringF(
                                  IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_FOLLOW, u"")
                               handler:^{
                               }];
}

- (OverflowMenuAction*)newAddBookmarkAction {
  __weak __typeof(self) weakSelf = self;
  NSString* hideItemText = l10n_util::GetNSString(
      IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_ADD_TO_BOOKMARKS);
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_ADD_TO_BOOKMARKS
                              actionType:overflow_menu::ActionType::Bookmark
                              symbolName:kAddBookmarkActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuAddToBookmarks
                            hideItemText:hideItemText
                                 handler:^{
                                   [weakSelf addOrEditBookmark];
                                 }];
}

- (OverflowMenuAction*)openLensOverlayAction {
  __weak __typeof(self) weakSelf = self;
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_CONTENT_CONTEXT_OPENLENSOVERLAY
                              actionType:overflow_menu::ActionType::LensOverlay
                              symbolName:kCameraLensSymbol
                            systemSymbol:NO
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuOpenLensOverlay
                            hideItemText:nil
                                 handler:^{
                                   [weakSelf startLensOverlay];
                                 }];
}

- (OverflowMenuAction*)newReadLaterAction {
  __weak __typeof(self) weakSelf = self;
  NSString* hideItemText =
      l10n_util::GetNSString(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_READING_LIST);
  return [self
      createOverflowMenuActionWithNameID:
          IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST
                              actionType:overflow_menu::ActionType::ReadingList
                              symbolName:kReadLaterActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuReadLater
                            hideItemText:hideItemText
                                 handler:^{
                                   [weakSelf addToReadingList];
                                 }];
}

- (OverflowMenuAction*)newClearBrowsingDataAction {
  __weak __typeof(self) weakSelf = self;
  NSString* hideItemText = l10n_util::GetNSString(
      IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_CLEAR_BROWSING_DATA);
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_CLEAR_BROWSING_DATA
                              actionType:overflow_menu::ActionType::
                                             ClearBrowsingData
                              symbolName:kTrashSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuClearBrowsingData
                            hideItemText:hideItemText
                                 handler:^{
                                   [weakSelf openClearBrowsingData];
                                 }];
}

- (OverflowMenuAction*)newTranslateAction {
  __weak __typeof(self) weakSelf = self;
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_TRANSLATE
                              actionType:overflow_menu::ActionType::Translate
                              symbolName:kTranslateSymbol
                            systemSymbol:NO
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuTranslateId
                            hideItemText:
                                l10n_util::GetNSString(
                                    IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_TRANSLATE)
                                 handler:^{
                                   [weakSelf translatePage];
                                 }];
}

- (OverflowMenuAction*)newRequestDesktopAction {
  __weak __typeof(self) weakSelf = self;
  NSString* hideItemText =
      l10n_util::GetNSString(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_MOBILE_SITE);
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_REQUEST_DESKTOP_SITE
                              actionType:overflow_menu::ActionType::DesktopSite
                              symbolName:kDesktopSymbol
                            systemSymbol:YES
                        monochromeSymbol:YES
                         accessibilityID:kToolsMenuRequestDesktopId
                            hideItemText:hideItemText
                                 handler:^{
                                   [weakSelf requestDesktopSite];
                                 }];
}

- (OverflowMenuAction*)newFindInPageAction {
  __weak __typeof(self) weakSelf = self;
  NSString* hideItemText =
      l10n_util::GetNSString(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_FIND_IN_PAGE);
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_FIND_IN_PAGE
                              actionType:overflow_menu::ActionType::FindInPage
                              symbolName:kFindInPageActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuFindInPageId
                            hideItemText:hideItemText
                                 handler:^{
                                   [weakSelf openFindInPage];
                                 }];
}

- (OverflowMenuAction*)newTextZoomAction {
  __weak __typeof(self) weakSelf = self;
  return [self
      createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_TEXT_ZOOM
                              actionType:overflow_menu::ActionType::TextZoom
                              symbolName:kZoomTextActionSymbol
                            systemSymbol:YES
                        monochromeSymbol:NO
                         accessibilityID:kToolsMenuTextZoom
                            hideItemText:
                                l10n_util::GetNSString(
                                    IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_TEXT_ZOOM)
                                 handler:^{
                                   [weakSelf openTextZoom];
                                 }];
}

- (OverflowMenuDestination*)newBookmarksDestination {
  __weak __typeof(self) weakSelf = self;
  return
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_BOOKMARKS
                              destination:overflow_menu::Destination::Bookmarks
                               symbolName:kBookmarksSymbol
                             systemSymbol:YES
                          accessibilityID:kToolsMenuBookmarksId
                                  handler:^{
                                    [weakSelf openBookmarks];
                                  }];
}

- (OverflowMenuDestination*)newHistoryDestination {
  __weak __typeof(self) weakSelf = self;
  return [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_HISTORY
                                 destination:overflow_menu::Destination::History
                                  symbolName:kHistorySymbol
                                systemSymbol:YES
                             accessibilityID:kToolsMenuHistoryId
                                     handler:^{
                                       [weakSelf openHistory];
                                     }];
}

- (OverflowMenuDestination*)newReadingListDestination {
  __weak __typeof(self) weakSelf = self;
  return [self
      createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_READING_LIST
                        destination:overflow_menu::Destination::ReadingList
                         symbolName:kReadingListSymbol
                       systemSymbol:NO
                    accessibilityID:kToolsMenuReadingListId
                            handler:^{
                              [weakSelf openReadingList];
                            }];
}

- (OverflowMenuDestination*)newPasswordsDestination {
  __weak __typeof(self) weakSelf = self;
  return
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_PASSWORD_MANAGER
                              destination:overflow_menu::Destination::Passwords
                               symbolName:kPasswordSymbol
                             systemSymbol:NO
                          accessibilityID:kToolsMenuPasswordsId
                                  handler:^{
                                    [weakSelf openPasswords];
                                  }];
}

- (OverflowMenuDestination*)newDownloadsDestination {
  __weak __typeof(self) weakSelf = self;
  return
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_DOWNLOADS
                              destination:overflow_menu::Destination::Downloads
                               symbolName:kDownloadSymbol
                             systemSymbol:YES
                          accessibilityID:kToolsMenuDownloadsId
                                  handler:^{
                                    [weakSelf openDownloads];
                                  }];
}

- (OverflowMenuDestination*)newRecentTabsDestination {
  __weak __typeof(self) weakSelf = self;
  return
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_RECENT_TABS
                              destination:overflow_menu::Destination::RecentTabs
                               symbolName:kRecentTabsSymbol
                             systemSymbol:NO
                          accessibilityID:kToolsMenuOtherDevicesId
                                  handler:^{
                                    [weakSelf openRecentTabs];
                                  }];
}

- (OverflowMenuDestination*)newSiteInfoDestination {
  __weak __typeof(self) weakSelf = self;
  OverflowMenuDestination* destination =
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_SITE_INFORMATION
                              destination:overflow_menu::Destination::SiteInfo
                               symbolName:kTunerSymbol
                             systemSymbol:NO
                          accessibilityID:kToolsMenuSiteInformation
                                  handler:^{
                                    [weakSelf openSiteInformation];
                                  }];
  destination.canBeHidden = NO;
  return destination;
}

- (OverflowMenuDestination*)newSettingsDestination {
  __weak __typeof(self) weakSelf = self;
  OverflowMenuDestination* destination =
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_SETTINGS
                              destination:overflow_menu::Destination::Settings
                               symbolName:kSettingsSymbol
                             systemSymbol:YES
                          accessibilityID:kToolsMenuSettingsId
                                  handler:^{
                                    [weakSelf openSettings];
                                  }];
  destination.canBeHidden = NO;
  return destination;
}

- (OverflowMenuDestination*)newWhatsNewDestination {
  __weak __typeof(self) weakSelf = self;
  return
      [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_WHATS_NEW
                              destination:overflow_menu::Destination::WhatsNew
                               symbolName:kCheckmarkSealSymbol
                             systemSymbol:YES
                          accessibilityID:kToolsMenuWhatsNewId
                                  handler:^{
                                    [weakSelf openWhatsNew];
                                  }];
}

- (OverflowMenuDestination*)newSpotlightDebuggerDestination {
  __weak __typeof(self) weakSelf = self;
  return [self destinationForSpotlightDebugger:^{
    [weakSelf openSpotlightDebugger];
  }];
}

- (OverflowMenuDestination*)newPriceNotificationsDestination {
  __weak __typeof(self) weakSelf = self;
  return [self createOverflowMenuDestination:IDS_IOS_TOOLS_MENU_PRICE_TRACKING
                                 destination:overflow_menu::Destination::
                                                 PriceNotifications
                                  symbolName:kDownTrendSymbol
                                systemSymbol:NO
                             accessibilityID:kToolsMenuPriceNotifications
                                     handler:^{
                                       [weakSelf openPriceNotifications];
                                     }];
}

- (NSString*)hideItemTextForDestination:
    (overflow_menu::Destination)destination {
  switch (destination) {
    case overflow_menu::Destination::SiteInfo:
    case overflow_menu::Destination::Settings:
    case overflow_menu::Destination::SpotlightDebugger:
      // These items are unhideable.
      return nil;
    case overflow_menu::Destination::Bookmarks:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_BOOKMARKS);
    case overflow_menu::Destination::History:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_HISTORY);
    case overflow_menu::Destination::ReadingList:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_READING_LIST);
    case overflow_menu::Destination::Passwords:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_PASSWORDS);
    case overflow_menu::Destination::Downloads:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_DOWNLOADS);
    case overflow_menu::Destination::RecentTabs:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_RECENT_TABS);
    case overflow_menu::Destination::WhatsNew:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_WHATS_NEW);
    case overflow_menu::Destination::PriceNotifications:
      return l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDE_DESTINATION_PRICE_NOTIFICATIONS);
  }
}

#pragma mark - Model Creation Utilities

// Creates an OverflowMenuDestination to be displayed in the destinations
// carousel.
- (OverflowMenuDestination*)
    createOverflowMenuDestination:(int)nameID
                      destination:(overflow_menu::Destination)destination
                       symbolName:(NSString*)symbolName
                     systemSymbol:(BOOL)systemSymbol
                  accessibilityID:(NSString*)accessibilityID
                          handler:(Handler)handler {
  __weak __typeof(self) weakSelf = self;

  NSString* name = l10n_util::GetNSString(nameID);

  auto handlerWithMetrics = ^{
    if (weakSelf.menuHasBeenDismissed) {
      return;
    }
    overflow_menu::RecordUmaActionForDestination(destination);

    [weakSelf.menuOrderer recordClickForDestination:destination];

    [weakSelf logFeatureEngagementEventForClickOnDestination:destination];

    handler();
  };

  OverflowMenuDestination* result =
      [[OverflowMenuDestination alloc] initWithName:name
                                         symbolName:symbolName
                                       systemSymbol:systemSymbol
                                   monochromeSymbol:NO
                            accessibilityIdentifier:accessibilityID
                                 enterpriseDisabled:NO
                                displayNewLabelIcon:NO
                                            handler:handlerWithMetrics];

  result.destination = static_cast<NSInteger>(destination);

  if (IsOverflowMenuCustomizationEnabled()) {
    NSMutableArray<OverflowMenuLongPressItem*>* longPressItems =
        [[NSMutableArray alloc] init];

    NSString* hideItemText = [self hideItemTextForDestination:destination];
    if (hideItemText) {
      [longPressItems addObject:[[OverflowMenuLongPressItem alloc]
                                    initWithTitle:hideItemText
                                       symbolName:@"eye.slash"
                                          handler:^{
                                            [weakSelf
                                                hideDestination:destination];
                                          }]];
    }
    [longPressItems
        addObject:[[OverflowMenuLongPressItem alloc]
                      initWithTitle:l10n_util::GetNSString(
                                        IDS_IOS_OVERFLOW_MENU_EDIT_ACTIONS)
                         symbolName:@"pencil"
                            handler:^{
                              [weakSelf beginCustomization];
                            }]];
    result.longPressItems = longPressItems;

    __weak __typeof(result) weakResult = result;
    result.onShownToggleCallback = ^{
      [weakSelf onShownToggledForDestination:weakResult];
    };
  }

  return result;
}

// Creates an OverflowMenuAction with the given name to be displayed.
- (OverflowMenuAction*)
    createOverflowMenuActionWithName:(NSString*)name
                          actionType:(overflow_menu::ActionType)actionType
                          symbolName:(NSString*)symbolName
                        systemSymbol:(BOOL)systemSymbol
                    monochromeSymbol:(BOOL)monochromeSymbol
                     accessibilityID:(NSString*)accessibilityID
                        hideItemText:(NSString*)hideItemText
                             handler:(Handler)handler {
  Handler newHandler =
      [self fullOverflowMenuActionHandlerForActionType:actionType
                                               handler:handler];

  OverflowMenuAction* action =
      [[OverflowMenuAction alloc] initWithName:name
                                    symbolName:symbolName
                                  systemSymbol:systemSymbol
                              monochromeSymbol:monochromeSymbol
                       accessibilityIdentifier:accessibilityID
                            enterpriseDisabled:NO
                           displayNewLabelIcon:NO
                                       handler:newHandler];
  action.actionType = static_cast<NSInteger>(actionType);

  ActionRanking reorderableActions = [self basePageActions];
  // If this action is not reorderable, then don't add any longpress items.
  bool actionIsReorderable =
      std::find(reorderableActions.begin(), reorderableActions.end(),
                actionType) != reorderableActions.end();
  if (IsOverflowMenuCustomizationEnabled() && actionIsReorderable) {
    action.longPressItems =
        [self actionLongPressItemsForActionType:actionType
                                   hideItemText:hideItemText];
  }
  return action;
}

// Creates an OverflowMenuAction with the given nameID as a localized string ID
// to be displayed.
- (OverflowMenuAction*)
    createOverflowMenuActionWithNameID:(int)nameID
                            actionType:(overflow_menu::ActionType)actionType
                            symbolName:(NSString*)symbolName
                          systemSymbol:(BOOL)systemSymbol
                      monochromeSymbol:(BOOL)monochromeSymbol
                       accessibilityID:(NSString*)accessibilityID
                          hideItemText:(NSString*)hideItemText
                               handler:(Handler)handler {
  NSString* name = l10n_util::GetNSString(nameID);

  return [self createOverflowMenuActionWithName:name
                                     actionType:actionType
                                     symbolName:symbolName
                                   systemSymbol:systemSymbol
                               monochromeSymbol:monochromeSymbol
                                accessibilityID:accessibilityID
                                   hideItemText:hideItemText
                                        handler:handler];
}

// Adds any necessary additions to the handler for any specific action.
- (Handler)fullOverflowMenuActionHandlerForActionType:
               (overflow_menu::ActionType)actionType
                                              handler:(Handler)handler {
  __weak __typeof(self) weakSelf = self;
  return ^{
    if (weakSelf.menuHasBeenDismissed) {
      return;
    }
    [weakSelf logFeatureEngagementEventForClickOnAction:actionType];
    handler();
  };
}

// Returns the LongPress items for the given action and hide item text. Can
// be used if actions need to update their name after action creation, as
// the hide item text should correspond to the name.
- (NSArray<OverflowMenuLongPressItem*>*)
    actionLongPressItemsForActionType:(overflow_menu::ActionType)actionType
                         hideItemText:(NSString*)hideItemText {
  __weak __typeof(self) weakSelf = self;
  NSMutableArray<OverflowMenuLongPressItem*>* longPressItems =
      [[NSMutableArray alloc] init];
  if (hideItemText) {
    [longPressItems addObject:[[OverflowMenuLongPressItem alloc]
                                  initWithTitle:hideItemText
                                     symbolName:@"eye.slash"
                                        handler:^{
                                          [weakSelf hideActionType:actionType];
                                        }]];
  }
  [longPressItems
      addObject:[[OverflowMenuLongPressItem alloc]
                    initWithTitle:l10n_util::GetNSString(
                                      IDS_IOS_OVERFLOW_MENU_EDIT_ACTIONS)
                       symbolName:@"pencil"
                          handler:^{
                            [weakSelf
                                beginCustomizationFromActionType:actionType];
                          }]];
  return [longPressItems copy];
}

#pragma mark - Private

// Creates an OverflowMenuDestination for the Spotlight debugger.
- (OverflowMenuDestination*)destinationForSpotlightDebugger:(Handler)handler {
  OverflowMenuDestination* result =
      [[OverflowMenuDestination alloc] initWithName:@"Spotlight Debugger"
                                         symbolName:kSettingsSymbol
                                       systemSymbol:YES
                                   monochromeSymbol:NO
                            accessibilityIdentifier:@"Spotlight Debugger"
                                 enterpriseDisabled:NO
                                displayNewLabelIcon:NO
                                            handler:handler];
  result.destination =
      static_cast<NSInteger>(overflow_menu::Destination::SpotlightDebugger);
  return result;
}

- (DestinationRanking)baseDestinations {
  std::vector<overflow_menu::Destination> destinations = {
      overflow_menu::Destination::Bookmarks,
      overflow_menu::Destination::History,
      overflow_menu::Destination::ReadingList,
      overflow_menu::Destination::Passwords,
      overflow_menu::Destination::Downloads,
      overflow_menu::Destination::RecentTabs,
      overflow_menu::Destination::SiteInfo,
      overflow_menu::Destination::Settings,
  };

  if (IsPriceNotificationsEnabled()) {
    destinations.push_back(overflow_menu::Destination::PriceNotifications);
  }

  destinations.push_back(overflow_menu::Destination::WhatsNew);

  return destinations;
}

// Returns YES if the Overflow Menu should indicate an identity error.
- (BOOL)shouldIndicateIdentityError {
  if (!self.syncService) {
    return NO;
  }

  return ShouldIndicateIdentityErrorInOverflowMenu(self.syncService);
}

// Updates the model to match the current page state.
- (void)updateModel {
  // First update the items' states, and then update all the orders.
  [self updateModelItemsState];
  [self updateModelOrdering];
}

// Updates the state of the individual model items (actions, destinations,
// group footers).
- (void)updateModelItemsState {
  // If the model hasn't been created, there's no need to update.
  if (!self.model) {
    return;
  }

  bool hasMachineLevelPolicies =
      _browserPolicyConnector &&
      _browserPolicyConnector->HasMachineLevelPolicies();
  bool canFetchUserPolicies =
      _authenticationService && _browserStatePrefs &&
      CanFetchUserPolicy(_authenticationService, _browserStatePrefs);
  // Set footer (on last section), if any.
  auto* browser_state =
      self.webState ? self.webState->GetBrowserState() : nullptr;
  auto* chrome_browser_state =
      ChromeBrowserState::FromBrowserState(browser_state);
  if (hasMachineLevelPolicies || canFetchUserPolicies) {
    // Set the Enterprise footer if there are machine level or user level
    // (aka ChromeBrowserState level) policies.
    self.helpActionsGroup.footer = CreateOverflowMenuManagedFooter(
        IDS_IOS_TOOLS_MENU_ENTERPRISE_MANAGED,
        IDS_IOS_TOOLS_MENU_ENTERPRISE_LEARN_MORE, kTextMenuEnterpriseInfo,
        @"overflow_menu_footer_managed", ^{
          [self enterpriseLearnMore];
        });
  } else if (chrome_browser_state &&
             supervised_user::IsSubjectToParentalControls(
                 chrome_browser_state)) {
    self.helpActionsGroup.footer = CreateOverflowMenuManagedFooter(
        IDS_IOS_TOOLS_MENU_PARENT_MANAGED, IDS_IOS_TOOLS_MENU_PARENT_LEARN_MORE,
        kTextMenuFamilyLinkInfo, @"overflow_menu_footer_family_link", ^{
          [self parentLearnMore];
        });
  } else {
    self.helpActionsGroup.footer = nil;
  }

  // The "Add to Reading List" functionality requires JavaScript execution,
  // which is paused while overlays are displayed over the web content area.
  self.readLaterAction.enabled =
      !self.webContentAreaShowingOverlay && [self isCurrentURLWebURL];

  BOOL bookmarkEnabled =
      [self isCurrentURLWebURL] && [self isEditBookmarksEnabled];
  self.addBookmarkAction.enabled = bookmarkEnabled;
  self.editBookmarkAction.enabled = bookmarkEnabled;
  self.translateAction.enabled = [self isTranslateEnabled];
  self.findInPageAction.enabled = [self isFindInPageEnabled];
  self.textZoomAction.enabled = [self isTextZoomEnabled];
  self.requestDesktopAction.enabled =
      [self userAgentType] == web::UserAgentType::MOBILE;
  self.requestMobileAction.enabled =
      [self userAgentType] == web::UserAgentType::DESKTOP;

  // Enable/disable items based on enterprise policies.
  self.openTabAction.enterpriseDisabled =
      IsIncognitoModeForced(self.browserStatePrefs);
  self.openIncognitoTabAction.enterpriseDisabled =
      IsIncognitoModeDisabled(self.browserStatePrefs);
}

// Updates the order of the items in each section or group.
- (void)updateModelOrdering {
  // If the model hasn't been created, there's no need to update.
  if (!self.model) {
    return;
  }

  [self.menuOrderer updateDestinations];

  NSMutableArray<OverflowMenuAction*>* appActions =
      [[NSMutableArray alloc] init];

  // The reload/stop action is only shown when the reload button is not in the
  // toolbar. The reload button is shown in the toolbar when the toolbar is not
  // split.
  if (IsSplitToolbarMode(self.baseViewController)) {
    OverflowMenuAction* reloadStopAction =
        ([self isPageLoading]) ? self.stopLoadAction : self.reloadAction;
    [appActions addObject:reloadStopAction];
  }

  [appActions
      addObjectsFromArray:@[ self.openTabAction, self.openIncognitoTabAction ]];

  if (base::ios::IsMultipleScenesSupported()) {
    [appActions addObject:self.openNewWindowAction];
  }

  self.appActionsGroup.actions = appActions;

  [self.menuOrderer updatePageActions];

  NSMutableArray<OverflowMenuAction*>* helpActions =
      [[NSMutableArray alloc] init];

  if (ios::provider::IsUserFeedbackSupported()) {
    [helpActions addObject:self.reportIssueAction];
  }

  [helpActions addObject:self.helpAction];
  [helpActions addObject:self.shareChromeAction];

  self.helpActionsGroup.actions = helpActions;
}

// Returns whether the page can be manually translated. If `forceMenuLogging` is
// true the translate client will log this result.
- (BOOL)canManuallyTranslate:(BOOL)forceMenuLogging {
  if (!self.webState) {
    return NO;
  }

  auto* translate_client =
      ChromeIOSTranslateClient::FromWebState(self.webState);
  if (!translate_client) {
    return NO;
  }

  translate::TranslateManager* translate_manager =
      translate_client->GetTranslateManager();
  DCHECK(translate_manager);
  return translate_manager->CanManuallyTranslate(forceMenuLogging);
}

// Returns whether translate is enabled on the current page.
- (BOOL)isTranslateEnabled {
  return [self canManuallyTranslate:NO];
}

// Determines whether or not translate is available on the page and logs the
// result. This method should only be called once per popup menu shown.
- (void)logTranslateAvailability {
  [self canManuallyTranslate:YES];
}

// Whether find in page is enabled.
- (BOOL)isFindInPageEnabled {
  if (!self.webState) {
    return NO;
  }

  auto* helper = GetConcreteFindTabHelperFromWebState(self.webState);
  return (helper && helper->CurrentPageSupportsFindInPage() &&
          !helper->IsFindUIActive());
}

// Whether or not text zoom is enabled for this page.
- (BOOL)isTextZoomEnabled {
  if (self.webContentAreaShowingOverlay) {
    return NO;
  }

  if (!self.webState) {
    return NO;
  }
  FontSizeTabHelper* helper = FontSizeTabHelper::FromWebState(self.webState);
  return helper && helper->CurrentPageSupportsTextZoom() &&
         !helper->IsTextZoomUIActive();
}

// Returns YES if user is allowed to edit any bookmarks.
- (BOOL)isEditBookmarksEnabled {
  return self.browserStatePrefs->GetBoolean(
      bookmarks::prefs::kEditBookmarksEnabled);
}

// Whether the page is currently loading.
- (BOOL)isPageLoading {
  return (self.webState) ? self.webState->IsLoading() : NO;
}

// Whether the current page is a web page.
- (BOOL)isCurrentURLWebURL {
  if (!self.webState) {
    return NO;
  }

  const GURL& URL = self.webState->GetLastCommittedURL();
  return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
}

// Whether the current web page has available site info.
- (BOOL)currentWebPageSupportsSiteInfo {
  if (!self.webState) {
    return NO;
  }
  web::NavigationItem* navItem =
      self.webState->GetNavigationManager()->GetVisibleItem();
  if (!navItem) {
    return NO;
  }
  const GURL& URL = navItem->GetURL();
  // Show site info for offline pages.
  if (reading_list::IsOfflineURL(URL)) {
    return YES;
  }
  // Do not show site info for NTP.
  if (URL.spec() == kChromeUIAboutNewTabURL ||
      URL.spec() == kChromeUINewTabURL) {
    return NO;
  }

  if (self.contentBlocked) {
    return NO;
  }

  return navItem->GetVirtualURL().is_valid();
}

// Returns the UserAgentType currently in use.
- (web::UserAgentType)userAgentType {
  if (!self.webState) {
    return web::UserAgentType::NONE;
  }
  web::NavigationItem* visibleItem =
      self.webState->GetNavigationManager()->GetVisibleItem();
  if (!visibleItem) {
    return web::UserAgentType::NONE;
  }

  return visibleItem->GetUserAgentType();
}

// Creates a follow action if needed, when the follow action state is not
// hidden.
- (OverflowMenuAction*)createFollowActionIfNeeded {
  // Returns nil if the follow action state is hidden.
  if (GetFollowActionState(self.webState) == FollowActionStateHidden) {
    return nil;
  }

  OverflowMenuAction* action = [self newFollowAction];
  action.enabled = NO;
  return action;
}

- (void)dismissMenu {
  self.menuHasBeenDismissed = YES;
  [self.popupMenuHandler dismissPopupMenuAnimated:YES];
}

// Possibly logs a feature engagement tracker event when the user clicks on a
// destination.
- (void)logFeatureEngagementEventForClickOnDestination:
    (overflow_menu::Destination)destination {
  if (DestinationWasInitiallyVisible(
          destination, self.model.destinations,
          self.menuOrderer.visibleDestinationsCount)) {
    return;
  }

  if (self.engagementTracker) {
    self.engagementTracker->NotifyEvent(
        feature_engagement::events::kIOSOverflowMenuOffscreenItemUsed);
  }
}

// Possibly logs a feature engagement tracker event when the user clicks on an
// action.
- (void)logFeatureEngagementEventForClickOnAction:
    (overflow_menu::ActionType)action {
  if (ActionWasInitiallyVisible(action, self.pageActionsGroup.actions,
                                kDefaultVisiblePageActionCount)) {
    return;
  }

  if (self.engagementTracker) {
    self.engagementTracker->NotifyEvent(
        feature_engagement::events::kIOSOverflowMenuOffscreenItemUsed);
  }
}

#pragma mark - CRWWebStateObserver

- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webState:(web::WebState*)webState
    didStartNavigation:(web::NavigationContext*)navigation {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webState:(web::WebState*)webState
    didFinishNavigation:(web::NavigationContext*)navigation {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webStateDidStartLoading:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webStateDidStopLoading:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webState:(web::WebState*)webState
    didChangeLoadingProgress:(double)progress {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webStateDidChangeBackForwardState:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webStateDidChangeVisibleSecurityState:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  [self updateModel];
}

- (void)webStateDestroyed:(web::WebState*)webState {
  DCHECK_EQ(_webState, webState);
  self.webState = nullptr;
}

#pragma mark - WebStateListObserving

- (void)didChangeWebStateList:(WebStateList*)webStateList
                       change:(const WebStateListChange&)change
                       status:(const WebStateListStatus&)status {
  if (!status.active_web_state_change()) {
    return;
  }

  self.webState = status.new_active_web_state;
  if (self.webState && self.followAction) {
    FollowTabHelper* followTabHelper =
        FollowTabHelper::FromWebState(self.webState);
    if (followTabHelper) {
      followTabHelper->SetFollowMenuUpdater(self);
    }
  }
}

#pragma mark - BookmarkModelBridgeObserver

// If an added or removed bookmark is the same as the current url, update the
// toolbar so the star highlight is kept in sync.
- (void)didChangeChildrenForNode:(const bookmarks::BookmarkNode*)bookmarkNode {
  [self updateModel];
}

// If all bookmarks are removed, update the toolbar so the star highlight is
// kept in sync.
- (void)bookmarkModelRemovedAllNodes {
  [self updateModel];
}

// In case we are on a bookmarked page before the model is loaded.
- (void)bookmarkModelLoaded {
  [self updateModel];
}

- (void)didChangeNode:(const bookmarks::BookmarkNode*)bookmarkNode {
  [self updateModel];
}
- (void)didMoveNode:(const bookmarks::BookmarkNode*)bookmarkNode
         fromParent:(const bookmarks::BookmarkNode*)oldParent
           toParent:(const bookmarks::BookmarkNode*)newParent {
  // No-op -- required by BookmarkModelBridgeObserver but not used.
}
- (void)didDeleteNode:(const bookmarks::BookmarkNode*)node
           fromFolder:(const bookmarks::BookmarkNode*)folder {
  [self updateModel];
}

#pragma mark - ReadingListModelBridgeObserver

- (void)readingListModelLoaded:(const ReadingListModel*)model {
  [self updateModel];
}

- (void)readingListModelDidApplyChanges:(const ReadingListModel*)model {
  [self updateModel];
}

#pragma mark - FollowMenuUpdater

- (void)updateFollowMenuItemWithWebPage:(WebPageURLs*)webPageURLs
                               followed:(BOOL)followed
                             domainName:(NSString*)domainName
                                enabled:(BOOL)enable {
  DCHECK(IsWebChannelsEnabled());
  self.followAction.enabled = enable;
  if (followed) {
    __weak __typeof(self) weakSelf = self;
    self.followAction.name = l10n_util::GetNSStringF(
        IDS_IOS_TOOLS_MENU_UNFOLLOW, base::SysNSStringToUTF16(domainName));
    self.followAction.symbolName = kXMarkSymbol;
    self.followAction.handler = [self
        fullOverflowMenuActionHandlerForActionType:overflow_menu::ActionType::
                                                       Follow
                                           handler:^{
                                             [weakSelf
                                                 unfollowWebPage:webPageURLs];
                                           }];
    if (IsOverflowMenuCustomizationEnabled()) {
      NSString* hideItemText =
          l10n_util::GetNSStringF(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_UNFOLLOW,
                                  base::SysNSStringToUTF16(domainName));
      self.followAction.longPressItems = [self
          actionLongPressItemsForActionType:overflow_menu::ActionType::Follow
                               hideItemText:hideItemText];
    }
  } else {
    __weak __typeof(self) weakSelf = self;
    self.followAction.name = l10n_util::GetNSStringF(
        IDS_IOS_TOOLS_MENU_FOLLOW, base::SysNSStringToUTF16(domainName));
    self.followAction.symbolName = kPlusSymbol;
    self.followAction.handler = [self
        fullOverflowMenuActionHandlerForActionType:overflow_menu::ActionType::
                                                       Follow
                                           handler:^{
                                             [weakSelf
                                                 followWebPage:webPageURLs];
                                           }];
    if (IsOverflowMenuCustomizationEnabled()) {
      NSString* hideItemText =
          l10n_util::GetNSStringF(IDS_IOS_OVERFLOW_MENU_HIDE_ACTION_FOLLOW,
                                  base::SysNSStringToUTF16(domainName));
      self.followAction.longPressItems = [self
          actionLongPressItemsForActionType:overflow_menu::ActionType::Follow
                               hideItemText:hideItemText];
    }
  }
}

#pragma mark - BrowserContainerConsumer

- (void)setContentBlocked:(BOOL)contentBlocked {
  if (_contentBlocked == contentBlocked) {
    return;
  }
  _contentBlocked = contentBlocked;
  [self updateModel];
}

#pragma mark - PrefObserverDelegate

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  if (preferenceName == bookmarks::prefs::kEditBookmarksEnabled)
    [self updateModel];
}

#pragma mark - IOSLanguageDetectionTabHelperObserving

- (void)iOSLanguageDetectionTabHelper:
            (language::IOSLanguageDetectionTabHelper*)tabHelper
                 didDetermineLanguage:
                     (const translate::LanguageDetectionDetails&)details {
  [self updateModel];
}

#pragma mark - OverlayPresenterObserving

- (void)overlayPresenter:(OverlayPresenter*)presenter
    willShowOverlayForRequest:(OverlayRequest*)request
          initialPresentation:(BOOL)initialPresentation {
  self.webContentAreaShowingOverlay = YES;
}

- (void)overlayPresenter:(OverlayPresenter*)presenter
    didHideOverlayForRequest:(OverlayRequest*)request {
  self.webContentAreaShowingOverlay = NO;
}

- (void)overlayPresenterDestroyed:(OverlayPresenter*)presenter {
  self.webContentAreaOverlayPresenter = nullptr;
}

#pragma mark - OverflowMenuDestinationProvider

- (OverflowMenuDestination*)destinationForDestinationType:
    (overflow_menu::Destination)destinationType {
  switch (destinationType) {
    case overflow_menu::Destination::Bookmarks:
      return self.bookmarksDestination;
    case overflow_menu::Destination::History:
      return (self.isIncognito) ? nil : self.historyDestination;
    case overflow_menu::Destination::ReadingList:
      // Set badges if necessary.
      if (self.engagementTracker &&
          self.engagementTracker->ShouldTriggerHelpUI(
              feature_engagement::kIPHBadgedReadingListFeature)) {
        self.readingListDestination.badge = BadgeTypePromo;
      }
      return self.readingListModel->loaded() ? self.readingListDestination
                                             : nil;
    case overflow_menu::Destination::Passwords:
      return self.passwordsDestination;
    case overflow_menu::Destination::Downloads:
      return self.downloadsDestination;
    case overflow_menu::Destination::RecentTabs:
      return self.isIncognito ? nil : self.recentTabsDestination;
    case overflow_menu::Destination::SiteInfo:
      return ([self currentWebPageSupportsSiteInfo]) ? self.siteInfoDestination
                                                     : nil;
    case overflow_menu::Destination::Settings:
      if ([self shouldIndicateIdentityError]) {
        self.settingsDestination.badge = BadgeTypeError;
      } else if (self.hasSettingsBlueDot) {
        self.settingsDestination.badge = BadgeTypePromo;
      }
      return self.settingsDestination;
    case overflow_menu::Destination::WhatsNew:
      // Possibly set the new label badge if it was never used before.
      if (self.whatsNewDestination.badge == BadgeTypeNone &&
          !WasWhatsNewUsed() && self.engagementTracker) {
        // First check if another active menu (e.g. in another window) has an
        // active badge. If so, just set the badge here without querying the
        // FET. Only query the FET if there is no currently active badge.
        WhatsNewActiveMenusData* data = static_cast<WhatsNewActiveMenusData*>(
            self.engagementTracker->GetUserData(WhatsNewActiveMenusData::key));
        if (data) {
          self.whatsNewDestination.badge = BadgeTypeNew;
          data->activeMenus++;
        } else if (self.engagementTracker->ShouldTriggerHelpUI(
                       feature_engagement::kIPHWhatsNewUpdatedFeature)) {
          std::unique_ptr<WhatsNewActiveMenusData> new_data =
              std::make_unique<WhatsNewActiveMenusData>();
          new_data->activeMenus++;
          self.engagementTracker->SetUserData(WhatsNewActiveMenusData::key,
                                              std::move(new_data));
          self.whatsNewDestination.badge = BadgeTypeNew;
        }
      }
      return self.whatsNewDestination;
    case overflow_menu::Destination::SpotlightDebugger:
      return self.spotlightDebuggerDestination;
    case overflow_menu::Destination::PriceNotifications:
      BOOL priceNotificationsActive =
          self.webState &&
          IsPriceTrackingEnabled(ChromeBrowserState::FromBrowserState(
              self.webState->GetBrowserState()));
      return (priceNotificationsActive) ? self.priceNotificationsDestination
                                        : nil;
  }
}

- (OverflowMenuDestination*)customizationDestinationForDestinationType:
    (overflow_menu::Destination)destinationType {
  switch (destinationType) {
    case overflow_menu::Destination::Bookmarks:
      return [self newBookmarksDestination];
    case overflow_menu::Destination::History:
      return [self newHistoryDestination];
    case overflow_menu::Destination::ReadingList:
      return [self newReadingListDestination];
    case overflow_menu::Destination::Passwords:
      return [self newPasswordsDestination];
    case overflow_menu::Destination::Downloads:
      return [self newDownloadsDestination];
    case overflow_menu::Destination::RecentTabs:
      return [self newRecentTabsDestination];
    case overflow_menu::Destination::SiteInfo:
      return [self newSiteInfoDestination];
    case overflow_menu::Destination::Settings:
      return [self newSettingsDestination];
    case overflow_menu::Destination::WhatsNew:
      return [self newWhatsNewDestination];
    case overflow_menu::Destination::SpotlightDebugger:
      return [self newSpotlightDebuggerDestination];
    case overflow_menu::Destination::PriceNotifications:
      return [self newPriceNotificationsDestination];
  }
}

- (void)destinationCustomizationCompleted {
  if (self.engagementTracker &&
      self.settingsDestination.badge == BadgeTypePromo) {
    self.engagementTracker->NotifyEvent(
        feature_engagement::events::kBlueDotOverflowMenuCustomized);
    [self.popupMenuHandler updateToolsMenuBlueDotVisibility];
  }
}

#pragma mark - OverflowMenuActionProvider

- (ActionRanking)basePageActions {
  ActionRanking actions = {
      overflow_menu::ActionType::Follow,
      overflow_menu::ActionType::Bookmark,
      overflow_menu::ActionType::ReadingList,
      overflow_menu::ActionType::ClearBrowsingData,
      overflow_menu::ActionType::Translate,
      overflow_menu::ActionType::DesktopSite,
      overflow_menu::ActionType::FindInPage,
      overflow_menu::ActionType::TextZoom,
  };

  if (IsLensOverlayAvailable()) {
    actions.push_back(overflow_menu::ActionType::LensOverlay);
  }

  return actions;
}

- (OverflowMenuAction*)actionForActionType:
    (overflow_menu::ActionType)actionType {
  switch (actionType) {
    case overflow_menu::ActionType::Reload:
      return ([self isPageLoading]) ? self.stopLoadAction : self.reloadAction;
    case overflow_menu::ActionType::NewTab:
      return self.openTabAction;
    case overflow_menu::ActionType::NewIncognitoTab:
      return self.openIncognitoTabAction;
    case overflow_menu::ActionType::NewWindow:
      return self.openNewWindowAction;
    case overflow_menu::ActionType::Follow: {
      // Try to create the followAction if there isn't one. It's possible that
      // sometimes when creating the model the followActionState is hidden so
      // the followAction hasn't been created but at the time when updating the
      // model, the followAction should be valid.
      if (!self.followAction) {
        self.followAction = [self createFollowActionIfNeeded];
        DCHECK(!self.followAction || self.webState != nullptr);
      }

      if (self.followAction) {
        FollowTabHelper* followTabHelper =
            FollowTabHelper::FromWebState(self.webState);
        if (followTabHelper) {
          followTabHelper->UpdateFollowMenuItem();
        }
      }
      return self.followAction;
    }
    case overflow_menu::ActionType::Bookmark: {
      BOOL pageIsBookmarked =
          self.webState && self.bookmarkModel &&
          self.bookmarkModel->IsBookmarked(self.webState->GetVisibleURL());
      return (pageIsBookmarked) ? self.editBookmarkAction
                                : self.addBookmarkAction;
    }
    case overflow_menu::ActionType::ReadingList:
      return self.readLaterAction;
    case overflow_menu::ActionType::ClearBrowsingData:
      // Showing the Clear Browsing Data Action would be confusing in incognito.
      return (self.isIncognito) ? nil : self.clearBrowsingDataAction;
    case overflow_menu::ActionType::Translate:
      return self.translateAction;
    case overflow_menu::ActionType::DesktopSite:
      return ([self userAgentType] != web::UserAgentType::DESKTOP)
                 ? self.requestDesktopAction
                 : self.requestMobileAction;
    case overflow_menu::ActionType::FindInPage:
      return self.findInPageAction;
    case overflow_menu::ActionType::TextZoom:
      return self.textZoomAction;
    case overflow_menu::ActionType::ReportAnIssue:
      return self.reportIssueAction;
    case overflow_menu::ActionType::Help:
      return self.helpAction;
    case overflow_menu::ActionType::ShareChrome:
      return self.shareChromeAction;
    case overflow_menu::ActionType::EditActions:
      return self.editActionsAction;
    case overflow_menu::ActionType::LensOverlay:
      return self.lensOverlayAction;
  }
}

// Returns an action for the given `actionType` suitable for displaying in a
// customization UI. This means that it should not depend on any page state
// for things like choosing which variant of an action to show (e.g. Add to
// Bookmarks vs Edit Bookmarks) and shouldn't be enabled based on page state.
- (OverflowMenuAction*)customizationActionForActionType:
    (overflow_menu::ActionType)actionType {
  switch (actionType) {
    // These actions should not be customizable.
    case overflow_menu::ActionType::Reload:
    case overflow_menu::ActionType::NewTab:
    case overflow_menu::ActionType::NewIncognitoTab:
    case overflow_menu::ActionType::NewWindow:
    case overflow_menu::ActionType::ReportAnIssue:
    case overflow_menu::ActionType::Help:
    case overflow_menu::ActionType::ShareChrome:
    case overflow_menu::ActionType::EditActions:
      NOTREACHED_IN_MIGRATION();
      return nil;
    case overflow_menu::ActionType::Follow:
      return [self newFollowAction];
    case overflow_menu::ActionType::Bookmark:
      return [self newAddBookmarkAction];
    case overflow_menu::ActionType::ReadingList:
      return [self newReadLaterAction];
    case overflow_menu::ActionType::ClearBrowsingData:
      return [self newClearBrowsingDataAction];
    case overflow_menu::ActionType::Translate:
      return [self newTranslateAction];
    case overflow_menu::ActionType::DesktopSite:
      return [self newRequestDesktopAction];
    case overflow_menu::ActionType::FindInPage:
      return [self newFindInPageAction];
    case overflow_menu::ActionType::TextZoom:
      return [self newTextZoomAction];
    case overflow_menu::ActionType::LensOverlay:
      return [self openLensOverlayAction];
  }
}

#pragma mark - Action handlers

// Dismisses the menu and reloads the current page.
- (void)reload {
  RecordAction(UserMetricsAction("MobileMenuReload"));
  self.tabBasedIPHBrowserAgent->NotifyMultiGestureRefreshEvent();
  [self dismissMenu];
  self.navigationAgent->Reload();
}

// Dismisses the menu and stops the current page load.
- (void)stopLoading {
  RecordAction(UserMetricsAction("MobileMenuStop"));
  [self dismissMenu];
  self.navigationAgent->StopLoading();
}

// Dismisses the menu and opens a new tab.
- (void)openTab {
  RecordAction(UserMetricsAction("MobileMenuNewTab"));
  RecordAction(UserMetricsAction("MobileTabNewTab"));

  [self dismissMenu];
  [self.applicationHandler
      openURLInNewTab:[OpenNewTabCommand commandWithIncognito:NO]];
}

// Dismisses the menu and opens a new incognito tab.
- (void)openIncognitoTab {
  RecordAction(UserMetricsAction("MobileMenuNewIncognitoTab"));
  [self dismissMenu];
  [self.applicationHandler
      openURLInNewTab:[OpenNewTabCommand commandWithIncognito:YES]];
}

// Dismisses the menu and opens a new window.
- (void)openNewWindow {
  RecordAction(UserMetricsAction("MobileMenuNewWindow"));
  [self dismissMenu];
  [self.applicationHandler
      openNewWindowWithActivity:ActivityToLoadURL(WindowActivityToolsOrigin,
                                                  GURL(kChromeUINewTabURL))];
}

// Dismisses the menu and opens the Clear Browsing Data screen.
- (void)openClearBrowsingData {
  RecordAction(UserMetricsAction("MobileMenuClearBrowsingData"));
  [self dismissMenu];
  if (IsIosQuickDeleteEnabled()) {
    [self.quickDeleteHandler
        showQuickDeleteAndCanPerformTabsClosureAnimation:YES];
  } else {
    [self.settingsHandler showClearBrowsingDataSettings];
  }
}

// Follows the website corresponding to `webPage` and dismisses the menu.
- (void)followWebPage:(WebPageURLs*)webPage {
  // FollowBrowserAgent may be null after -disconnect has been called.
  FollowBrowserAgent* followBrowserAgent = self.followBrowserAgent;
  if (followBrowserAgent)
    followBrowserAgent->FollowWebSite(webPage, FollowSource::OverflowMenu);
  [self dismissMenu];
}

// Unfollows the website corresponding to `webPage` and dismisses the menu.
- (void)unfollowWebPage:(WebPageURLs*)webPage {
  // FollowBrowserAgent may be null after -disconnect has been called.
  FollowBrowserAgent* followBrowserAgent = self.followBrowserAgent;
  if (followBrowserAgent)
    followBrowserAgent->UnfollowWebSite(webPage, FollowSource::OverflowMenu);
  [self dismissMenu];
}

// Dismisses the menu and adds the current page as a bookmark or opens the
// bookmark edit screen if the current page is bookmarked.
- (void)addOrEditBookmark {
  RecordAction(UserMetricsAction("MobileMenuAddToOrEditBookmark"));
  // Dismissing the menu disconnects the mediator, so save anything cleaned up
  // there.
  web::WebState* currentWebState = self.webState;
  [self dismissMenu];
  if (!currentWebState) {
    return;
  }
  [self.bookmarksHandler bookmarkWithWebState:currentWebState];
}

// Dismisses the menu and adds the current page to the reading list.
- (void)addToReadingList {
  RecordAction(UserMetricsAction("MobileMenuReadLater"));

  web::WebState* webState = self.webState;
  if (!webState) {
    return;
  }

  // Fetching the canonical URL is asynchronous (and happen on a background
  // thread), so the operation can be started before the UI is dismissed.
  reading_list::AddToReadingListUsingCanonicalUrl(self.readingListBrowserAgent,
                                                  webState);

  [self dismissMenu];
}

// Dismisses the menu and starts translating the current page.
- (void)translatePage {
  base::RecordAction(UserMetricsAction("MobileMenuTranslate"));
  [self dismissMenu];
  [self.browserCoordinatorHandler showTranslate];
}

// Dismisses the menu and requests the desktop version of the current page
- (void)requestDesktopSite {
  RecordAction(UserMetricsAction("MobileMenuRequestDesktopSite"));
  [self dismissMenu];
  self.navigationAgent->RequestDesktopSite();
  [self.helpHandler
      presentInProductHelpWithType:InProductHelpType::kDefaultSiteView];
}

// Dismisses the menu and requests the mobile version of the current page
- (void)requestMobileSite {
  RecordAction(UserMetricsAction("MobileMenuRequestMobileSite"));
  [self dismissMenu];
  self.navigationAgent->RequestMobileSite();
}

// Dismisses the menu and opens Find In Page
- (void)openFindInPage {
  RecordAction(UserMetricsAction("MobileMenuFindInPage"));
  [self dismissMenu];
  [self.findInPageHandler openFindInPage];
}

// Dismisses the menu and opens Text Zoom
- (void)openTextZoom {
  RecordAction(UserMetricsAction("MobileMenuTextZoom"));
  [self dismissMenu];
  [self.textZoomHandler openTextZoom];
}

// Dismisses the menu and opens the Report an Issue screen.
- (void)reportAnIssue {
  RecordAction(UserMetricsAction("MobileMenuReportAnIssue"));
  [self dismissMenu];
  [self.applicationHandler
      showReportAnIssueFromViewController:self.baseViewController
                                   sender:UserFeedbackSender::ToolsMenu];
}

// Dismisses the menu and opens the help screen.
- (void)openHelp {
  RecordAction(UserMetricsAction("MobileMenuHelp"));
  [self dismissMenu];
  [self.browserCoordinatorHandler showHelpPage];
}

// Begins the action edit flow.
- (void)beginCustomization {
  // Clear the new badge if it's active.
  self.editActionsAction.displayNewLabelIcon = NO;
  self.editActionsAction.highlighted = NO;
  [self.overflowMenuCustomizationHandler showMenuCustomization];
}

- (void)beginCustomizationFromActionType:(overflow_menu::ActionType)actionType {
  [self.overflowMenuCustomizationHandler
      showMenuCustomizationFromActionType:actionType];
}

- (void)hideDestination:(overflow_menu::Destination)destination {
  DestinationCustomizationModel* destinationCustomizationModel =
      self.menuOrderer.destinationCustomizationModel;
  for (OverflowMenuDestination* menuDestination in destinationCustomizationModel
           .shownDestinations) {
    if (menuDestination.destination == static_cast<int>(destination)) {
      menuDestination.shown = NO;
    }
  }
  [self.menuOrderer commitDestinationsUpdate];
}

- (void)hideActionType:(overflow_menu::ActionType)actionType {
  ActionCustomizationModel* actionCustomizationModel =
      self.menuOrderer.actionCustomizationModel;
  for (OverflowMenuAction* action in actionCustomizationModel.shownActions) {
    if (action.actionType == static_cast<int>(actionType)) {
      action.shown = NO;
    }
  }
  [self.menuOrderer commitActionsUpdate];
}

// Creates and opens the lens overlay UI.
- (void)startLensOverlay {
  RecordAction(UserMetricsAction("MobileMenuLensOverlay"));
  [self dismissMenu];
  [self.lensOverlayHandler
      createAndShowLensUI:YES
               entrypoint:LensOverlayEntrypoint::kOverflowMenu];
}

#pragma mark - Destinations Handlers

// Dismisses the menu and opens bookmarks.
- (void)openBookmarks {
  [self dismissMenu];
  [self.browserCoordinatorHandler showBookmarksManager];
}

// Dismisses the menu and opens share sheet to share Chrome's app store link
- (void)shareChromeApp {
  [self dismissMenu];
  [self.activityServiceHandler shareChromeApp];
}

// Dismisses the menu and opens history.
- (void)openHistory {
  if (base::FeatureList::IsEnabled(
          feature_engagement::kIPHiOSHistoryOnOverflowMenuFeature) &&
      _engagementTracker) {
    _engagementTracker->NotifyEvent(
        feature_engagement::events::kHistoryOnOverflowMenuUsed);
  }
  [IntentDonationHelper donateIntent:IntentType::kViewHistory];
  [self dismissMenu];
  [self.applicationHandler showHistory];
}

// Dismisses the menu and opens reading list.
- (void)openReadingList {
  [self dismissMenu];
  [self.browserCoordinatorHandler showReadingList];
}

// Dismisses the menu and opens password list.
- (void)openPasswords {
  UmaHistogramEnumeration(
      "PasswordManager.ManagePasswordsReferrer",
      password_manager::ManagePasswordsReferrer::kChromeMenuItem);
  [self dismissMenu];
  [self.settingsHandler
      showSavedPasswordsSettingsFromViewController:self.baseViewController
                                  showCancelButton:NO];
}

// Dismisses the menu and opens price notifications list.
- (void)openPriceNotifications {
  RecordAction(UserMetricsAction("MobileMenuPriceNotifications"));
  _engagementTracker->NotifyEvent(
      feature_engagement::events::kPriceNotificationsUsed);
  [self dismissMenu];
  [self.priceNotificationHandler showPriceNotifications];
}

// Dismisses the menu and opens downloads.
- (void)openDownloads {
  [self dismissMenu];
  profile_metrics::BrowserProfileType type =
      self.isIncognito ? profile_metrics::BrowserProfileType::kIncognito
                       : profile_metrics::BrowserProfileType::kRegular;
  UmaHistogramEnumeration("Download.OpenDownloadsFromMenu.PerProfileType",
                          type);
  [self.browserCoordinatorHandler showDownloadsFolder];
}

// Dismisses the menu and opens recent tabs.
- (void)openRecentTabs {
  [self dismissMenu];
  [self.browserCoordinatorHandler showRecentTabs];
}

// Dismisses the menu and shows page information.
- (void)openSiteInformation {
  [self dismissMenu];
  [self.pageInfoHandler showPageInfo];
}

// Dismisses the menu and opens What's New.
- (void)openWhatsNew {
  [self dismissMenu];
  [self.whatsNewHandler showWhatsNew];
}

// Dismisses the menu and opens settings.
- (void)openSettings {
  [self dismissMenu];
  profile_metrics::BrowserProfileType type =
      self.isIncognito ? profile_metrics::BrowserProfileType::kIncognito
                       : profile_metrics::BrowserProfileType::kRegular;
  UmaHistogramEnumeration("Settings.OpenSettingsFromMenu.PerProfileType", type);
  [self.applicationHandler
      showSettingsFromViewController:self.baseViewController
            hasDefaultBrowserBlueDot:(self.settingsDestination.badge ==
                                      BadgeTypePromo)];
}

- (void)enterpriseLearnMore {
  [self dismissMenu];
  [self.applicationHandler
      openURLInNewTab:[OpenNewTabCommand commandWithURLFromChrome:
                                             GURL(kChromeUIManagementURL)]];
}

- (void)parentLearnMore {
  [self dismissMenu];
  GURL familyLinkURL = GURL(supervised_user::kManagedByParentUiMoreInfoUrl);
  [self.applicationHandler
      openURLInNewTab:[OpenNewTabCommand
                          commandWithURLFromChrome:familyLinkURL]];
}

- (void)openSpotlightDebugger {
  DCHECK(IsSpotlightDebuggingEnabled());
  [self dismissMenu];
  [self.browserCoordinatorHandler showSpotlightDebugger];
}

// Make any necessary updates for when `destination`'s shown state is toggled.
- (void)onShownToggledForDestination:(OverflowMenuDestination*)destination {
  // If customization is not in progress, there's no need to update any UI.
  if (!self.menuOrderer.isDestinationCustomizationInProgress) {
    return;
  }

  overflow_menu::Destination destinationType =
      static_cast<overflow_menu::Destination>(destination.destination);
  overflow_menu::ActionType correspondingActionType;
  NSString* subtitle;
  switch (destinationType) {
    case overflow_menu::Destination::History:
    case overflow_menu::Destination::Passwords:
    case overflow_menu::Destination::Downloads:
    case overflow_menu::Destination::RecentTabs:
    case overflow_menu::Destination::SiteInfo:
    case overflow_menu::Destination::Settings:
    case overflow_menu::Destination::WhatsNew:
    case overflow_menu::Destination::SpotlightDebugger:
    case overflow_menu::Destination::PriceNotifications:
      // Most destinations have no corresponding destination and nothing special
      // to be done when their shown state is toggled.
      return;
    case overflow_menu::Destination::Bookmarks:
      correspondingActionType = overflow_menu::ActionType::Bookmark;
      subtitle = l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDDEN_BOOKMARKS_SUBTITLE);
      break;
    case overflow_menu::Destination::ReadingList:
      correspondingActionType = overflow_menu::ActionType::ReadingList;
      subtitle = l10n_util::GetNSString(
          IDS_IOS_OVERFLOW_MENU_HIDDEN_READING_LIST_SUBTITLE);
      break;
  }

  [self.menuOrderer customizationUpdateToggledShown:destination.shown
                                forLinkedActionType:correspondingActionType
                                     actionSubtitle:subtitle];
}

@end