chromium/ios/chrome/browser/autofill/ui_bundled/autofill_profile_edit_mediator.mm

// Copyright 2023 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/autofill/ui_bundled/autofill_profile_edit_mediator.h"

#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/core/browser/address_data_manager.h"
#import "components/autofill/core/browser/autofill_address_util.h"
#import "components/autofill/core/browser/autofill_data_util.h"
#import "components/autofill/core/browser/geo/autofill_country.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/autofill/core/browser/profile_requirement_utils.h"
#import "components/autofill/core/browser/ui/country_combobox_model.h"
#import "components/autofill/ios/common/features.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_profile_edit_consumer.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_profile_edit_mediator_delegate.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_ui_type_util.h"
#import "ios/chrome/browser/autofill/ui_bundled/cells/country_item.h"
#import "third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h"
#import "third_party/libaddressinput/src/cpp/include/libaddressinput/localization.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

typedef NS_ENUM(NSInteger, ItemType) {
  ItemTypeCountry = kItemTypeEnumZero,
};

// Field types that do not change with the country value.
constexpr std::array<autofill::FieldType, 5> kStaticFieldsTypes = {
    autofill::NAME_FULL, autofill::COMPANY_NAME, autofill::ADDRESS_HOME_COUNTRY,
    autofill::PHONE_HOME_WHOLE_NUMBER, autofill::EMAIL_ADDRESS};

}  // namespace

@interface AutofillProfileEditMediator ()

// Stores the address input fields.
@property(nonatomic, strong, readonly)
    NSArray<AutofillProfileAddressField*>* inputAddressFields;

@end

@implementation AutofillProfileEditMediator {
  raw_ptr<autofill::AutofillProfile> _autofillProfile;

  // Used for editing autofill profile.
  autofill::PersonalDataManager* _personalDataManager;

  // This property is for an interface which sends a response about saving the
  // edited profile.
  __weak id<AutofillProfileEditMediatorDelegate> _delegate;

  // The fetched country list.
  NSArray<CountryItem*>* _allCountries;

  // The country code that has been selected.
  NSString* _selectedCountryCode;

  // YES, when the mediator belongs to the migration prompt.
  BOOL _isMigrationPrompt;

  // If YES, denote that the particular field requires a value.
  BOOL _line1Required;
  BOOL _cityRequired;
  BOOL _stateRequired;
  BOOL _zipRequired;

  // Stores the required field names whose values are empty;
  NSMutableSet<NSString*>* _requiredFieldsWithEmptyValue;
}

- (instancetype)initWithDelegate:
                    (id<AutofillProfileEditMediatorDelegate>)delegate
             personalDataManager:(autofill::PersonalDataManager*)dataManager
                 autofillProfile:(autofill::AutofillProfile*)autofillProfile
               isMigrationPrompt:(BOOL)isMigrationPrompt {
  self = [super init];

  if (self) {
    DCHECK(dataManager);
    _personalDataManager = dataManager;
    _autofillProfile = autofillProfile;
    _delegate = delegate;
    _isMigrationPrompt = isMigrationPrompt;
    _requiredFieldsWithEmptyValue = [[NSMutableSet<NSString*> alloc] init];
    _selectedCountryCode =
        base::SysUTF8ToNSString(autofill::data_util::GetCountryCodeWithFallback(
            *autofillProfile, GetApplicationContext()->GetApplicationLocale()));

    [self loadCountries];
  }

  return self;
}

- (void)setConsumer:(id<AutofillProfileEditConsumer>)consumer {
  if (_consumer == consumer) {
    return;
  }

  _consumer = consumer;

  [self fetchAndSetInputAddressFields];
  [self sendAutofillProfileDataToConsumer];
  [self fetchAndUpdateFieldRequirements];

  [_consumer setAccountProfile:[self isAccountProfile]];
}

#pragma mark - Public

- (void)didSelectCountry:(CountryItem*)countryItem {
  if ([_selectedCountryCode isEqualToString:countryItem.countryCode]) {
    return;
  }

  _selectedCountryCode = countryItem.countryCode;

  [self fetchAndSetInputAddressFields];
  [self fetchAndUpdateFieldRequirements];
  [self.consumer didSelectCountry:countryItem.text];
}

#pragma mark - AutofillSettingsProfileEditTableViewControllerDelegate

- (void)didEditAutofillProfileFromSettings {
  _personalDataManager->address_data_manager().UpdateProfile(*_autofillProfile);

  // Push the saved profile data to the consumer.
  [self sendAutofillProfileDataToConsumer];
}

- (BOOL)isMinimumAddress {
  return autofill::IsMinimumAddress(*_autofillProfile);
}

- (void)didTapMigrateToAccountButton {
  _personalDataManager->address_data_manager().MigrateProfileToAccount(
      *_autofillProfile);

  // Push the saved profile data to the consumer.
  [self sendAutofillProfileDataToConsumer];
}

#pragma mark - AutofillProfileEditTableViewControllerDelegate

- (void)willSelectCountryWithCurrentlySelectedCountry:(NSString*)country {
  [_delegate willSelectCountryWithCurrentlySelectedCountry:country
                                               countryList:_allCountries];
}

- (void)didSaveProfileFromModal {
  [_delegate didSaveProfile];
}

- (void)updateProfileMetadataWithValue:(NSString*)value
                  forAutofillFieldType:(NSString*)autofillFieldType {
  autofill::FieldType serverFieldType =
      [self typeNameToFieldType:autofillFieldType];

  // Since the country field is a text field, we should use SetInfo() to
  // make sure they get converted to country codes.
  // Use SetInfo for fullname to propogate the change to the name_first,
  // name_middle and name_last subcomponents.
  if (serverFieldType == autofill::ADDRESS_HOME_COUNTRY ||
      serverFieldType == autofill::NAME_FULL) {
    _autofillProfile->SetInfoWithVerificationStatus(
        autofill::AutofillType(serverFieldType),
        base::SysNSStringToUTF16(value),
        GetApplicationContext()->GetApplicationLocale(),
        autofill::VerificationStatus::kUserVerified);
  } else {
    _autofillProfile->SetRawInfoWithVerificationStatus(
        serverFieldType, base::SysNSStringToUTF16(value),
        autofill::VerificationStatus::kUserVerified);
  }
}

- (BOOL)fieldContainsValidValue:(NSString*)autofillFieldType
                  hasEmptyValue:(BOOL)hasEmptyValue
      moveToAccountFromSettings:(BOOL)moveToAccountFromSettings {
  if (![self isAutofillFieldTypeRequiredField:autofillFieldType] ||
      [self requiredFieldWasEmptyOnProfileLoadForType:autofillFieldType
                            moveToAccountFromSettings:
                                moveToAccountFromSettings]) {
    // Early return if the text field is not a required field or contained an
    // empty value when the profile was loaded.
    return YES;
  }

  // If the required text field contains a value now, remove it from
  // `_requiredFieldsWithEmptyValue`.
  if ([_requiredFieldsWithEmptyValue containsObject:autofillFieldType] &&
      !hasEmptyValue) {
    [_requiredFieldsWithEmptyValue removeObject:autofillFieldType];
    return YES;
  }

  // If the required field is empty, add it to `_requiredFieldsWithEmptyValue`.
  if (hasEmptyValue) {
    [_requiredFieldsWithEmptyValue addObject:autofillFieldType];
    return NO;
  }

  return !hasEmptyValue;
}

- (void)viewDidDisappear {
  [_delegate autofillEditProfileMediatorDidFinish:self];
}

- (NSString*)fieldTypeToTypeName:(autofill::FieldType)autofillType {
  return base::SysUTF8ToNSString(autofill::FieldTypeToStringView(autofillType));
}

- (int)requiredFieldsWithEmptyValuesCount {
  return (int)[_requiredFieldsWithEmptyValue count];
}

- (void)resetRequiredFieldsWithEmptyValuesCount {
  [_requiredFieldsWithEmptyValue removeAllObjects];
}

#pragma mark - Private

// Returns true if the `autofillFieldType` belongs to a required field.
- (BOOL)isAutofillFieldTypeRequiredField:(NSString*)autofillFieldType {
  autofill::FieldType serverFieldType =
      [self typeNameToFieldType:autofillFieldType];
  return (serverFieldType == autofill::ADDRESS_HOME_LINE1 && _line1Required) ||
         (serverFieldType == autofill::ADDRESS_HOME_STREET_ADDRESS &&
          _line1Required) ||
         (serverFieldType == autofill::ADDRESS_HOME_CITY && _cityRequired) ||
         (serverFieldType == autofill::ADDRESS_HOME_STATE && _stateRequired) ||
         (serverFieldType == autofill::ADDRESS_HOME_ZIP && _zipRequired);
}

// Returns YES if the profile contained an empty value for the required
// `itemType`.
- (BOOL)requiredFieldWasEmptyOnProfileLoadForType:(NSString*)autofillFieldType
                        moveToAccountFromSettings:
                            (BOOL)moveToAccountFromSettings {
  if (moveToAccountFromSettings) {
    return NO;
  }
  autofill::FieldType serverFieldType =
      [self typeNameToFieldType:autofillFieldType];
  return _autofillProfile
      ->GetInfo(serverFieldType,
                GetApplicationContext()->GetApplicationLocale())
      .empty();
}

// Loads the country codes and names and sets the default selected country code.
- (void)loadCountries {
  autofill::CountryComboboxModel countryModel;
  countryModel.SetCountries(*_personalDataManager,
                            base::RepeatingCallback<bool(const std::string&)>(),
                            GetApplicationContext()->GetApplicationLocale());
  const autofill::CountryComboboxModel::CountryVector& countriesVector =
      countryModel.countries();

  NSMutableArray<CountryItem*>* countryItems = [[NSMutableArray alloc]
      initWithCapacity:static_cast<NSUInteger>(countriesVector.size())];
  // Skip the first country as it appears twice in the
  // list. It was relevant to other platforms where the country does not have a
  // search option.
  for (size_t i = 1; i < countriesVector.size(); ++i) {
    if (countriesVector[i].get()) {
      if (([self isAccountProfile] || _isMigrationPrompt) &&
          !_personalDataManager->address_data_manager()
               .IsCountryEligibleForAccountStorage(
                   countriesVector[i]->country_code())) {
        continue;
      }
      CountryItem* countryItem =
          [[CountryItem alloc] initWithType:ItemTypeCountry];
      countryItem.text = base::SysUTF16ToNSString(countriesVector[i]->name());
      countryItem.countryCode =
          base::SysUTF8ToNSString(countriesVector[i]->country_code());
      countryItem.accessibilityIdentifier = countryItem.text;
      countryItem.accessibilityTraits |= UIAccessibilityTraitButton;
      [countryItems addObject:countryItem];
    }
  }
  _allCountries = countryItems;
}

// Fetches and computes the required fields based on `_selectedCountryCode`.
- (void)fetchAndUpdateFieldRequirements {
  for (CountryItem* countryItem in _allCountries) {
    if ([_selectedCountryCode isEqualToString:countryItem.countryCode]) {
      countryItem.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
      countryItem.accessoryType = UITableViewCellAccessoryNone;
    }
  }

  autofill::AutofillCountry country(
      base::SysNSStringToUTF8(_selectedCountryCode),
      GetApplicationContext()->GetApplicationLocale());
  _line1Required = country.requires_line1();
  _cityRequired = country.requires_city();
  _stateRequired = country.requires_state();
  _zipRequired = country.requires_zip();
}

// Fetches the address fields for input and sets them to inputAddressFields.
- (void)fetchAndSetInputAddressFields {
  NSMutableArray<AutofillProfileAddressField*>* addressFields =
      [[NSMutableArray alloc] init];

  if (base::FeatureList::IsEnabled(
          kAutofillDynamicallyLoadsFieldsForAddressInput)) {
    i18n::addressinput::Localization localization;
    localization.SetGetter(l10n_util::GetStringUTF8);
    std::string best_language_tag_unused;
    std::string country_code = base::SysNSStringToUTF8(_selectedCountryCode);
    autofill::AutofillCountry country(country_code);
    std::vector<autofill::AutofillAddressUIComponent> ui_components =
        ConvertAddressUiComponents(
            BuildComponents(country_code, localization,
                            GetApplicationContext()->GetApplicationLocale(),
                            &best_language_tag_unused),
            country);
    ExtendAddressComponents(ui_components, country, localization,
                            /*include_literals=*/false);
    for (const auto& item : ui_components) {
      if (GroupTypeOfFieldType(item.field) !=
          autofill::FieldTypeGroup::kAddress) {
        continue;
      }

      AutofillProfileAddressField* field =
          [[AutofillProfileAddressField alloc] init];
      field.fieldType = [self fieldTypeToTypeName:item.field];
      field.fieldLabel = base::SysUTF8ToNSString(item.name);

      [addressFields addObject:field];
    }
  } else {
    for (size_t i = 0; i < std::size(kProfileFieldsToDisplay); ++i) {
      const AutofillProfileFieldDisplayInfo& fieldDisplayInfo =
          kProfileFieldsToDisplay[i];

      if (!FieldIsUsedInAddress(fieldDisplayInfo.autofillType,
                                _selectedCountryCode) ||
          GroupTypeOfFieldType(fieldDisplayInfo.autofillType) !=
              autofill::FieldTypeGroup::kAddress ||
          fieldDisplayInfo.autofillType == autofill::ADDRESS_HOME_COUNTRY) {
        // Country field is added separately in the VC.
        continue;
      }

      AutofillProfileAddressField* field =
          [[AutofillProfileAddressField alloc] init];
      field.fieldLabel =
          l10n_util::GetNSString(fieldDisplayInfo.displayStringID);
      field.fieldType =
          [self fieldTypeToTypeName:fieldDisplayInfo.autofillType];

      [addressFields addObject:field];
    }
  }

  _inputAddressFields = addressFields;
}

// Informs the consumer of the profile's data.
- (void)sendAutofillProfileDataToConsumer {
  int totalFieldCount =
      [self.inputAddressFields count] + kStaticFieldsTypes.size();
  NSMutableDictionary<NSString*, NSString*>* fieldValueMap =
      [[NSMutableDictionary alloc] initWithCapacity:totalFieldCount];
  for (AutofillProfileAddressField* field in self.inputAddressFields) {
    NSString* fieldValue = base::SysUTF16ToNSString(_autofillProfile->GetInfo(
        [self typeNameToFieldType:field.fieldType],
        GetApplicationContext() -> GetApplicationLocale()));
    fieldValueMap[field.fieldType] = fieldValue;
  }

  for (const auto& field_type : kStaticFieldsTypes) {
    NSString* fieldValue = base::SysUTF16ToNSString(_autofillProfile->GetInfo(
        field_type, GetApplicationContext()->GetApplicationLocale()));
    fieldValueMap[[self fieldTypeToTypeName:field_type]] = fieldValue;
  }

  [self.consumer setFieldValuesMap:fieldValueMap];
}

// Returns YES if `autofillProfile` is an account profile.
- (BOOL)isAccountProfile {
  return _autofillProfile->IsAccountProfile();
}

- (autofill::FieldType)typeNameToFieldType:(NSString*)autofillFieldType {
  return autofill::TypeNameToFieldType(
      base::SysNSStringToUTF8(autofillFieldType));
}

@end