chromium/ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.mm

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

#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.h"

#import <optional>

#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/check_op.h"
#import "base/i18n/message_formatter.h"
#import "base/metrics/histogram_functions.h"
#import "base/notreached.h"
#import "base/strings/sys_string_conversions.h"
#import "components/password_manager/core/browser/password_manager_metrics_util.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/base/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_image_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_constants.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/common/ui/util/image_util.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// Sections of the password settings UI.
typedef NS_ENUM(NSInteger, SectionIdentifier) {
  SectionIdentifierSavePasswordsSwitch = kSectionIdentifierEnumZero,
  SectionIdentifierBulkMovePasswordsToAccount,
  SectionIdentifierPasswordsInOtherApps,
  SectionIdentifierAutomaticPasskeyUpgradesSwitch,
  SectionIdentifierGooglePasswordManagerPin,
  SectionIdentifierOnDeviceEncryption,
  SectionIdentifierExportPasswordsButton,
};

// Items within the password settings UI.
typedef NS_ENUM(NSInteger, ItemType) {
  ItemTypeSavePasswordsSwitch = kItemTypeEnumZero,
  ItemTypeManagedSavePasswords,
  ItemTypeBulkMovePasswordsToAccountDescription,
  ItemTypeBulkMovePasswordsToAccountButton,
  ItemTypePasswordsInOtherApps,
  ItemTypeAutomaticPasskeyUpgradesSwitch,
  ItemTypeChangeGooglePasswordManagerPinDescription,
  ItemTypeChangeGooglePasswordManagerPinButton,
  ItemTypeOnDeviceEncryptionOptInDescription,
  ItemTypeOnDeviceEncryptionOptedInDescription,
  ItemTypeOnDeviceEncryptionOptedInLearnMore,
  ItemTypeOnDeviceEncryptionSetUp,
  ItemTypeExportPasswordsButton,
};

// Indicates whether the model has not started loading, is in the process of
// loading, or has completed loading.
typedef NS_ENUM(NSInteger, ModelLoadStatus) {
  ModelNotLoaded = 0,
  ModelIsLoading,
  ModelLoadComplete,
};

bool SyncingWebauthnCredentialsEnabled() {
  return base::FeatureList::IsEnabled(syncer::kSyncWebauthnCredentials);
}

}  // namespace

@interface PasswordSettingsViewController () {
  // The item related to the button for exporting passwords.
  TableViewTextItem* _exportPasswordsItem;

  // Whether or not Chromium has been enabled as a credential provider at the
  // iOS level. This may not be known at load time; the detail text showing on
  // or off status will be omitted until this is populated.
  std::optional<bool> _passwordsInOtherAppsEnabled;
}

// State

// Tracks whether or not the model has loaded.
@property(nonatomic, assign) ModelLoadStatus modelLoadStatus;

// Whether or not the exporter should be enabled.
@property(nonatomic, assign) BOOL canExportPasswords;

// Whether or not the Password Manager is managed by enterprise policy.
@property(nonatomic, assign, getter=isManagedByPolicy) BOOL managedByPolicy;

// Indicates whether or not "Offer to Save Passwords" is set to enabled.
@property(nonatomic, assign, getter=isSavePasswordsEnabled)
    BOOL savePasswordsEnabled;

// The amount of local passwords present on device.
@property(nonatomic, assign) int localPasswordsCount;

// Inidicates whether or not the bulk move passwords to account section should
// be shown.
@property(nonatomic, assign) BOOL showBulkMovePasswordsToAccount;

// Indicates the signed in account.
@property(nonatomic, copy) NSString* signedInAccount;

// On-device encryption state according to the sync service.
@property(nonatomic, assign)
    PasswordSettingsOnDeviceEncryptionState onDeviceEncryptionState;

// UI elements

// The item related to the switch for the password manager setting.
@property(nonatomic, readonly) TableViewSwitchItem* savePasswordsItem;

// The item related to the enterprise managed save password setting.
@property(nonatomic, readonly)
    TableViewInfoButtonItem* managedSavePasswordsItem;

// The item related to the description of bulk moving passwords to the user's
// account.
@property(nonatomic, readonly)
    TableViewImageItem* bulkMovePasswordsToAccountDescriptionItem;

// The item related to the button allowing users to bulk move passwords to their
// account.
@property(nonatomic, readonly)
    TableViewTextItem* bulkMovePasswordsToAccountButtonItem;

// The item showing the current status of Passwords in Other Apps (i.e.,
// credential provider).
@property(nonatomic, readonly)
    TableViewDetailIconItem* passwordsInOtherAppsItem;

// The item related to the switch for the automatic passkey upgrades setting.
@property(nonatomic, readonly)
    TableViewSwitchItem* automaticPasskeyUpgradesSwitchItem;

// Descriptive text shown when the user has an option of changing their Google
// Password Manager PIN.
@property(nonatomic, readonly)
    TableViewImageItem* changeGooglePasswordManagerPinDescriptionItem;

// A button which triggers the change Google Password Manager PIN flow.
@property(nonatomic, readonly)
    TableViewTextItem* changeGooglePasswordManagerPinItem;

// Descriptive text shown when the user has the option of enabling on-device
// encryption.
@property(nonatomic, readonly)
    TableViewImageItem* onDeviceEncryptionOptInDescriptionItem;

// Descriptive text shown when the user has already enabled on-device
// encryption.
@property(nonatomic, readonly)
    TableViewImageItem* onDeviceEncryptionOptedInDescription;

// A button giving the user more information about on-device encrpytion, shown
// when they have already enabled it.
@property(nonatomic, readonly)
    TableViewTextItem* onDeviceEncryptionOptedInLearnMore;

// A button which triggers the setup of on-device encryption.
@property(nonatomic, readonly) TableViewTextItem* setUpOnDeviceEncryptionItem;

@end

@implementation PasswordSettingsViewController

@synthesize savePasswordsItem = _savePasswordsItem;
@synthesize managedSavePasswordsItem = _managedSavePasswordsItem;
@synthesize bulkMovePasswordsToAccountDescriptionItem =
    _bulkMovePasswordsToAccountDescriptionItem;
@synthesize bulkMovePasswordsToAccountButtonItem =
    _bulkMovePasswordsToAccountButtonItem;
@synthesize passwordsInOtherAppsItem = _passwordsInOtherAppsItem;
@synthesize automaticPasskeyUpgradesSwitchItem =
    _automaticPasskeyUpgradesSwitchItem;
@synthesize changeGooglePasswordManagerPinDescriptionItem =
    _changeGooglePasswordManagerPinDescriptionItem;
@synthesize changeGooglePasswordManagerPinItem =
    _changeGooglePasswordManagerPinItem;
@synthesize onDeviceEncryptionOptInDescriptionItem =
    _onDeviceEncryptionOptInDescriptionItem;
@synthesize onDeviceEncryptionOptedInDescription =
    _onDeviceEncryptionOptedInDescription;
@synthesize onDeviceEncryptionOptedInLearnMore =
    _onDeviceEncryptionOptedInLearnMore;
@synthesize setUpOnDeviceEncryptionItem = _setUpOnDeviceEncryptionItem;

- (instancetype)init {
  self = [super initWithStyle:ChromeTableViewStyle()];
  return self;
}

- (CGRect)sourceRectForBulkMovePasswordsToAccount {
  return [self.tableView
             cellForRowAtIndexPath:
                 [self.tableViewModel
                     indexPathForItem:_bulkMovePasswordsToAccountButtonItem]]
      .frame;
}

- (CGRect)sourceRectForPasswordExportAlerts {
  return [self.tableView
             cellForRowAtIndexPath:[self.tableViewModel
                                       indexPathForItem:_exportPasswordsItem]]
      .frame;
}

- (UIView*)sourceViewForAlerts {
  return self.tableView;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  self.title = l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS);
  self.tableView.accessibilityIdentifier = kPasswordsSettingsTableViewId;

  [self loadModel];
}

#pragma mark - LegacyChromeTableViewController

- (void)loadModel {
  [super loadModel];

  self.modelLoadStatus = ModelIsLoading;

  TableViewModel* model = self.tableViewModel;

  [model addSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
  [self addSavePasswordsSwitchOrManagedInfo];

  [model addSectionWithIdentifier:SectionIdentifierPasswordsInOtherApps];
  [model addItem:[self passwordsInOtherAppsItem]
      toSectionWithIdentifier:SectionIdentifierPasswordsInOtherApps];

  if (SyncingWebauthnCredentialsEnabled()) {
    // TODO(crbug.com/358343061): Add item for the policy enforced toggle.
    [model addSectionWithIdentifier:
               SectionIdentifierAutomaticPasskeyUpgradesSwitch];
    [model addItem:[self automaticPasskeyUpgradesSwitchItem]
        toSectionWithIdentifier:
            SectionIdentifierAutomaticPasskeyUpgradesSwitch];

    // TODO(crbug.com/358342483): Add this section only if the device is
    // bootstrapped for using passkeys.
    [model addSectionWithIdentifier:SectionIdentifierGooglePasswordManagerPin];
    [model addItem:[self changeGooglePasswordManagerPinDescriptionItem]
        toSectionWithIdentifier:SectionIdentifierGooglePasswordManagerPin];
    [model addItem:[self changeGooglePasswordManagerPinItem]
        toSectionWithIdentifier:SectionIdentifierGooglePasswordManagerPin];
  }

  if (self.onDeviceEncryptionState !=
      PasswordSettingsOnDeviceEncryptionStateNotShown) {
    [self updateOnDeviceEncryptionSectionWithOldState:
              PasswordSettingsOnDeviceEncryptionStateNotShown];
  }

  // Export passwords button.
  [model addSectionWithIdentifier:SectionIdentifierExportPasswordsButton];
  _exportPasswordsItem = [self makeExportPasswordsItem];
  [self updateExportPasswordsButton];
  [model addItem:_exportPasswordsItem
      toSectionWithIdentifier:SectionIdentifierExportPasswordsButton];

  if (self.showBulkMovePasswordsToAccount) {
    [self updateBulkMovePasswordsToAccountSection];
  }

  self.modelLoadStatus = ModelLoadComplete;
}

#pragma mark - UITableViewDataSource

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  UITableViewCell* cell = [super tableView:tableView
                     cellForRowAtIndexPath:indexPath];

  switch ([self.tableViewModel itemTypeForIndexPath:indexPath]) {
    case ItemTypeSavePasswordsSwitch: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(savePasswordsSwitchChanged:)
                      forControlEvents:UIControlEventValueChanged];
      break;
    }
    case ItemTypeManagedSavePasswords: {
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapManagedUIInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
    case ItemTypeAutomaticPasskeyUpgradesSwitch: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView
                 addTarget:self
                    action:@selector(automaticPasskeyUpgradesSwitchChanged)
          forControlEvents:(UIControlEvents)UIControlEventValueChanged];
    }
  }
  return cell;
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
  switch (itemType) {
    case ItemTypePasswordsInOtherApps: {
      [self.presentationDelegate showPasswordsInOtherAppsScreen];
      break;
    }
    case ItemTypeBulkMovePasswordsToAccountButton: {
      if (self.showBulkMovePasswordsToAccount) {
        [self.delegate bulkMovePasswordsToAccountButtonClicked];
      }
      break;
    }
    case ItemTypeExportPasswordsButton: {
      if (self.canExportPasswords) {
        [self.presentationDelegate startExportFlow];
      }
      break;
    }
    case ItemTypeOnDeviceEncryptionSetUp: {
      [self.presentationDelegate showOnDeviceEncryptionSetUp];
      break;
    }
    case ItemTypeOnDeviceEncryptionOptedInLearnMore: {
      [self.presentationDelegate showOnDeviceEncryptionHelp];
      break;
    }
    case ItemTypeOnDeviceEncryptionOptedInDescription:
    case ItemTypeOnDeviceEncryptionOptInDescription:
    case ItemTypeSavePasswordsSwitch:
    case ItemTypeManagedSavePasswords: {
      DUMP_WILL_BE_NOTREACHED();
    }
  }
  [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (BOOL)tableView:(UITableView*)tableView
    shouldHighlightRowAtIndexPath:(NSIndexPath*)indexPath {
  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
  switch (itemType) {
    case ItemTypeExportPasswordsButton:
      return self.canExportPasswords;
    case ItemTypeSavePasswordsSwitch:
      return NO;
  }
  return YES;
}

#pragma mark - UI item factories

// Creates the switch allowing users to enable/disable the saving of passwords.
- (TableViewSwitchItem*)savePasswordsItem {
  if (_savePasswordsItem) {
    return _savePasswordsItem;
  }

  _savePasswordsItem =
      [[TableViewSwitchItem alloc] initWithType:ItemTypeSavePasswordsSwitch];
  _savePasswordsItem.text =
      l10n_util::GetNSString(IDS_IOS_OFFER_TO_SAVE_PASSWORDS);
  _savePasswordsItem.accessibilityIdentifier =
      kPasswordSettingsSavePasswordSwitchTableViewId;
  [self updateSavePasswordsSwitch];
  return _savePasswordsItem;
}

// Creates the row which replaces `savePasswordsItem` when this preference is
// being managed by enterprise policy.
- (TableViewInfoButtonItem*)managedSavePasswordsItem {
  if (_managedSavePasswordsItem) {
    return _managedSavePasswordsItem;
  }

  _managedSavePasswordsItem = [[TableViewInfoButtonItem alloc]
      initWithType:ItemTypeManagedSavePasswords];
  _managedSavePasswordsItem.text =
      l10n_util::GetNSString(IDS_IOS_OFFER_TO_SAVE_PASSWORDS);
  _managedSavePasswordsItem.accessibilityHint =
      l10n_util::GetNSString(IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT);
  _managedSavePasswordsItem.accessibilityIdentifier =
      kPasswordSettingsManagedSavePasswordSwitchTableViewId;
  [self updateManagedSavePasswordsItem];
  return _managedSavePasswordsItem;
}

// Creates and returns the move passwords to account description item.
- (TableViewImageItem*)bulkMovePasswordsToAccountDescriptionItem {
  if (_bulkMovePasswordsToAccountDescriptionItem) {
    return _bulkMovePasswordsToAccountDescriptionItem;
  }

  _bulkMovePasswordsToAccountDescriptionItem = [[TableViewImageItem alloc]
      initWithType:ItemTypeBulkMovePasswordsToAccountDescription];
  _bulkMovePasswordsToAccountDescriptionItem.title = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_SECTION_TITLE);
  // TODO(crbug.com/40283775): Without setting the table view image item to
  // enabled, the accessibility voiceover reads out dimmed.
  _bulkMovePasswordsToAccountDescriptionItem.enabled = YES;
  _bulkMovePasswordsToAccountDescriptionItem.accessibilityIdentifier =
      kPasswordSettingsBulkMovePasswordsToAccountDescriptionTableViewId;
  _bulkMovePasswordsToAccountDescriptionItem.accessibilityTraits =
      UIAccessibilityTraitHeader;

  std::u16string pattern = l10n_util::GetStringUTF16(
      IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_SECTION_DESCRIPTION);
  std::u16string result = base::i18n::MessageFormatter::FormatWithNamedArgs(
      pattern, "COUNT", self.localPasswordsCount, "EMAIL",
      base::SysNSStringToUTF16(self.signedInAccount));

  _bulkMovePasswordsToAccountDescriptionItem.detailText =
      base::SysUTF16ToNSString(result);

  return _bulkMovePasswordsToAccountDescriptionItem;
}

// Creates and returns the move passwords to account button.
- (TableViewTextItem*)bulkMovePasswordsToAccountButtonItem {
  if (_bulkMovePasswordsToAccountButtonItem) {
    return _bulkMovePasswordsToAccountButtonItem;
  }

  _bulkMovePasswordsToAccountButtonItem = [[TableViewTextItem alloc]
      initWithType:ItemTypeBulkMovePasswordsToAccountButton];
  _bulkMovePasswordsToAccountButtonItem.text = l10n_util::GetPluralNSStringF(
      IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_SECTION_BUTTON,
      self.localPasswordsCount);
  _bulkMovePasswordsToAccountButtonItem.textColor =
      [UIColor colorNamed:kBlueColor];
  _bulkMovePasswordsToAccountButtonItem.accessibilityTraits =
      UIAccessibilityTraitButton;
  _bulkMovePasswordsToAccountButtonItem.accessibilityIdentifier =
      kPasswordSettingsBulkMovePasswordsToAccountButtonTableViewId;
  return _bulkMovePasswordsToAccountButtonItem;
}

- (TableViewDetailIconItem*)passwordsInOtherAppsItem {
  if (_passwordsInOtherAppsItem) {
    return _passwordsInOtherAppsItem;
  }

  _passwordsInOtherAppsItem = [[TableViewDetailIconItem alloc]
      initWithType:ItemTypePasswordsInOtherApps];
  _passwordsInOtherAppsItem.text =
      l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS);
  _passwordsInOtherAppsItem.accessoryType =
      UITableViewCellAccessoryDisclosureIndicator;
  _passwordsInOtherAppsItem.accessibilityTraits |= UIAccessibilityTraitButton;
  _passwordsInOtherAppsItem.accessibilityIdentifier =
      kPasswordSettingsPasswordsInOtherAppsRowId;
  [self updatePasswordsInOtherAppsItem];
  return _passwordsInOtherAppsItem;
}

- (TableViewSwitchItem*)automaticPasskeyUpgradesSwitchItem {
  _automaticPasskeyUpgradesSwitchItem = [[TableViewSwitchItem alloc]
      initWithType:ItemTypeAutomaticPasskeyUpgradesSwitch];
  _automaticPasskeyUpgradesSwitchItem.text =
      l10n_util::GetNSString(IDS_IOS_ALLOW_AUTOMATIC_PASSKEY_UPGRADES);
  _automaticPasskeyUpgradesSwitchItem.detailText =
      l10n_util::GetNSString(IDS_IOS_ALLOW_AUTOMATIC_PASSKEY_UPGRADES_SUBTITLE);
  return _automaticPasskeyUpgradesSwitchItem;
}

- (TableViewImageItem*)changeGooglePasswordManagerPinDescriptionItem {
  _changeGooglePasswordManagerPinDescriptionItem = [[TableViewImageItem alloc]
      initWithType:ItemTypeChangeGooglePasswordManagerPinDescription];
  _changeGooglePasswordManagerPinDescriptionItem.title = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_GOOGLE_PASSWORD_MANAGER_PIN_TITLE);
  _changeGooglePasswordManagerPinDescriptionItem.detailText =
      l10n_util::GetNSString(
          IDS_IOS_PASSWORD_SETTINGS_GOOGLE_PASSWORD_MANAGER_PIN_DESCRIPTION);
  return _changeGooglePasswordManagerPinDescriptionItem;
}

- (TableViewTextItem*)changeGooglePasswordManagerPinItem {
  _changeGooglePasswordManagerPinItem = [[TableViewTextItem alloc]
      initWithType:ItemTypeChangeGooglePasswordManagerPinButton];
  _changeGooglePasswordManagerPinItem.text =
      l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_CHANGE_PIN);
  _changeGooglePasswordManagerPinItem.textColor =
      [UIColor colorNamed:kBlueColor];
  _changeGooglePasswordManagerPinItem.accessibilityTraits =
      UIAccessibilityTraitButton;
  return _changeGooglePasswordManagerPinItem;
}

- (TableViewImageItem*)onDeviceEncryptionOptInDescriptionItem {
  if (_onDeviceEncryptionOptInDescriptionItem) {
    return _onDeviceEncryptionOptInDescriptionItem;
  }

  _onDeviceEncryptionOptInDescriptionItem = [[TableViewImageItem alloc]
      initWithType:ItemTypeOnDeviceEncryptionOptInDescription];
  _onDeviceEncryptionOptInDescriptionItem.title =
      l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION);
  _onDeviceEncryptionOptInDescriptionItem.detailText = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION_OPT_IN);
  _onDeviceEncryptionOptInDescriptionItem.enabled = NO;
  _onDeviceEncryptionOptInDescriptionItem.accessibilityIdentifier =
      kPasswordSettingsOnDeviceEncryptionOptInId;
  return _onDeviceEncryptionOptInDescriptionItem;
}

- (TableViewImageItem*)onDeviceEncryptionOptedInDescription {
  if (_onDeviceEncryptionOptedInDescription) {
    return _onDeviceEncryptionOptedInDescription;
  }

  _onDeviceEncryptionOptedInDescription = [[TableViewImageItem alloc]
      initWithType:ItemTypeOnDeviceEncryptionOptedInDescription];
  _onDeviceEncryptionOptedInDescription.title =
      l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION);
  _onDeviceEncryptionOptedInDescription.detailText = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION_LEARN_MORE);
  _onDeviceEncryptionOptedInDescription.enabled = NO;
  _onDeviceEncryptionOptedInDescription.accessibilityIdentifier =
      kPasswordSettingsOnDeviceEncryptionOptedInTextId;
  return _onDeviceEncryptionOptedInDescription;
}

- (TableViewTextItem*)onDeviceEncryptionOptedInLearnMore {
  if (_onDeviceEncryptionOptedInLearnMore) {
    return _onDeviceEncryptionOptedInLearnMore;
  }

  _onDeviceEncryptionOptedInLearnMore = [[TableViewTextItem alloc]
      initWithType:ItemTypeOnDeviceEncryptionOptedInLearnMore];
  _onDeviceEncryptionOptedInLearnMore.text = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION_OPTED_IN_LEARN_MORE);
  _onDeviceEncryptionOptedInLearnMore.textColor =
      [UIColor colorNamed:kBlueColor];
  _onDeviceEncryptionOptedInLearnMore.accessibilityTraits =
      UIAccessibilityTraitButton;
  _onDeviceEncryptionOptedInLearnMore.accessibilityIdentifier =
      kPasswordSettingsOnDeviceEncryptionLearnMoreId;
  return _onDeviceEncryptionOptedInLearnMore;
}

- (TableViewTextItem*)setUpOnDeviceEncryptionItem {
  if (_setUpOnDeviceEncryptionItem) {
    return _setUpOnDeviceEncryptionItem;
  }

  _setUpOnDeviceEncryptionItem =
      [[TableViewTextItem alloc] initWithType:ItemTypeOnDeviceEncryptionSetUp];
  _setUpOnDeviceEncryptionItem.text = l10n_util::GetNSString(
      IDS_IOS_PASSWORD_SETTINGS_ON_DEVICE_ENCRYPTION_SET_UP);
  _setUpOnDeviceEncryptionItem.textColor = [UIColor colorNamed:kBlueColor];
  _setUpOnDeviceEncryptionItem.accessibilityTraits = UIAccessibilityTraitButton;
  _setUpOnDeviceEncryptionItem.accessibilityIdentifier =
      kPasswordSettingsOnDeviceEncryptionSetUpId;
  return _setUpOnDeviceEncryptionItem;
}

// Creates the "Export Passwords..." button. Coloring and enabled/disabled state
// are handled by `updateExportPasswordsButton`, which should be called as soon
// as the mediator has provided the necessary state.
- (TableViewTextItem*)makeExportPasswordsItem {
  TableViewTextItem* exportPasswordsItem =
      [[TableViewTextItem alloc] initWithType:ItemTypeExportPasswordsButton];
  exportPasswordsItem.text = l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS);
  exportPasswordsItem.accessibilityTraits = UIAccessibilityTraitButton;
  return exportPasswordsItem;
}

#pragma mark - PasswordSettingsConsumer

// The `setCanExportPasswords` method required for the PasswordSettingsConsumer
// protocol is provided by property synthesis.

- (void)setManagedByPolicy:(BOOL)managedByPolicy {
  if (_managedByPolicy == managedByPolicy) {
    return;
  }

  _managedByPolicy = managedByPolicy;

  if (self.modelLoadStatus == ModelNotLoaded) {
    return;
  }

  [self.tableViewModel deleteAllItemsFromSectionWithIdentifier:
                           SectionIdentifierSavePasswordsSwitch];
  [self addSavePasswordsSwitchOrManagedInfo];

  NSIndexSet* indexSet = [[NSIndexSet alloc]
      initWithIndex:[self.tableViewModel
                        sectionForSectionIdentifier:
                            SectionIdentifierSavePasswordsSwitch]];

  [self.tableView reloadSections:indexSet
                withRowAnimation:UITableViewRowAnimationAutomatic];
}

- (void)setSavePasswordsEnabled:(BOOL)enabled {
  if (_savePasswordsEnabled == enabled) {
    return;
  }

  _savePasswordsEnabled = enabled;

  if (self.modelLoadStatus == ModelNotLoaded) {
    return;
  }

  if (self.isManagedByPolicy) {
    [self updateManagedSavePasswordsItem];
  } else {
    [self updateSavePasswordsSwitch];
  }
}

- (void)setLocalPasswordsCount:(int)count
           withUserEligibility:(BOOL)eligibility {
  BOOL showSection = count > 0 && eligibility;

  if (_localPasswordsCount == count &&
      _showBulkMovePasswordsToAccount == showSection) {
    return;
  }

  _localPasswordsCount = count;
  _showBulkMovePasswordsToAccount = showSection;
  [self updateBulkMovePasswordsToAccountSection];
}

- (void)setPasswordsInOtherAppsEnabled:(BOOL)enabled {
  if (_passwordsInOtherAppsEnabled.has_value() &&
      _passwordsInOtherAppsEnabled.value() == enabled) {
    return;
  }

  _passwordsInOtherAppsEnabled = enabled;

  if (self.modelLoadStatus == ModelNotLoaded) {
    return;
  }

  [self updatePasswordsInOtherAppsItem];
}

- (void)setOnDeviceEncryptionState:
    (PasswordSettingsOnDeviceEncryptionState)onDeviceEncryptionState {
  PasswordSettingsOnDeviceEncryptionState oldState = _onDeviceEncryptionState;
  if (oldState == onDeviceEncryptionState) {
    return;
  }
  _onDeviceEncryptionState = onDeviceEncryptionState;

  if (self.modelLoadStatus == ModelNotLoaded) {
    return;
  }

  [self updateOnDeviceEncryptionSectionWithOldState:oldState];
}

- (void)updateExportPasswordsButton {
  // This can be invoked before the item is ready when passwords are received
  // too early.
  if (self.modelLoadStatus == ModelNotLoaded) {
    return;
  }
  if (self.canExportPasswords) {
    _exportPasswordsItem.textColor = [UIColor colorNamed:kBlueColor];
    _exportPasswordsItem.accessibilityTraits &= ~UIAccessibilityTraitNotEnabled;
  } else {
    // Disable, rather than remove, because the button will go back and forth
    // between enabled/disabled status as the flow progresses.
    _exportPasswordsItem.textColor = [UIColor colorNamed:kTextSecondaryColor];
    _exportPasswordsItem.accessibilityTraits |= UIAccessibilityTraitNotEnabled;
  }
  [self reconfigureCellsForItems:@[ _exportPasswordsItem ]];
}

#pragma mark - Actions

- (void)savePasswordsSwitchChanged:(UISwitch*)switchView {
  base::UmaHistogramBoolean(
      "PasswordManager.Settings.ToggleOfferToSavePasswords", switchView.on);
  [self.delegate savedPasswordSwitchDidChange:switchView.on];
}

// Called when the user clicks on the information button of the managed
// setting's UI. Shows a textual bubble with the information of the enterprise.
- (void)didTapManagedUIInfoButton:(UIButton*)buttonView {
  [self.presentationDelegate showManagedPrefInfoForSourceView:buttonView];
  // Disable the button when showing the bubble.
  buttonView.enabled = NO;
}

// Called when the user changes the state of the automatic passkey upgrades
// switch.
- (void)automaticPasskeyUpgradesSwitchChanged {
  // TODO(crbug.com/358343061): Handle changing the switch value.
}

#pragma mark - Private

// Adds the appropriate content to the Save Passwords Switch section depending
// on whether or not the pref is managed.
- (void)addSavePasswordsSwitchOrManagedInfo {
  TableViewItem* item = self.isManagedByPolicy ? self.managedSavePasswordsItem
                                               : self.savePasswordsItem;
  [self.tableViewModel addItem:item
       toSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
}

// Updates the appearance of the Managed Save Passwords item to reflect the
// current state of `isSavePasswordEnabled`.
- (void)updateManagedSavePasswordsItem {
  self.managedSavePasswordsItem.statusText =
      self.isSavePasswordsEnabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                                  : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
  [self reconfigureCellsForItems:@[ self.managedSavePasswordsItem ]];
}

// Updates the appearance of the Save Passwords switch to reflect the current
// state of `isSavePasswordEnabled`.
- (void)updateSavePasswordsSwitch {
  self.savePasswordsItem.on = self.isSavePasswordsEnabled;

  if (self.modelLoadStatus != ModelLoadComplete) {
    return;
  }
  [self reconfigureCellsForItems:@[ self.savePasswordsItem ]];
}

- (void)updateBulkMovePasswordsToAccountSection {
  BOOL sectionExists =
      [self.tableViewModel hasSectionForSectionIdentifier:
                               SectionIdentifierBulkMovePasswordsToAccount];

  // Remove the section if it exists and we shouldn't show it.
  if (!_showBulkMovePasswordsToAccount && sectionExists) {
    NSInteger section =
        [self.tableViewModel sectionForSectionIdentifier:
                                 SectionIdentifierBulkMovePasswordsToAccount];
    [self.tableViewModel removeSectionWithIdentifier:
                             SectionIdentifierBulkMovePasswordsToAccount];
    if (self.modelLoadStatus == ModelLoadComplete) {
      [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:section]
                    withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    return;
  }

  if (!_showBulkMovePasswordsToAccount) {
    return;
  }

  // Prepare the section in the model, either by clearing or adding it.
  if (sectionExists) {
    [self.tableViewModel deleteAllItemsFromSectionWithIdentifier:
                             SectionIdentifierBulkMovePasswordsToAccount];
  } else {
    // Find the section that's supposed to be before Bulk Move Passwords to
    // Account, and insert after that.
    NSInteger bulkMovePasswordsToAccountSectionIndex =
        [self.tableViewModel
            sectionForSectionIdentifier:SectionIdentifierSavePasswordsSwitch] +
        1;
    [self.tableViewModel
        insertSectionWithIdentifier:SectionIdentifierBulkMovePasswordsToAccount
                            atIndex:bulkMovePasswordsToAccountSectionIndex];

    // Record histogram only if the section doesn't already exist but is about
    // to be shown.
    base::UmaHistogramEnumeration(
        "PasswordManager.AccountStorage.MoveToAccountStoreFlowOffered",
        password_manager::metrics_util::MoveToAccountStoreTrigger::
            kExplicitlyTriggeredForMultiplePasswordsInSettings);
  }

  // Add the description and button items to the bulk move passwords to account
  // section.
  [self.tableViewModel addItem:self.bulkMovePasswordsToAccountDescriptionItem
       toSectionWithIdentifier:SectionIdentifierBulkMovePasswordsToAccount];
  [self.tableViewModel addItem:self.bulkMovePasswordsToAccountButtonItem
       toSectionWithIdentifier:SectionIdentifierBulkMovePasswordsToAccount];

  NSIndexSet* indexSet = [NSIndexSet
      indexSetWithIndex:[self.tableViewModel
                            sectionForSectionIdentifier:
                                SectionIdentifierBulkMovePasswordsToAccount]];

  if (self.modelLoadStatus != ModelLoadComplete) {
    return;
  }

  // Reload the section if it exists, otherwise insert it if it does not.
  if (sectionExists) {
    [self.tableView reloadSections:indexSet
                  withRowAnimation:UITableViewRowAnimationAutomatic];
  } else {
    [self.tableView insertSections:indexSet
                  withRowAnimation:UITableViewRowAnimationAutomatic];
  }
}

// Updates the appearance of the Passwords In Other Apps item to reflect the
// current state of `_passwordsInOtherAppsEnabled`.
- (void)updatePasswordsInOtherAppsItem {
  if (_passwordsInOtherAppsEnabled.has_value()) {
    self.passwordsInOtherAppsItem.detailText =
        _passwordsInOtherAppsEnabled.value()
            ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
            : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);

    if (self.modelLoadStatus != ModelLoadComplete) {
      return;
    }
    [self reconfigureCellsForItems:@[ self.passwordsInOtherAppsItem ]];
  }
}

// Updates the UI to present the correct elements for the user's current
// on-device encryption state. `oldState` indicates the currently-displayed UI
// at the time of invocation and is used to determine if we need to add a new
// section or clear (and possibly reload) an existing one.
- (void)updateOnDeviceEncryptionSectionWithOldState:
    (PasswordSettingsOnDeviceEncryptionState)oldState {
  // Easy case: the section just needs to be removed.
  if (self.onDeviceEncryptionState ==
          PasswordSettingsOnDeviceEncryptionStateNotShown &&
      [self.tableViewModel
          hasSectionForSectionIdentifier:SectionIdentifierOnDeviceEncryption]) {
    NSInteger section = [self.tableViewModel
        sectionForSectionIdentifier:SectionIdentifierOnDeviceEncryption];
    [self.tableViewModel
        removeSectionWithIdentifier:SectionIdentifierOnDeviceEncryption];
    if (self.modelLoadStatus == ModelLoadComplete) {
      [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:section]
                    withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    return;
  }

  // Prepare the section in the model, either by clearing or adding it.
  if ([self.tableViewModel
          hasSectionForSectionIdentifier:SectionIdentifierOnDeviceEncryption]) {
    [self.tableViewModel deleteAllItemsFromSectionWithIdentifier:
                             SectionIdentifierOnDeviceEncryption];
  } else {
    // Find the section that's supposed to be before On-Device Encryption, and
    // insert after that.
    NSInteger priorSectionIndex =
        [self.tableViewModel sectionForSectionIdentifier:
                                 SyncingWebauthnCredentialsEnabled()
                                     ? SectionIdentifierGooglePasswordManagerPin
                                     : SectionIdentifierPasswordsInOtherApps];
    NSInteger onDeviceEncryptionSectionIndex = priorSectionIndex + 1;
    [self.tableViewModel
        insertSectionWithIdentifier:SectionIdentifierOnDeviceEncryption
                            atIndex:onDeviceEncryptionSectionIndex];
  }

  // Actually populate the section.
  switch (self.onDeviceEncryptionState) {
    case PasswordSettingsOnDeviceEncryptionStateOptedIn: {
      [self.tableViewModel addItem:self.onDeviceEncryptionOptedInDescription
           toSectionWithIdentifier:SectionIdentifierOnDeviceEncryption];
      [self.tableViewModel addItem:self.onDeviceEncryptionOptedInLearnMore
           toSectionWithIdentifier:SectionIdentifierOnDeviceEncryption];
      break;
    }
    case PasswordSettingsOnDeviceEncryptionStateOfferOptIn: {
      [self.tableViewModel addItem:self.onDeviceEncryptionOptInDescriptionItem
           toSectionWithIdentifier:SectionIdentifierOnDeviceEncryption];
      [self.tableViewModel addItem:self.setUpOnDeviceEncryptionItem
           toSectionWithIdentifier:SectionIdentifierOnDeviceEncryption];
      break;
    }
    default: {
      // If the state is PasswordSettingsOnDeviceEncryptionStateNotShown, then
      // we shouldn't be trying to populate this section. If it's some other
      // value, then this switch needs to be updated.
      NOTREACHED_IN_MIGRATION();
      break;
    }
  }

  // If the model hasn't finished loading, there's no need to update the table
  // view.
  if (self.modelLoadStatus != ModelLoadComplete) {
    return;
  }

  NSIndexSet* indexSet = [NSIndexSet
      indexSetWithIndex:
          [self.tableViewModel
              sectionForSectionIdentifier:SectionIdentifierOnDeviceEncryption]];

  if (oldState == PasswordSettingsOnDeviceEncryptionStateNotShown) {
    [self.tableView insertSections:indexSet
                  withRowAnimation:UITableViewRowAnimationAutomatic];
  } else {
    [self.tableView reloadSections:indexSet
                  withRowAnimation:UITableViewRowAnimationAutomatic];
  }
}

@end