// Copyright 2014 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/suggestions/suggestion.h"

#include <stddef.h>

#include <map>
#include <ostream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/feature.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_ostream_operators.h"
#include "base/strings/utf_string_conversions.h"
#include "build/buildflag.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_instance.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/integrators/at_memory/memory_data_type.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/common/dense_set.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "components/autofill/android/main_autofill_jni_headers/AutofillAiPayload_jni.h"
#include "components/autofill/android/main_autofill_jni_headers/AutofillProfilePayload_jni.h"
#include "components/autofill/android/main_autofill_jni_headers/PaymentsPayload_jni.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace autofill {

namespace {

std::string ConvertMinorTextToPrintableString(Suggestion suggestion) {
  std::vector<std::string> fragments;
  for (size_t index = 0; index < suggestion.minor_texts.size(); index++) {
    fragments.push_back(base::StrCat(
        {"minor_text_", base::NumberToString(index), ": \"",
         base::UTF16ToUTF8(suggestion.minor_texts[index].value), "\" ",
         (suggestion.minor_texts[index].is_primary ? "(Primary)\n"
                                                   : "(Not Primary)\n")}));
  }
  return base::StrCat(fragments);
}

std::string_view ConvertAcceptabilityToPrintableString(
    Suggestion::Acceptability acceptability) {
  switch (acceptability) {
    case Suggestion::Acceptability::kSelectableAndAcceptable:
      return "kSelectableAndAcceptable";
    case Suggestion::Acceptability::kSelectableButUnacceptable:
      return "kSelectableButUnacceptable";
    case Suggestion::Acceptability::kUnselectableAndUnacceptable:
      return "kUnselectableAndUnacceptable";
  }
  NOTREACHED();
}

std::string_view ConvertIconToPrintableString(Suggestion::Icon icon) {
  switch (icon) {
    // kNoIcon is kept at the top of the list.
    case Suggestion::Icon::kNoIcon:
      return "kNoIcon";

    // 1P Google services start
    case Suggestion::Icon::kGmail:
      return "kGmail";
    case Suggestion::Icon::kGoogleCalendar:
      return "kGoogleCalendar";
    case Suggestion::Icon::kGooglePhotos:
      return "kGooglePhotos";
    // 1P Google services end

    // Address profile icons start
    case Suggestion::Icon::kHome:
      return "kHome";
    case Suggestion::Icon::kWork:
      return "kWork";
    // Address profile icons end

    // Generic icons start
    case Suggestion::Icon::kAccount:
      return "kAccount";
    case Suggestion::Icon::kAndroidMessages:
      return "kAndroidMessages";
    case Suggestion::Icon::kClose:
      return "kClose";
    case Suggestion::Icon::kCode:
      return "kCode";
    case Suggestion::Icon::kDelete:
      return "kDelete";
    case Suggestion::Icon::kDevice:
      return "kDevice";
    case Suggestion::Icon::kEdit:
      return "kEdit";
    case Suggestion::Icon::kEmail:
      return "kEmail";
    case Suggestion::Icon::kError:
      return "kError";
    case Suggestion::Icon::kFlight:
      return "kFlight";
    case Suggestion::Icon::kFlightSpark:
      return "kFlightSpark";
    case Suggestion::Icon::kGlobe:
      return "kGlobe";
    case Suggestion::Icon::kGoogle:
      return "kGoogle";
    case Suggestion::Icon::kGoogleMonochrome:
      return "kGoogleMonochrome";
    case Suggestion::Icon::kGooglePasswordManager:
      return "kGooglePasswordManager";
    case Suggestion::Icon::kGooglePay:
      return "kGooglePay";
    case Suggestion::Icon::kGoogleWallet:
      return "kGoogleWallet";
    case Suggestion::Icon::kGoogleWalletMonochrome:
      return "kGoogleWalletMonochrome";
    case Suggestion::Icon::kIdCard:
      return "kIdCard";
    case Suggestion::Icon::kIdCard2:
      return "kIdCard2";
    case Suggestion::Icon::kIdCard2Spark:
      return "kIdCard2Spark";
    case Suggestion::Icon::kIdCardSpark:
      return "kIdCardSpark";
    case Suggestion::Icon::kKey:
      return "kKey";
    case Suggestion::Icon::kLocation:
      return "kLocation";
    case Suggestion::Icon::kLocationSpark:
      return "kLocationSpark";
    case Suggestion::Icon::kLoyalty:
      return "kLoyalty";
    case Suggestion::Icon::kMagic:
      return "kMagic";
    case Suggestion::Icon::kOfferTag:
      return "kOfferTag";
    case Suggestion::Icon::kOrder:
      return "kOrder";
    case Suggestion::Icon::kOrderSpark:
      return "kOrderSpark";
    case Suggestion::Icon::kPassport:
      return "kPassport";
    case Suggestion::Icon::kPassportSpark:
      return "kPassportSpark";
    case Suggestion::Icon::kPenSpark:
      return "kPenSpark";
    case Suggestion::Icon::kPersonCheck:
      return "kPersonCheck";
    case Suggestion::Icon::kQuestionMark:
      return "kQuestionMark";
    case Suggestion::Icon::kRecoveryPassword:
      return "kRecoveryPassword";
    case Suggestion::Icon::kSadTab:
      return "kSadTab";
    case Suggestion::Icon::kScanCreditCard:
      return "kScanCreditCard";
    case Suggestion::Icon::kSettings:
      return "kSettings";
    case Suggestion::Icon::kShipment:
      return "kShipment";
    case Suggestion::Icon::kShipmentSpark:
      return "kShipmentSpark";
    case Suggestion::Icon::kSpark:
      return "kSpark";
    case Suggestion::Icon::kTextSpark:
      return "kTextSpark";
    case Suggestion::Icon::kUndo:
      return "kUndo";
    case Suggestion::Icon::kVehicle:
      return "kVehicle";
    case Suggestion::Icon::kVehicleSpark:
      return "kVehicleSpark";
    // Generic icons end

    // Payment method icons start
    case Suggestion::Icon::kCardGeneric:
      return "kCardGeneric";
    case Suggestion::Icon::kCardGenericSpark:
      return "kCardGenericSpark";
    case Suggestion::Icon::kCardGenericVector:
      return "kCardGenericVector";
    case Suggestion::Icon::kCardAmericanExpress:
      return "kCardAmericanExpress";
    case Suggestion::Icon::kCardDiners:
      return "kCardDiners";
    case Suggestion::Icon::kCardDiscover:
      return "kCardDiscover";
    case Suggestion::Icon::kCardElo:
      return "kCardElo";
    case Suggestion::Icon::kCardJCB:
      return "kCardJCB";
    case Suggestion::Icon::kCardMasterCard:
      return "kCardMasterCard";
    case Suggestion::Icon::kCardMir:
      return "kCardMir";
    case Suggestion::Icon::kCardTroy:
      return "kCardTroy";
    case Suggestion::Icon::kCardUnionPay:
      return "kCardUnionPay";
    case Suggestion::Icon::kCardVerve:
      return "kCardVerve";
    case Suggestion::Icon::kCardVisa:
      return "kCardVisa";
    case Suggestion::Icon::kIban:
      return "kIban";
    case Suggestion::Icon::kBnplGeneric:
      return "kBnplGeneric";
    case Suggestion::Icon::kBnplAffirm:
      return "kBnplAffirm";
    case Suggestion::Icon::kBnplAfterpay:
      return "kBnplAfterpay";
    case Suggestion::Icon::kBnplKlarna:
      return "kBnplKlarna";
    case Suggestion::Icon::kBnplZip:
      return "kBnplZip";
    case Suggestion::Icon::kSaveAndFill:
      return "kSaveAndFill";
      // Payment method icons end
  }
  NOTREACHED();
}

}  // namespace

Suggestion::PasswordSuggestionDetails::PasswordSuggestionDetails() = default;
Suggestion::PasswordSuggestionDetails::PasswordSuggestionDetails(
    std::u16string_view username,
    std::u16string_view password,
    std::string_view signon_realm,
    std::u16string_view display_signon_realm,
    bool is_cross_domain)
    : username(username),
      password(password),
      signon_realm(signon_realm),
      display_signon_realm(display_signon_realm),
      is_cross_domain(is_cross_domain) {}

Suggestion::PasswordSuggestionDetails::PasswordSuggestionDetails(
    std::u16string_view username,
    std::u16string_view password,
    std::u16string_view backup_password,
    std::string_view signon_realm,
    bool is_cross_domain)
    : username(username),
      password(password),
      backup_password(backup_password),
      signon_realm(signon_realm),
      is_cross_domain(is_cross_domain) {}

Suggestion::PasswordSuggestionDetails::PasswordSuggestionDetails(
    const PasswordSuggestionDetails&) = default;
Suggestion::PasswordSuggestionDetails::PasswordSuggestionDetails(
    PasswordSuggestionDetails&&) = default;
Suggestion::PasswordSuggestionDetails&
Suggestion::PasswordSuggestionDetails::operator=(
    const PasswordSuggestionDetails&) = default;
Suggestion::PasswordSuggestionDetails&
Suggestion::PasswordSuggestionDetails::operator=(PasswordSuggestionDetails&&) =
    default;
Suggestion::PasswordSuggestionDetails::~PasswordSuggestionDetails() = default;

Suggestion::AutofillAiPayload::AutofillAiPayload() = default;

Suggestion::AutofillAiPayload::AutofillAiPayload(EntityInstance::EntityId guid,
                                                 bool requires_server_fetch)
    : guid(std::move(guid)), requires_server_fetch(requires_server_fetch) {}

Suggestion::AutofillAiPayload::AutofillAiPayload(const AutofillAiPayload&) =
    default;

Suggestion::AutofillAiPayload::AutofillAiPayload(AutofillAiPayload&&) = default;

Suggestion::AutofillAiPayload& Suggestion::AutofillAiPayload::operator=(
    const AutofillAiPayload&) = default;

Suggestion::AutofillAiPayload& Suggestion::AutofillAiPayload::operator=(
    AutofillAiPayload&&) = default;

Suggestion::AutofillAiPayload::~AutofillAiPayload() = default;

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject>
Suggestion::AutofillAiPayload::CreateJavaObject() const {
  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_AutofillAiPayload_Constructor(env, guid.value(),
                                            requires_server_fetch);
}
#endif  // BUILDFLAG(IS_ANDROID)

Suggestion::AutofillProfilePayload::AutofillProfilePayload() = default;
Suggestion::AutofillProfilePayload::AutofillProfilePayload(Guid guid)
    : guid(std::move(guid)) {}

Suggestion::AutofillProfilePayload::AutofillProfilePayload(
    const AutofillProfilePayload&) = default;

Suggestion::AutofillProfilePayload::AutofillProfilePayload(
    AutofillProfilePayload&&) = default;

Suggestion::AutofillProfilePayload&
Suggestion::AutofillProfilePayload::operator=(const AutofillProfilePayload&) =
    default;

Suggestion::AutofillProfilePayload&
Suggestion::AutofillProfilePayload::operator=(AutofillProfilePayload&&) =
    default;

Suggestion::AutofillProfilePayload::~AutofillProfilePayload() = default;

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject>
Suggestion::AutofillProfilePayload::CreateJavaObject() const {
  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_AutofillProfilePayload_Constructor(env, guid.value());
}
#endif  // BUILDFLAG(IS_ANDROID)

Suggestion::IdentityCredentialPayload::IdentityCredentialPayload() = default;
Suggestion::IdentityCredentialPayload::IdentityCredentialPayload(
    GURL config_url,
    std::string account_id,
    const std::map<FieldType, std::u16string>& fields)
    : config_url(std::move(config_url)),
      account_id(std::move(account_id)),
      fields(fields) {}

Suggestion::IdentityCredentialPayload::IdentityCredentialPayload(
    const IdentityCredentialPayload&) = default;

Suggestion::IdentityCredentialPayload::IdentityCredentialPayload(
    IdentityCredentialPayload&&) = default;

Suggestion::IdentityCredentialPayload&
Suggestion::IdentityCredentialPayload::operator=(
    const IdentityCredentialPayload&) = default;

Suggestion::IdentityCredentialPayload&
Suggestion::IdentityCredentialPayload::operator=(IdentityCredentialPayload&&) =
    default;

Suggestion::IdentityCredentialPayload::~IdentityCredentialPayload() = default;

Suggestion::AtMemoryPayload::AtMemoryPayload() = default;

Suggestion::AtMemoryPayload::AtMemoryPayload(std::u16string value,
                                             MemoryDataType memory_data_type)
    : value(std::move(value)), memory_data_type(memory_data_type) {}

Suggestion::AtMemoryPayload::AtMemoryPayload(const AtMemoryPayload&) = default;

Suggestion::AtMemoryPayload::AtMemoryPayload(AtMemoryPayload&&) = default;

Suggestion::AtMemoryPayload& Suggestion::AtMemoryPayload::operator=(
    const AtMemoryPayload&) = default;

Suggestion::AtMemoryPayload& Suggestion::AtMemoryPayload::operator=(
    AtMemoryPayload&&) = default;

Suggestion::AtMemoryPayload::~AtMemoryPayload() = default;

Suggestion::OpenGeminiPayload::OpenGeminiPayload() = default;
Suggestion::OpenGeminiPayload::OpenGeminiPayload(std::u16string prompt)
    : prompt(std::move(prompt)) {}
Suggestion::OpenGeminiPayload::OpenGeminiPayload(const OpenGeminiPayload&) =
    default;
Suggestion::OpenGeminiPayload::OpenGeminiPayload(OpenGeminiPayload&&) = default;
Suggestion::OpenGeminiPayload& Suggestion::OpenGeminiPayload::operator=(
    const OpenGeminiPayload&) = default;
Suggestion::OpenGeminiPayload& Suggestion::OpenGeminiPayload::operator=(
    OpenGeminiPayload&&) = default;
Suggestion::OpenGeminiPayload::~OpenGeminiPayload() = default;

Suggestion::PaymentsPayload::PaymentsPayload() = default;

Suggestion::PaymentsPayload::PaymentsPayload(
    std::u16string main_text_content_description,
    bool should_display_terms_available,
    Guid guid)
    : main_text_content_description(main_text_content_description),
      should_display_terms_available(should_display_terms_available),
      guid(std::move(guid)) {}

Suggestion::PaymentsPayload::PaymentsPayload(const PaymentsPayload&) = default;

Suggestion::PaymentsPayload::PaymentsPayload(PaymentsPayload&&) = default;

Suggestion::PaymentsPayload& Suggestion::PaymentsPayload::operator=(
    const PaymentsPayload&) = default;

Suggestion::PaymentsPayload& Suggestion::PaymentsPayload::operator=(
    PaymentsPayload&&) = default;

Suggestion::PaymentsPayload::~PaymentsPayload() = default;

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject>
Suggestion::PaymentsPayload::CreateJavaObject() const {
  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_PaymentsPayload_Constructor(env, main_text_content_description,
                                          should_display_terms_available,
                                          guid.value());
}
#endif  // BUILDFLAG(IS_ANDROID)

Suggestion::IPHMetadata::IPHMetadata() = default;

Suggestion::IPHMetadata::IPHMetadata(const base::Feature* feature,
                                     std::vector<std::u16string> iph_params)
    : feature(feature), iph_params(std::move(iph_params)) {}

Suggestion::IPHMetadata::IPHMetadata(const IPHMetadata& other) = default;
Suggestion::IPHMetadata::IPHMetadata(IPHMetadata&& other) = default;

Suggestion::IPHMetadata& Suggestion::IPHMetadata::operator=(
    const IPHMetadata& other) = default;
Suggestion::IPHMetadata& Suggestion::IPHMetadata::operator=(
    IPHMetadata&& other) = default;

Suggestion::IPHMetadata::~IPHMetadata() = default;

Suggestion::Text::Text() = default;

Suggestion::Text::Text(std::u16string value,
                       IsPrimary is_primary,
                       ShouldTruncate should_truncate)
    : value(value), is_primary(is_primary), should_truncate(should_truncate) {}

Suggestion::Text::Text(const Text& other) = default;
Suggestion::Text::Text(Text&& other) = default;

Suggestion::Text& Suggestion::Text::operator=(const Text& other) = default;
Suggestion::Text& Suggestion::Text::operator=(Text&& other) = default;

Suggestion::Text::~Text() = default;

Suggestion::Suggestion(SuggestionType type) : type(type) {}

Suggestion::Suggestion(std::u16string main_text, SuggestionType type)
    : type(type), main_text(std::move(main_text), Text::IsPrimary(true)) {}

Suggestion::Suggestion(std::u16string main_text,
                       std::u16string label,
                       Icon icon,
                       SuggestionType type)
    : type(type),
      main_text(std::move(main_text), Text::IsPrimary(true)),
      icon(icon) {
  if (!label.empty()) {
    labels = {{Text(std::move(label))}};
  }
}

Suggestion::Suggestion(std::u16string_view main_text,
                       std::vector<std::vector<Text>> labels,
                       Icon icon,
                       SuggestionType type)
    : type(type),
      main_text(std::u16string(main_text), Text::IsPrimary(true)),
      labels(std::move(labels)),
      icon(icon) {}

Suggestion::Suggestion(std::u16string_view main_text,
                       base::span<const std::u16string> minor_text_labels,
                       std::u16string_view label,
                       Icon icon,
                       SuggestionType type)
    : type(type),
      main_text(std::u16string(main_text), Text::IsPrimary(true)),
      minor_texts(base::ToVector(minor_text_labels,
                                 [](std::u16string_view minor_text) {
                                   return Text(
                                       std::u16string(minor_text),
                                       Suggestion::Text::IsPrimary(true));
                                 })),
      icon(icon) {
  if (!label.empty()) {
    labels = {{Text(std::u16string(label))}};
  }
}

Suggestion::Suggestion(const Suggestion& other) = default;
Suggestion::Suggestion(Suggestion&& other) = default;

Suggestion& Suggestion::operator=(const Suggestion& other) = default;
Suggestion& Suggestion::operator=(Suggestion&& other) = default;

Suggestion::~Suggestion() = default;

bool Suggestion::IsAcceptable() const {
  using enum SuggestionType;
  // LINT.IfChange(UnacceptableSuggestionTypes)
  static constexpr auto kUnacceptableItemIds =
      DenseSet({kSeparator, kInsecureContextPaymentDisabledMessage, kTitle,
                kAtMemorySourceAttribution});
  // LINT.ThenChange(/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java:UnacceptableSuggestionTypes)
  if (kUnacceptableItemIds.contains(type)) {
    return false;
  }
  switch (acceptability) {
    case Acceptability::kSelectableAndAcceptable:
      return true;
    case Acceptability::kSelectableButUnacceptable:
    case Acceptability::kUnselectableAndUnacceptable:
      return false;
  }
  NOTREACHED();
}

bool Suggestion::IsSelectable() const {
  switch (acceptability) {
    case Acceptability::kSelectableAndAcceptable:
    case Acceptability::kSelectableButUnacceptable:
      return true;
    case Acceptability::kUnselectableAndUnacceptable:
      return false;
  }
  NOTREACHED();
}

void PrintTo(const Suggestion& suggestion, std::ostream* os) {
  *os << std::endl
      << "Suggestion (type:" << suggestion.type << ", main_text:\""
      << suggestion.main_text.value << "\""
      << (suggestion.main_text.is_primary ? "(Primary)" : "(Not Primary)")
      << ConvertMinorTextToPrintableString(suggestion)
      << ", additional_label: \"" << suggestion.additional_label << "\""
      << ", acceptability: \""
      << ConvertAcceptabilityToPrintableString(suggestion.acceptability) << "\""
      << ", icon:" << ConvertIconToPrintableString(suggestion.icon)
      << ", trailing_icon:"
      << ConvertIconToPrintableString(suggestion.trailing_icon) << ")";
}

}  // namespace autofill

#if BUILDFLAG(IS_ANDROID)
DEFINE_JNI(AutofillProfilePayload)
DEFINE_JNI(PaymentsPayload)
#endif
