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

#include "components/autofill/core/browser/data_model/addresses/phone_number.h"

#include <limits.h>
#include <stddef.h>

#include <optional>
#include <string>
#include <string_view>

#include "base/check.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_structured_address_component.h"
#include "components/autofill/core/browser/data_model/data_model_utils.h"
#include "components/autofill/core/browser/data_model/form_group.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/autofill_country.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "third_party/abseil-cpp/absl/strings/ascii.h"

namespace autofill {

namespace {

// Returns the region code for this phone number, which is an ISO 3166 2-letter
// country code.  The returned value is based on the `profile`; if the `profile`
// does not have a country code associated with it, falls back to the country
// code corresponding to the `app_locale`.
std::string GetRegion(const AutofillProfile& profile,
                      std::string_view app_locale) {
  std::u16string country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY);
  if (!country_code.empty())
    return base::UTF16ToASCII(country_code);

  return AutofillCountry::CountryCodeForLocale(app_locale);
}

}  // namespace

PhoneNumber::PhoneNumber(const AutofillProfile* profile) : profile_(profile) {}

PhoneNumber::PhoneNumber(const PhoneNumber& number) : profile_(nullptr) {
  *this = number;
}

PhoneNumber::~PhoneNumber() = default;

PhoneNumber& PhoneNumber::operator=(const PhoneNumber& number) {
  if (this == &number)
    return *this;

  number_ = number.number_;
  profile_ = number.profile_;
  cached_parsed_phone_ = number.cached_parsed_phone_;
  return *this;
}

bool PhoneNumber::operator==(const PhoneNumber& other) const {
  if (this == &other)
    return true;

  return number_ == other.number_ && profile_ == other.profile_;
}

FieldTypeSet PhoneNumber::GetSupportedTypes() const {
  static constexpr FieldTypeSet supported_types{
      PHONE_HOME_WHOLE_NUMBER,
      PHONE_HOME_NUMBER,
      PHONE_HOME_NUMBER_PREFIX,
      PHONE_HOME_NUMBER_SUFFIX,
      PHONE_HOME_CITY_CODE,
      PHONE_HOME_CITY_AND_NUMBER,
      PHONE_HOME_COUNTRY_CODE,
      PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX,
      PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX};
  return supported_types;
}

std::u16string PhoneNumber::GetRawInfo(FieldType type) const {
  DCHECK_EQ(FieldTypeGroup::kPhone, GroupTypeOfFieldType(type));
  if (type == PHONE_HOME_WHOLE_NUMBER)
    return number_;

  // Only the whole number is available as raw data.  All of the other types are
  // parsed from this raw info, and parsing requires knowledge of the phone
  // number's region, which is only available via GetInfo().
  return std::u16string();
}

void PhoneNumber::SetRawInfoWithVerificationStatus(FieldType type,
                                                   std::u16string_view value,
                                                   VerificationStatus status) {
  DCHECK_EQ(FieldTypeGroup::kPhone, GroupTypeOfFieldType(type));
  if (type != PHONE_HOME_WHOLE_NUMBER) {
    // Only full phone numbers should be set directly. The browser is
    // intentionally caused to crash to prevent all users from setting raw info
    // to the non-storable fields.
    NOTREACHED();
  }

  number_ = value;

  // Invalidate the cached number.
  cached_parsed_phone_ = i18n::PhoneObject();
}

void PhoneNumber::GetMatchingTypes(std::u16string_view text,
                                   std::string_view app_locale,
                                   FieldTypeSet* matching_types) const {
  // Strip the common phone number non numerical characters before calling the
  // base matching type function. For example, the `text` "(514) 121-1523"
  // would become the stripped text "5141211523". Since the base matching
  // function only does simple canonicalization to match against the stored
  // data, some domain specific cases will be covered below.
  std::u16string stripped_text;
  base::RemoveChars(text, u" .()-", &stripped_text);
  FormGroup::GetMatchingTypes(stripped_text, app_locale, matching_types);

  // TODO(crbug.com/41236729): Investigate the use of PhoneNumberUtil when
  // matching phone numbers for upload.
  // If there is not already a match for PHONE_HOME_WHOLE_NUMBER, normalize the
  // `text` based on the app_locale before comparing it to the whole number. For
  // example, the France number "33 2 49 19 70 70" would be normalized to
  // "+33249197070" whereas the US number "+1 (234) 567-8901" would be
  // normalized to "12345678901".
  if (!matching_types->contains(PHONE_HOME_WHOLE_NUMBER)) {
    std::u16string whole_number = GetInfo(PHONE_HOME_WHOLE_NUMBER, app_locale);
    if (!whole_number.empty()) {
      std::u16string normalized_number =
          i18n::NormalizePhoneNumber(text, GetRegion(*profile_, app_locale));
      if (normalized_number == whole_number)
        matching_types->insert(PHONE_HOME_WHOLE_NUMBER);
    }
  }

  // `PHONE_HOME_COUNTRY_CODE` is added to the set of the `matching_types` when
  // the digits extracted from the `stripped_text` match the `country_code`.
  std::u16string candidate =
      data_util::FindPossiblePhoneCountryCode(stripped_text);
  std::u16string country_code = GetInfo(PHONE_HOME_COUNTRY_CODE, app_locale);
  if (candidate.size() > 0 && candidate == country_code)
    matching_types->insert(PHONE_HOME_COUNTRY_CODE);

  // The following pairs of types coincide in countries without trunk prefixes:
  // - PHONE_HOME_CITY_CODE, PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX
  // - PHONE_HOME_CITY_AND_NUMBER,
  //   PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX
  // We explicitly keep both matches, as the type prediction doesn't make a
  // difference for these countries. Votes from other countries can then tip
  // the counts to the right type.
  //
  // When the phone number is stored without a country code,
  // PHONE_HOME_WHOLE_NUMBER and PHONE_HOME_CITY_AND_NUMBER coincide (and
  // potentially PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX too, as
  // indicated above).
  // Since PHONE_HOME_WHOLE_NUMBER is meant to represent an international
  // number, it is not voted in this case.
  if (auto it = matching_types->find(PHONE_HOME_WHOLE_NUMBER);
      it != matching_types->end() &&
      matching_types->contains_any(
          {PHONE_HOME_CITY_AND_NUMBER,
           PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX})) {
    matching_types->erase(it);
  }
}

// Normalize phones if `type` is a whole number:
//   (650)2345678 -> 6502345678
//   1-800-FLOWERS -> 18003569377
// If the phone cannot be normalized, returns the stored value verbatim.
std::u16string PhoneNumber::GetInfo(const AutofillType& autofill_type,
                                    std::string_view app_locale) const {
  FieldType type = autofill_type.GetAddressType();
  UpdateCacheIfNeeded(app_locale);

  // When the phone number autofill has stored cannot be normalized, it
  // responds to queries for complete numbers with whatever the raw stored value
  // is, and simply return empty string for any queries for phone components.
  if (!cached_parsed_phone_.IsValidNumber()) {
    if (type == PHONE_HOME_WHOLE_NUMBER || type == PHONE_HOME_CITY_AND_NUMBER) {
      return cached_parsed_phone_.GetWholeNumber();
    }
    return std::u16string();
  }

  auto GetTrunkPrefix = [&] {
    const std::u16string national_number =
        GetInfo(PHONE_HOME_CITY_AND_NUMBER, app_locale);
    // Everything before the city code in the nationally formatted number.
    return national_number.substr(
        0, national_number.find(cached_parsed_phone_.city_code()));
  };

  switch (type) {
    case PHONE_HOME_WHOLE_NUMBER: {
      std::u16string whole_number_ = cached_parsed_phone_.GetWholeNumber();

      // Drop the leading '+' for US/CA numbers as some sites can't handle the
      // "+", and in these regions dialing "+1..." is the same as dialing
      // "1...".
      // TODO(crbug.com/40311205): Investigate whether the leading "+" is
      // desirable in other regions. Closed bug crbug.com/98911 contains
      // additional context.
      std::string country_code = *profile_->GetAddressCountryCode();
      const std::string& region_code = cached_parsed_phone_.region();
      if ((country_code == "US" || country_code == "CA") &&
          (region_code == "US" || region_code == "CA") &&
          whole_number_[0] == u'+') {
        whole_number_.erase(whole_number_.begin());
      }
      return whole_number_;
    }

    case PHONE_HOME_NUMBER:
      return cached_parsed_phone_.number();

    case PHONE_HOME_NUMBER_PREFIX: {
      const std::u16string number = GetInfo(PHONE_HOME_NUMBER, app_locale);
      const std::u16string number_suffix =
          GetInfo(PHONE_HOME_NUMBER_SUFFIX, app_locale);
      DCHECK(number.size() >= number_suffix.size());
      // As PHONE_HOME_NUMBER = PHONE_HOME_NUMBER_PREFIX +
      // PHONE_HOME_NUMBER_SUFFIX, extract the appropriate prefix from `number`.
      return number.substr(0, number.size() - number_suffix.size());
    }

    case PHONE_HOME_NUMBER_SUFFIX: {
      const std::u16string number = GetInfo(PHONE_HOME_NUMBER, app_locale);
      // Libphonenumber doesn't provide functionality to split PHONE_HOME_NUMBER
      // further, and the HTML standard doesn't specify which suffix
      // autocomplete="tel-local-suffix" corresponds to. In all countries using
      // this format that we are aware of (see unit tests), the suffix consists
      // of the last 4 digits, while the length of the prefix varies.
      constexpr size_t kHomePhoneNumberSuffixLength = 4;
      return number.size() >= kHomePhoneNumberSuffixLength
                 ? number.substr(number.size() - kHomePhoneNumberSuffixLength)
                 : number;
    }

    case PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX:
      return GetTrunkPrefix() + cached_parsed_phone_.city_code();

    case PHONE_HOME_CITY_CODE:
      return cached_parsed_phone_.city_code();

    case PHONE_HOME_COUNTRY_CODE:
      return cached_parsed_phone_.country_code();

    case PHONE_HOME_CITY_AND_NUMBER: {
      // Just concatenating city code and phone number is insufficient because
      // a number of non-US countries (e.g. Germany and France) use a leading 0
      // to indicate that the next digits represent a city code.
      std::u16string national_number =
          cached_parsed_phone_.GetNationallyFormattedNumber();
      // GetNationallyFormattedNumber optimizes for screen display, e.g. it
      // shows a US number as (888) 123-1234. The following retains only the
      // digits.
      std::erase_if(national_number, [](char16_t c) {
        return c > UCHAR_MAX ||
               !absl::ascii_isdigit(static_cast<unsigned char>(c));
      });
      return national_number;
    }

    case PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX: {
      // Strip the trunk prefix from the nationally formatted number.
      const std::u16string national_number =
          GetInfo(PHONE_HOME_CITY_AND_NUMBER, app_locale);
      const std::size_t trunk_prefix_len = GetTrunkPrefix().length();
      DCHECK(trunk_prefix_len <= national_number.length());
      return national_number.substr(trunk_prefix_len);
    }

    case PHONE_HOME_EXTENSION:
      // Autofill doesn't support filling extensions, but some basic local
      // heuristics classify them.
      return std::u16string();

    default:
      NOTREACHED();
  }
}

bool PhoneNumber::SetInfoWithVerificationStatus(const AutofillType& type,
                                                std::u16string_view value,
                                                std::string_view app_locale,
                                                VerificationStatus status) {
  SetRawInfoWithVerificationStatus(type.GetAddressType(), value, status);

  if (number_.empty()) {
    return true;
  }

  // `SetRawInfoWithVerificationStatus()` invalidated `cached_parsed_phone_` and
  // calling `UpdateCacheIfNeeded()` will thus try parsing the `number_` here.
  UpdateCacheIfNeeded(app_locale);
  // If the number invalid, setting fails and `GetRawInfo()` and `GetInfo()`
  // should return an empty string. Clear both representations of the number.
  if (!cached_parsed_phone_.IsValidNumber()) {
    number_.clear();
    cached_parsed_phone_ = i18n::PhoneObject();
    return false;
  }
  number_ = cached_parsed_phone_.GetFormattedNumber();
  return true;
}

VerificationStatus PhoneNumber::GetVerificationStatus(FieldType type) const {
  return VerificationStatus::kNoStatus;
}

void PhoneNumber::UpdateCacheIfNeeded(std::string_view app_locale) const {
  std::string region = GetRegion(*profile_, app_locale);
  if (!number_.empty() && cached_parsed_phone_.region() != region) {
    // To enable filling of country calling codes for nationally formatted
    // numbers, infer it from the `profile_`'s country information while parsing
    // the number.
    cached_parsed_phone_ = i18n::PhoneObject(
        number_, region,
        /*infer_country_code=*/profile_->HasInfo(ADDRESS_HOME_COUNTRY));
  }
}

PhoneNumber::PhoneCombineHelper
PhoneNumber::PhoneCombineHelper::FromObservedValues(
    const base::flat_map<FieldType, std::u16string>& observed_values) {
  PhoneCombineHelper combined_phone;
  for (const auto& [type, value] : observed_values) {
    if (GroupTypeOfFieldType(type) == FieldTypeGroup::kPhone) {
      combined_phone.SetInfo(type, value);
    }
  }
  return combined_phone;
}

PhoneNumber::PhoneCombineHelper::PhoneCombineHelper() = default;
PhoneNumber::PhoneCombineHelper::PhoneCombineHelper(const PhoneCombineHelper&) =
    default;
PhoneNumber::PhoneCombineHelper::PhoneCombineHelper(PhoneCombineHelper&&) =
    default;
PhoneNumber::PhoneCombineHelper& PhoneNumber::PhoneCombineHelper::operator=(
    const PhoneCombineHelper&) = default;
PhoneNumber::PhoneCombineHelper& PhoneNumber::PhoneCombineHelper::operator=(
    PhoneCombineHelper&&) = default;
PhoneNumber::PhoneCombineHelper::~PhoneCombineHelper() = default;

void PhoneNumber::PhoneCombineHelper::SetInfo(FieldType field_type,
                                              std::u16string_view value) {
  CHECK_EQ(GroupTypeOfFieldType(field_type), FieldTypeGroup::kPhone);
  switch (field_type) {
    case PHONE_HOME_COUNTRY_CODE:
      country_ = value;
      return;
    case PHONE_HOME_CITY_CODE:
    case PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX:
      city_ = value;
      return;
    case PHONE_HOME_CITY_AND_NUMBER:
    case PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX:
      phone_ = value;
      return;
    case PHONE_HOME_WHOLE_NUMBER:
      whole_number_ = value;
      return;
    case PHONE_HOME_NUMBER:
    case PHONE_HOME_NUMBER_PREFIX:
      phone_ = value;
      return;
    case PHONE_HOME_NUMBER_SUFFIX:
      phone_.append(value);
      return;
    case PHONE_HOME_EXTENSION:
      // PHONE_HOME_EXTENSION is not stored or filled, but it's still classified
      // to prevent misclassifying such fields as something else.
      return;
    default:
      NOTREACHED();
  }
}

std::optional<std::u16string> PhoneNumber::PhoneCombineHelper::ParseNumber(
    const std::string& region) const {
  if (IsEmpty()) {
    return std::nullopt;
  }

  if (!whole_number_.empty()) {
    return whole_number_;
  }

  if (std::u16string result; i18n::ConstructPhoneNumber(
          base::StrCat({country_, city_, phone_}), region, &result)) {
    return result;
  }
  return std::nullopt;
}

std::optional<std::u16string> PhoneNumber::PhoneCombineHelper::GetRegionCode()
    const {
  auto get_region =
      [](std::u16string_view number) -> std::optional<std::u16string> {
    constexpr std::string_view kUnknownRegion("ZZ");
    const std::string region =
        i18n::PhoneObject(number, std::string(kUnknownRegion),
                          /*infer_country_code=*/false)
            .region();
    return region.empty() ? std::nullopt
                          : std::optional(base::UTF8ToUTF16(region));
  };

  // Prefer using the whole phone number over separate number components if
  // available and try to determine its associated region. If no whole number is
  // available, fall back to a combination of the components. This follows the
  // logic of `PhoneCombineHelper::ParseNumber()` which should return a phone
  // number that matches the region returned by this function.
  if (!whole_number_.empty()) {
    return get_region(whole_number_);
  }
  if (const std::u16string combined_number =
          base::StrCat({country_, city_, phone_});
      !combined_number.empty()) {
    return get_region(combined_number);
  }
  return std::nullopt;
}

// static
bool PhoneNumber::ImportPhoneNumberToProfile(
    const PhoneNumber::PhoneCombineHelper& combined_phone,
    std::string_view app_locale,
    AutofillProfile& profile) {
  // If the phone number only consists of a single component, the
  // `PhoneCombineHelper` won't try to parse it. This happens during `SetInfo()`
  // in this case.
  if (std::optional<std::u16string> constructed_number =
          combined_phone.ParseNumber(GetRegion(profile, app_locale))) {
    return profile.SetInfoWithVerificationStatus(
        PHONE_HOME_WHOLE_NUMBER, *constructed_number, app_locale,
        VerificationStatus::kObserved);
  }
  return false;
}

bool PhoneNumber::PhoneCombineHelper::IsEmpty() const {
  return phone_.empty() && whole_number_.empty();
}

}  // namespace autofill
