chromium/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller.mm

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

#import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller.h"

#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/feature_list.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/core/browser/metrics/payments/mandatory_reauth_metrics.h"
#import "components/autofill/core/browser/payments_data_manager.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/ios/browser/credit_card_util.h"
#import "components/autofill/ios/browser/personal_data_manager_observer_bridge.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/prefs/pref_service.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/scoped_autofill_payment_reauth_module_override.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.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_link_header_footer_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_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_coordinator.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_coordinator_delegate.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_settings_constants.h"
#import "ios/chrome/browser/ui/settings/autofill/cells/autofill_card_item.h"
#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/browser/ui/settings/settings_root_table_view_controller+toolbar_add.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/reauthentication/reauthentication_protocol.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

constexpr base::TimeDelta kUpdatePrefDelay = base::Seconds(0.3);

enum SectionIdentifier : NSInteger {
  SectionIdentifierAutofillCardSwitch = kSectionIdentifierEnumZero,
  SectionIdentifierMandatoryReauthSwitch,
  SectionIdentifierCards,
};

enum ItemType : NSInteger {
  ItemTypeAutofillCardSwitch = kItemTypeEnumZero,
  ItemTypeAutofillCardManaged,
  ItemTypeAutofillCardSwitchSubtitle,
  ItemTypeCard,
  ItemTypeHeader,
  ItemTypeMandatoryReauthSwitch,
  ItemTypeMandatoryReauthSwitchSubtitle,
};

}  // namespace

using autofill::autofill_metrics::LogMandatoryReauthOptInOrOutUpdateEvent;
using autofill::autofill_metrics::LogMandatoryReauthSettingsPageEditCardEvent;
using autofill::autofill_metrics::MandatoryReauthAuthenticationFlowEvent;
using autofill::autofill_metrics::MandatoryReauthOptInOrOutSource;

#pragma mark - AutofillCreditCardTableViewController

@interface AutofillCreditCardTableViewController () <
    AutofillAddCreditCardCoordinatorDelegate,
    PersonalDataManagerObserver,
    PopoverLabelViewControllerDelegate,
    SuccessfulReauthTimeAccessor> {
  raw_ptr<autofill::PersonalDataManager> _personalDataManager;

  raw_ptr<Browser> _browser;
  std::unique_ptr<autofill::PersonalDataManagerObserverBridge> _observer;

  // Whether Settings have been dismissed.
  BOOL _settingsAreDismissed;

  // Timestamp for last successful reauth attempt by the ReauthenticationModule.
  NSDate* _lastSuccessfulReauthTime;
}

@property(nonatomic, getter=isAutofillCreditCardEnabled)
    BOOL autofillCreditCardEnabled;

// Deleting credit cards updates PersonalDataManager resulting in an observer
// callback, which handles general data updates with a reloadData.
// It is better to handle user-initiated changes with more specific actions
// such as inserting or removing items/sections. This boolean is used to
// stop the observer callback from acting on user-initiated changes.
@property(nonatomic, readwrite, assign) BOOL deletionInProgress;

// Coordinator to add new credit card.
@property(nonatomic, strong)
    AutofillAddCreditCardCoordinator* addCreditCardCoordinator;

// Add button for the toolbar.
@property(nonatomic, strong) UIBarButtonItem* addButtonInToolbar;

// Reauthentication module.
@property(nonatomic, strong) ReauthenticationModule* reauthenticationModule;

@end

@implementation AutofillCreditCardTableViewController

#pragma mark - ViewController Life Cycle.

- (instancetype)initWithBrowser:(Browser*)browser {
  DCHECK(browser);

  self = [super initWithStyle:ChromeTableViewStyle()];
  if (self) {
    self.title = l10n_util::GetNSString(IDS_AUTOFILL_PAYMENT_METHODS);
    self.shouldDisableDoneButtonOnEdit = YES;
    _browser = browser;
    _personalDataManager =
        autofill::PersonalDataManagerFactory::GetForBrowserState(
            _browser->GetBrowserState());
    _observer.reset(new autofill::PersonalDataManagerObserverBridge(self));
    _personalDataManager->AddObserver(_observer.get());
  }
  return self;
}

#pragma mark - properties

- (ReauthenticationModule*)reauthenticationModule {
  id<ReauthenticationProtocol> overrideModule =
      ScopedAutofillPaymentReauthModuleOverride::Get();
  if (overrideModule) {
    return overrideModule;
  }

  if (!_reauthenticationModule) {
    _reauthenticationModule = [[ReauthenticationModule alloc]
        initWithSuccessfulReauthTimeAccessor:self];
  }
  return _reauthenticationModule;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.tableView.allowsMultipleSelectionDuringEditing = YES;
  self.tableView.accessibilityIdentifier = kAutofillCreditCardTableViewId;
  self.navigationController.toolbar.accessibilityIdentifier =
      kAutofillPaymentMethodsToolbarId;
  [self updateUIForEditState];
  [self loadModel];
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
  [super setEditing:editing animated:animated];
  if (editing) {
    self.deleteButton.enabled = NO;
  }

  // We don't want to update this preference when it is in editing mode.
  [self setSwitchItemEnabled:!editing
                    itemType:ItemTypeAutofillCardSwitch
           sectionIdentifier:SectionIdentifierAutofillCardSwitch];
  [self setSwitchItemEnabled:!editing
                    itemType:ItemTypeMandatoryReauthSwitch
           sectionIdentifier:SectionIdentifierMandatoryReauthSwitch];

  [self updateUIForEditState];
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  self.navigationController.toolbarHidden = NO;
}

#pragma mark - LegacyChromeTableViewController

- (void)loadModel {
  [super loadModel];
  if (_settingsAreDismissed) {
    return;
  }

  TableViewModel* model = self.tableViewModel;

  [model addSectionWithIdentifier:SectionIdentifierAutofillCardSwitch];
  if (_browser->GetBrowserState()->GetPrefs()->IsManagedPreference(
          autofill::prefs::kAutofillCreditCardEnabled)) {
    [model addItem:[self cardManagedItem]
        toSectionWithIdentifier:SectionIdentifierAutofillCardSwitch];
  } else {
    [model addItem:[self cardSwitchItem]
        toSectionWithIdentifier:SectionIdentifierAutofillCardSwitch];
  }

  [model setFooter:[self cardSwitchFooter]
      forSectionWithIdentifier:SectionIdentifierAutofillCardSwitch];

  [model addSectionWithIdentifier:SectionIdentifierMandatoryReauthSwitch];
  [model addItem:[self mandatoryReauthSwitchItem]
      toSectionWithIdentifier:SectionIdentifierMandatoryReauthSwitch];
  [model setFooter:[self mandatoryReauthSwitchFooter]
      forSectionWithIdentifier:SectionIdentifierMandatoryReauthSwitch];

  [self populateCardSection];
}

#pragma mark - LoadModel Helpers

// Populates card section using personalDataManager.
- (void)populateCardSection {
  if (_settingsAreDismissed) {
    return;
  }

  TableViewModel* model = self.tableViewModel;
  const std::vector<autofill::CreditCard*>& creditCards =
      _personalDataManager->payments_data_manager().GetCreditCards();
  if (!creditCards.empty()) {
    [model addSectionWithIdentifier:SectionIdentifierCards];
    [model setHeader:[self cardSectionHeader]
        forSectionWithIdentifier:SectionIdentifierCards];
    for (autofill::CreditCard* creditCard : creditCards) {
      DCHECK(creditCard);
      [model addItem:[self itemForCreditCard:*creditCard]
          toSectionWithIdentifier:SectionIdentifierCards];
    }
  }
}

- (TableViewItem*)cardSwitchItem {
  TableViewSwitchItem* switchItem =
      [[TableViewSwitchItem alloc] initWithType:ItemTypeAutofillCardSwitch];
  switchItem.text =
      l10n_util::GetNSString(IDS_AUTOFILL_ENABLE_CREDIT_CARDS_TOGGLE_LABEL);
  switchItem.on = [self isAutofillCreditCardEnabled];
  switchItem.accessibilityIdentifier = kAutofillCreditCardSwitchViewId;
  return switchItem;
}

- (TableViewInfoButtonItem*)cardManagedItem {
  TableViewInfoButtonItem* cardManagedItem = [[TableViewInfoButtonItem alloc]
      initWithType:ItemTypeAutofillCardManaged];
  cardManagedItem.text =
      l10n_util::GetNSString(IDS_AUTOFILL_ENABLE_CREDIT_CARDS_TOGGLE_LABEL);
  // The status could only be off when the pref is managed.
  cardManagedItem.statusText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
  cardManagedItem.accessibilityHint =
      l10n_util::GetNSString(IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT);
  cardManagedItem.accessibilityIdentifier = kAutofillCreditCardManagedViewId;
  return cardManagedItem;
}

- (TableViewHeaderFooterItem*)cardSwitchFooter {
  TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
      initWithType:ItemTypeAutofillCardSwitchSubtitle];
  footer.text =
      l10n_util::GetNSString(IDS_AUTOFILL_ENABLE_CREDIT_CARDS_TOGGLE_SUBLABEL);
  return footer;
}

- (TableViewItem*)mandatoryReauthSwitchItem {
  TableViewSwitchItem* switchItem =
      [[TableViewSwitchItem alloc] initWithType:ItemTypeMandatoryReauthSwitch];
  switchItem.text = l10n_util::GetNSString(
      IDS_PAYMENTS_AUTOFILL_ENABLE_MANDATORY_REAUTH_TOGGLE_LABEL);
  switchItem.accessibilityIdentifier = kAutofillMandatoryReauthSwitchViewId;
  BOOL canAttemptReauth = [self.reauthenticationModule canAttemptReauth];
  switchItem.enabled = canAttemptReauth;
  switchItem.on =
      canAttemptReauth && _personalDataManager->payments_data_manager()
                              .IsPaymentMethodsMandatoryReauthEnabled();
  return switchItem;
}

- (TableViewHeaderFooterItem*)mandatoryReauthSwitchFooter {
  TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
      initWithType:ItemTypeMandatoryReauthSwitchSubtitle];
  footer.text = l10n_util::GetNSString(
      IDS_PAYMENTS_AUTOFILL_ENABLE_MANDATORY_REAUTH_TOGGLE_SUBLABEL);
  return footer;
}

- (TableViewHeaderFooterItem*)cardSectionHeader {
  TableViewTextHeaderFooterItem* header =
      [[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader];
  header.text = l10n_util::GetNSString(IDS_AUTOFILL_PAYMENT_METHODS);
  return header;
}

// TODO(crbug.com/40123293): Add egtest for server cards.
- (TableViewItem*)itemForCreditCard:(const autofill::CreditCard&)creditCard {
  std::string guid(creditCard.guid());
  NSString* creditCardName = autofill::GetCreditCardName(
      creditCard, GetApplicationContext()->GetApplicationLocale());

  AutofillCardItem* item = [[AutofillCardItem alloc] initWithType:ItemTypeCard];
  item.text = creditCardName;
  item.leadingDetailText =
      autofill::GetCreditCardNameAndLastFourDigits(creditCard);
  item.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  item.accessibilityIdentifier = creditCardName;
  item.deletable = autofill::IsCreditCardLocal(creditCard);
  item.GUID = guid;
  if (![item isDeletable]) {
    item.trailingDetailText =
        l10n_util::GetNSString(IDS_IOS_AUTOFILL_WALLET_SERVER_NAME);
  }
  return item;
}

- (BOOL)localCreditCardsExist {
  return !_settingsAreDismissed &&
         !_personalDataManager->payments_data_manager()
              .GetLocalCreditCards()
              .empty();
}

#pragma mark - SettingsControllerProtocol

- (void)reportDismissalUserAction {
  base::RecordAction(base::UserMetricsAction("MobileCreditCardSettingsClose"));
}

- (void)reportBackUserAction {
  base::RecordAction(base::UserMetricsAction("MobileCreditCardSettingsBack"));
}

- (void)settingsWillBeDismissed {
  DCHECK(!_settingsAreDismissed);

  _personalDataManager->RemoveObserver(_observer.get());
  [self stopAutofillAddCreditCardCoordinator];

  // Remove observer bridges.
  _observer.reset();

  // Clear C++ ivars.
  _personalDataManager = nullptr;
  _browser = nullptr;

  _settingsAreDismissed = YES;
}

#pragma mark - SettingsRootTableViewController

- (BOOL)editButtonEnabled {
  return [self localCreditCardsExist];
}

// Override editButtonPressed to support triggering mandatory reauth when the
// user wants to edit/delete the card.
- (void)editButtonPressed {
  // If 1. reauth is not available or 2. reauth succeeded, we
  // proceed by calling the parent's editButtonPressed. Otherwise return
  // early and do nothing.
  if (_personalDataManager->payments_data_manager()
          .IsPaymentMethodsMandatoryReauthEnabled() &&
      [self.reauthenticationModule canAttemptReauth]) {
    LogMandatoryReauthSettingsPageDeleteCardEvent(
        MandatoryReauthAuthenticationFlowEvent::kFlowStarted);

    auto completionHandler = ^(ReauthenticationResult result) {
      switch (result) {
        case ReauthenticationResult::kSuccess:
          LogMandatoryReauthSettingsPageDeleteCardEvent(
              MandatoryReauthAuthenticationFlowEvent::kFlowSucceeded);
          [super editButtonPressed];
          break;
        case ReauthenticationResult::kSkipped:
          LogMandatoryReauthSettingsPageDeleteCardEvent(
              MandatoryReauthAuthenticationFlowEvent::kFlowSkipped);
          [super editButtonPressed];
          break;
        case ReauthenticationResult::kFailure:
          LogMandatoryReauthSettingsPageDeleteCardEvent(
              MandatoryReauthAuthenticationFlowEvent::kFlowFailed);
          break;
      }
    };
    [self.reauthenticationModule
        attemptReauthWithLocalizedReason:
            l10n_util::GetNSString(
                IDS_PAYMENTS_AUTOFILL_SETTINGS_EDIT_MANDATORY_REAUTH)
                    canReusePreviousAuth:YES
                                 handler:completionHandler];
  } else {
    [super editButtonPressed];
  }
}

- (void)deleteItems:(NSArray<NSIndexPath*>*)indexPaths {
  // Do not call super as this also deletes the section if it is empty.
  [self deleteItemAtIndexPaths:indexPaths];
}

- (BOOL)shouldHideToolbar {
  // There is a bug from apple that this method might be called in this view
  // controller even if it is not the top view controller.
  if (self.navigationController.topViewController == self) {
    return NO;
  }

  return [super shouldHideToolbar];
}

- (BOOL)shouldShowEditDoneButton {
  // The "Done" button in the navigation bar closes the sheet.
  return NO;
}

- (void)updateUIForEditState {
  [super updateUIForEditState];
  [self updatedToolbarForEditState];
}

- (UIBarButtonItem*)customLeftToolbarButton {
  if (self.tableView.isEditing) {
    return nil;
  }

  return self.addButtonInToolbar;
}

#pragma mark - Actions

// 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 {
  EnterpriseInfoPopoverViewController* bubbleViewController =
      [[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];
  bubbleViewController.delegate = self;
  [self presentViewController:bubbleViewController animated:YES completion:nil];

  // Disable the button when showing the bubble.
  buttonView.enabled = NO;

  // Set the anchor and arrow direction of the bubble.
  bubbleViewController.popoverPresentationController.sourceView = buttonView;
  bubbleViewController.popoverPresentationController.sourceRect =
      buttonView.bounds;
  bubbleViewController.popoverPresentationController.permittedArrowDirections =
      UIPopoverArrowDirectionAny;
}

#pragma mark - UITableViewDataSource

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

  switch (static_cast<ItemType>(
      [self.tableViewModel itemTypeForIndexPath:indexPath])) {
    case ItemTypeAutofillCardSwitchSubtitle:
    case ItemTypeCard:
    case ItemTypeHeader:
    case ItemTypeMandatoryReauthSwitchSubtitle:
      break;
    case ItemTypeMandatoryReauthSwitch: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(mandatoryReauthSwitchChanged:)
                      forControlEvents:UIControlEventValueChanged];
      break;
    }
    case ItemTypeAutofillCardSwitch: {
      TableViewSwitchCell* switchCell =
          base::apple::ObjCCastStrict<TableViewSwitchCell>(cell);
      [switchCell.switchView addTarget:self
                                action:@selector(autofillCardSwitchChanged:)
                      forControlEvents:UIControlEventValueChanged];
      break;
    }
    case ItemTypeAutofillCardManaged: {
      TableViewInfoButtonCell* managedCell =
          base::apple::ObjCCastStrict<TableViewInfoButtonCell>(cell);
      [managedCell.trailingButton
                 addTarget:self
                    action:@selector(didTapManagedUIInfoButton:)
          forControlEvents:UIControlEventTouchUpInside];
      break;
    }
  }

  return cell;
}

#pragma mark - Switch Callbacks

- (void)autofillCardSwitchChanged:(UISwitch*)switchView {
  [self setSwitchItemOn:[switchView isOn]
               itemType:ItemTypeAutofillCardSwitch
      sectionIdentifier:SectionIdentifierAutofillCardSwitch];

  // Delay updating the pref when VoiceOver is running to prevent a temporary
  // focus shift due to simultaneous UI updates, see crbug.com/326923292.
  if (UIAccessibilityIsVoiceOverRunning()) {
    __weak __typeof(self) weakSelf = self;
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, base::BindOnce(^{
          [weakSelf
              updateAutofillCreditCardPrefAndToolbarForState:[switchView isOn]];
        }),
        kUpdatePrefDelay);
  } else {
    [self updateAutofillCreditCardPrefAndToolbarForState:[switchView isOn]];
  }
}

- (void)mandatoryReauthSwitchChanged:(UISwitch*)switchView {
  if ([self.reauthenticationModule canAttemptReauth]) {
    // Get the original value.
    BOOL mandatoryReauthEnabled = _personalDataManager->payments_data_manager()
                                      .IsPaymentMethodsMandatoryReauthEnabled();
    LogMandatoryReauthOptInOrOutUpdateEvent(
        MandatoryReauthOptInOrOutSource::kSettingsPage,
        /*opt_in=*/!mandatoryReauthEnabled,
        MandatoryReauthAuthenticationFlowEvent::kFlowStarted);

    __weak __typeof(self) weakSelf = self;
    [self.reauthenticationModule
        attemptReauthWithLocalizedReason:
            l10n_util::GetNSString(
                IDS_PAYMENTS_AUTOFILL_SETTINGS_TOGGLE_MANDATORY_REAUTH)
                    canReusePreviousAuth:YES
                                 handler:^(ReauthenticationResult result) {
                                   [weakSelf
                                       handleReauthenticationResult:result];
                                 }];
  } else {
    // Reauth is not supported. Disable the Mandatory Reauth switch and set its
    // value to switched-off.
    [self setSwitchItemEnabled:NO
                      itemType:ItemTypeMandatoryReauthSwitch
             sectionIdentifier:SectionIdentifierMandatoryReauthSwitch];
    [self setSwitchItemOn:NO
                 itemType:ItemTypeMandatoryReauthSwitch
        sectionIdentifier:SectionIdentifierMandatoryReauthSwitch];
  }
}

#pragma mark - Switch Helpers

// Sets switchItem's state to `on`. It is important that there is only one item
// of `switchItemType` in section with `sectionIdentifier`.
- (void)setSwitchItemOn:(BOOL)on
               itemType:(ItemType)switchItemType
      sectionIdentifier:(SectionIdentifier)sectionIdentifier {
  NSIndexPath* switchPath =
      [self.tableViewModel indexPathForItemType:switchItemType
                              sectionIdentifier:sectionIdentifier];
  TableViewSwitchItem* switchItem =
      base::apple::ObjCCastStrict<TableViewSwitchItem>(
          [self.tableViewModel itemAtIndexPath:switchPath]);
  switchItem.on = on;
  [self reconfigureCellsForItems:@[ switchItem ]];
}

// Sets switchItem's enabled status to `enabled` and reconfigures the
// corresponding cell. It is important that there is no more than one item of
// `switchItemType` in section with `sectionIdentifier`.
- (void)setSwitchItemEnabled:(BOOL)enabled
                    itemType:(ItemType)switchItemType
           sectionIdentifier:(SectionIdentifier)sectionIdentifier {
  TableViewModel* model = self.tableViewModel;

  if (![model hasItemForItemType:switchItemType
               sectionIdentifier:sectionIdentifier]) {
    return;
  }
  NSIndexPath* switchPath = [model indexPathForItemType:switchItemType
                                      sectionIdentifier:sectionIdentifier];
  TableViewSwitchItem* switchItem =
      base::apple::ObjCCastStrict<TableViewSwitchItem>(
          [model itemAtIndexPath:switchPath]);
  [switchItem setEnabled:enabled];
  [self reconfigureCellsForItems:@[ switchItem ]];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  [super tableView:tableView didSelectRowAtIndexPath:indexPath];
  if (_settingsAreDismissed) {
    return;
  }

  // Edit mode is the state where the user can select and delete entries. In
  // edit mode, selection is handled by the superclass. When not in edit mode
  // selection presents the editing controller for the selected entry.
  if (self.editing) {
    self.deleteButton.enabled = YES;
    return;
  }

  [tableView deselectRowAtIndexPath:indexPath animated:YES];
  TableViewModel* model = self.tableViewModel;
  NSInteger type = [model itemTypeForIndexPath:indexPath];
  if (type != ItemTypeCard)
    return;

  const std::vector<autofill::CreditCard*>& creditCards =
      _personalDataManager->payments_data_manager().GetCreditCards();
  autofill::CreditCard selectedCard = *creditCards[indexPath.item];
  if (autofill::IsCreditCardLocal(selectedCard) &&
      _personalDataManager->payments_data_manager()
          .IsPaymentMethodsMandatoryReauthEnabled() &&
      [self.reauthenticationModule canAttemptReauth]) {
    [self attemptReauthenticationForEditCard:selectedCard];
  } else {
    [self openCreditCardDetails:selectedCard];
  }
}

// Attempt reauthentication, if all goes well proceed to card details page.
- (void)attemptReauthenticationForEditCard:(autofill::CreditCard)selectedCard {
  LogMandatoryReauthSettingsPageEditCardEvent(
      MandatoryReauthAuthenticationFlowEvent::kFlowStarted);
  auto completionHandler = ^(ReauthenticationResult result) {
    MandatoryReauthAuthenticationFlowEvent event =
        result == ReauthenticationResult::kFailure
            ? MandatoryReauthAuthenticationFlowEvent::kFlowFailed
            : MandatoryReauthAuthenticationFlowEvent::kFlowSucceeded;
    LogMandatoryReauthSettingsPageEditCardEvent(event);

    if (result != ReauthenticationResult::kFailure) {
      [self openCreditCardDetails:selectedCard];
    }
  };
  [self.reauthenticationModule
      attemptReauthWithLocalizedReason:
          l10n_util::GetNSString(
              IDS_PAYMENTS_AUTOFILL_SETTINGS_EDIT_MANDATORY_REAUTH)
                  canReusePreviousAuth:YES
                               handler:completionHandler];
}

- (void)openCreditCardDetails:(autofill::CreditCard)creditCard {
  AutofillCreditCardEditTableViewController* controller =
      [[AutofillCreditCardEditTableViewController alloc]
           initWithCreditCard:creditCard
          personalDataManager:_personalDataManager];
  [self configureHandlersForRootViewController:controller];
  [self.navigationController pushViewController:controller animated:YES];
}

- (void)tableView:(UITableView*)tableView
    didDeselectRowAtIndexPath:(NSIndexPath*)indexPath {
  [super tableView:tableView didDeselectRowAtIndexPath:indexPath];
  if (_settingsAreDismissed || !self.tableView.editing) {
    return;
  }

  if (self.tableView.indexPathsForSelectedRows.count == 0)
    self.deleteButton.enabled = NO;
}

#pragma mark - UITableViewDataSource

- (BOOL)tableView:(UITableView*)tableView
    canEditRowAtIndexPath:(NSIndexPath*)indexPath {
  if (_settingsAreDismissed) {
    return NO;
  }

  // Only autofill card cells are editable.
  TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
  if ([item isKindOfClass:[AutofillCardItem class]]) {
    AutofillCardItem* autofillItem =
        base::apple::ObjCCastStrict<AutofillCardItem>(item);
    return [autofillItem isDeletable];
  }
  return NO;
}

- (void)tableView:(UITableView*)tableView
    commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
     forRowAtIndexPath:(NSIndexPath*)indexPath {
  if (_settingsAreDismissed ||
      editingStyle != UITableViewCellEditingStyleDelete) {
    return;
  }
  [self deleteItemAtIndexPaths:@[ indexPath ]];
}

#pragma mark - helper methods

- (void)deleteItemAtIndexPaths:(NSArray<NSIndexPath*>*)indexPaths {
  if (_settingsAreDismissed) {
    return;
  }

  self.deletionInProgress = YES;
  for (NSIndexPath* indexPath in indexPaths) {
    AutofillCardItem* item = base::apple::ObjCCastStrict<AutofillCardItem>(
        [self.tableViewModel itemAtIndexPath:indexPath]);
    _personalDataManager->RemoveByGUID(item.GUID);
  }

  self.editing = NO;
  __weak AutofillCreditCardTableViewController* weakSelf = self;
  [self.tableView
      performBatchUpdates:^{
        // Obtain strong reference again.
        AutofillCreditCardTableViewController* strongSelf = weakSelf;
        if (!strongSelf) {
          return;
        }

        NSUInteger section = [self.tableViewModel
            sectionForSectionIdentifier:SectionIdentifierCards];
        NSUInteger currentCount =
            [self.tableViewModel numberOfItemsInSection:section];
        if (currentCount == indexPaths.count) {
          [[strongSelf tableViewModel]
              removeSectionWithIdentifier:SectionIdentifierCards];
          [[strongSelf tableView]
                deleteSections:[NSIndexSet indexSetWithIndex:section]
              withRowAnimation:UITableViewRowAnimationAutomatic];
        } else {
          [strongSelf removeFromModelItemAtIndexPaths:indexPaths];
          [strongSelf.tableView
              deleteRowsAtIndexPaths:indexPaths
                    withRowAnimation:UITableViewRowAnimationAutomatic];
        }
      }
      completion:^(BOOL finished) {
        // Obtain strong reference again.
        AutofillCreditCardTableViewController* strongSelf = weakSelf;
        if (!strongSelf) {
          return;
        }

        // Turn off edit mode if there is nothing to edit.
        if (![strongSelf localCreditCardsExist] && strongSelf.editing) {
          [strongSelf setEditing:NO animated:YES];
        }
        [strongSelf updateUIForEditState];
        strongSelf.deletionInProgress = NO;
      }];
}

// Opens new view controller `AutofillAddCreditCardViewController` for fillig
// credit card details.
- (void)handleAddPayment {
  if (_settingsAreDismissed) {
    return;
  }

  base::RecordAction(
      base::UserMetricsAction("MobileAddCreditCard.AddPaymentMethodButton"));

  self.addCreditCardCoordinator = [[AutofillAddCreditCardCoordinator alloc]
      initWithBaseViewController:self
                         browser:_browser];
  self.addCreditCardCoordinator.delegate = self;
  [self.addCreditCardCoordinator start];
}

#pragma mark PersonalDataManagerObserver

- (void)onPersonalDataChanged {
  if (self.deletionInProgress)
    return;

  if (![self localCreditCardsExist] && self.editing) {
    // Turn off edit mode if there exists nothing to edit.
    [self setEditing:NO animated:YES];
  }

  [self updateUIForEditState];
  [self reloadData];
}

#pragma mark - SuccessfulReauthTimeAccessor

- (void)updateSuccessfulReauthTime {
  _lastSuccessfulReauthTime = [[NSDate alloc] init];
}

- (NSDate*)lastSuccessfulReauthTime {
  return _lastSuccessfulReauthTime;
}

#pragma mark - Getters and Setter

- (BOOL)isAutofillCreditCardEnabled {
  return autofill::prefs::IsAutofillPaymentMethodsEnabled(
      _browser->GetBrowserState()->GetPrefs());
}

- (void)setAutofillCreditCardEnabled:(BOOL)isEnabled {
  return autofill::prefs::SetAutofillPaymentMethodsEnabled(
      _browser->GetBrowserState()->GetPrefs(), isEnabled);
}

#pragma mark - PopoverLabelViewControllerDelegate

- (void)didTapLinkURL:(NSURL*)URL {
  [self view:nil didTapLinkURL:[[CrURL alloc] initWithNSURL:URL]];
}

#pragma mark - Private

// Returns a toolbar button for starting the "Add Credit Card" flow.
- (UIBarButtonItem*)addButtonInToolbar {
  if (!_addButtonInToolbar) {
    _addButtonInToolbar =
        [self addButtonWithAction:@selector(addButtonCallback)];
    _addButtonInToolbar.enabled = [self isAutofillCreditCardEnabled];
  }
  return _addButtonInToolbar;
}

- (void)addButtonCallback {
  [self handleAddPayment];
}

- (void)stopAutofillAddCreditCardCoordinator {
  [self.addCreditCardCoordinator stop];
  self.addCreditCardCoordinator.delegate = nil;
  self.addCreditCardCoordinator = nil;
}

// Function that is invoked when the reauth is finished, and handles the reauth
// result.
- (void)handleReauthenticationResult:(ReauthenticationResult)result {
  // Get the original value.
  BOOL mandatoryReauthEnabled = _personalDataManager->payments_data_manager()
                                    .IsPaymentMethodsMandatoryReauthEnabled();

  MandatoryReauthAuthenticationFlowEvent flow_event;
  if (result == ReauthenticationResult::kFailure) {
    // If authentication fails, restore the switch to its original state.
    [self setSwitchItemOn:mandatoryReauthEnabled
                 itemType:ItemTypeMandatoryReauthSwitch
        sectionIdentifier:SectionIdentifierMandatoryReauthSwitch];
    flow_event = MandatoryReauthAuthenticationFlowEvent::kFlowFailed;

  } else {
    // Upon success, update the mandatory reauth pref and the switch.
    _personalDataManager->payments_data_manager()
        .SetPaymentMethodsMandatoryReauthEnabled(!mandatoryReauthEnabled);
    [self setSwitchItemOn:!mandatoryReauthEnabled
                 itemType:ItemTypeMandatoryReauthSwitch
        sectionIdentifier:SectionIdentifierMandatoryReauthSwitch];
    flow_event = MandatoryReauthAuthenticationFlowEvent::kFlowSucceeded;
  }
  LogMandatoryReauthOptInOrOutUpdateEvent(
      MandatoryReauthOptInOrOutSource::kSettingsPage,
      /*opt_in=*/!mandatoryReauthEnabled, flow_event);
}

// Updates the Autofill Credit Card pref and the view controller's toolbar
// according to the provided `enabled` state.
- (void)updateAutofillCreditCardPrefAndToolbarForState:(BOOL)enabled {
  [self setAutofillCreditCardEnabled:enabled];
  self.addButtonInToolbar.enabled = [self isAutofillCreditCardEnabled];
}

#pragma mark - AutofillAddCreditCardCoordinatorDelegate

- (void)autofillAddCreditCardCoordinatorWantsToBeStopped:
    (AutofillAddCreditCardCoordinator*)coordinator {
  CHECK_EQ(coordinator, self.addCreditCardCoordinator);
  [self stopAutofillAddCreditCardCoordinator];
}

@end