// 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.

#include "chrome/browser/keyboard_accessory/android/manual_filling_controller_impl.h"

#include <inttypes.h>

#include <numeric>
#include <optional>
#include <utility>

#include "base/containers/fixed_flat_set.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/autofill/manual_filling_view_interface.h"
#include "chrome/browser/keyboard_accessory/android/accessory_sheet_data.h"
#include "chrome/browser/keyboard_accessory/android/accessory_sheet_enums.h"
#include "chrome/browser/keyboard_accessory/android/address_accessory_controller.h"
#include "chrome/browser/keyboard_accessory/android/at_memory_accessory_controller.h"
#include "chrome/browser/keyboard_accessory/android/password_accessory_controller.h"
#include "chrome/browser/keyboard_accessory/android/payment_method_accessory_controller.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "components/autofill/content/browser/content_autofill_client.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/common/autofill_features.h"
#include "content/public/browser/web_contents.h"

using autofill::AccessoryAction;
using autofill::AccessorySheetData;
using autofill::AccessoryTabType;
using autofill::AddressAccessoryController;
using autofill::PaymentMethodAccessoryController;
using autofill::mojom::FocusedFieldType;

using FillingSource = ManualFillingController::FillingSource;

namespace {

constexpr auto kAllowedFillingSources = base::MakeFixedFlatSet<FillingSource>(
    {FillingSource::PASSWORD_FALLBACKS, FillingSource::CREDIT_CARD_FALLBACKS,
     FillingSource::ADDRESS_FALLBACKS, FillingSource::AT_MEMORY});

constexpr char
    kUmaAccessoryActionSelectedForNonCredentialFieldWithoutSuggestions[] =
        "KeyboardAccessory."
        "AccessoryActionSelectedForNonCredentialFieldWithoutSuggestions";

}  // namespace

// static
base::WeakPtr<ManualFillingController> ManualFillingController::GetOrCreate(
    content::WebContents* contents) {
  ManualFillingControllerImpl* mf_controller =
      ManualFillingControllerImpl::FromWebContents(contents);
  if (!mf_controller) {
    ManualFillingControllerImpl::CreateForWebContents(contents);
    mf_controller = ManualFillingControllerImpl::FromWebContents(contents);
    mf_controller->Initialize();
  }
  return mf_controller->AsWeakPtr();
}

// static
base::WeakPtr<ManualFillingController> ManualFillingController::Get(
    content::WebContents* contents) {
  ManualFillingControllerImpl* mf_controller =
      ManualFillingControllerImpl::FromWebContents(contents);
  return mf_controller ? mf_controller->AsWeakPtr() : nullptr;
}

// static
void ManualFillingControllerImpl::CreateForWebContentsForTesting(
    content::WebContents* web_contents,
    base::WeakPtr<PasswordAccessoryController> pwd_controller,
    base::WeakPtr<AddressAccessoryController> address_controller,
    base::WeakPtr<PaymentMethodAccessoryController> payment_method_controller,
    base::WeakPtr<AtMemoryAccessoryController> at_memory_controller,
    std::unique_ptr<ManualFillingViewInterface> view) {
  DCHECK(web_contents) << "Need valid WebContents to attach controller to!";
  DCHECK(!FromWebContents(web_contents)) << "Controller already attached!";
  DCHECK(pwd_controller);
  DCHECK(address_controller);
  DCHECK(payment_method_controller);
  DCHECK(at_memory_controller);
  DCHECK(view);

  web_contents->SetUserData(
      UserDataKey(),
      // Using `new` to access a non-public constructor.
      base::WrapUnique(new ManualFillingControllerImpl(
          web_contents, std::move(pwd_controller),
          std::move(address_controller), std::move(payment_method_controller),
          std::move(at_memory_controller), std::move(view))));

  FromWebContents(web_contents)->Initialize();
}

void ManualFillingControllerImpl::OnAccessoryActionAvailabilityChanged(
    ShouldShowAction shouldShowAction,
    autofill::AccessoryAction action) {
  DCHECK(view_);
  view_->OnAccessoryActionAvailabilityChanged(shouldShowAction, action);
}

void ManualFillingControllerImpl::NotifyFocusedInputChanged(
    autofill::FieldRendererId focused_field_id,
    autofill::mojom::FocusedFieldType focused_field_type) {
  TRACE_EVENT0("passwords",
               "ManualFillingControllerImpl::NotifyFocusedInputChanged");
  autofill::LocalFrameToken frame_token;
  if (content::RenderFrameHost* rfh = GetWebContents().GetFocusedFrame()) {
    frame_token = autofill::LocalFrameToken(rfh->GetFrameToken().value());
  }
  last_focused_field_id_ = {frame_token, focused_field_id};
  last_focused_field_type_ = focused_field_type;

  // Ensure warnings and filling state is updated according to focused field.
  if (payment_method_controller_) {
    payment_method_controller_->RefreshSuggestions();
  }

  // Whenever the focus changes, reset the accessory.
  if (ShouldShowAccessoryForLastFocusedFieldType()) {
    view_->SwapSheetWithKeyboard();
  } else {
    view_->CloseAccessorySheet();
  }

  UpdateVisibility();
}

autofill::FieldGlobalId ManualFillingControllerImpl::GetLastFocusedFieldId()
    const {
  return last_focused_field_id_;
}

void ManualFillingControllerImpl::ShowAccessorySheetTab(
    const autofill::AccessoryTabType& tab_type) {
  if (tab_type == autofill::AccessoryTabType::CREDIT_CARDS) {
    payment_method_controller_->RefreshSuggestions();
  } else {
    NOTIMPLEMENTED()
        << "ShowAccessorySheetTab does not support the given TabType yet "
        << tab_type;
  }
  view_->ShowAccessorySheetTab(tab_type);
}

void ManualFillingControllerImpl::UpdateSourceAvailability(
    FillingSource source,
    bool has_suggestions) {
  if (has_suggestions == available_sources_.contains(source)) {
    return;
  }

  if (has_suggestions) {
    available_sources_.insert(source);
    UpdateVisibility();
    return;
  }

  available_sources_.erase(source);
  if (!ShouldShowAccessoryForLastFocusedFieldType()) {
    UpdateVisibility();
  }
}

void ManualFillingControllerImpl::Hide() {
  view_->Hide();
}

void ManualFillingControllerImpl::OnFillingTriggered(
    AccessoryTabType type,
    const autofill::AccessorySheetField& selection) {
  bool is_non_credential_field_without_suggestions =
      last_focused_field_type_ != FocusedFieldType::kFillableUsernameField &&
      last_focused_field_type_ != FocusedFieldType::kFillablePasswordField &&
      !available_sources_.contains(FillingSource::AUTOFILL);
  UMA_HISTOGRAM_BOOLEAN(
      kUmaAccessoryActionSelectedForNonCredentialFieldWithoutSuggestions,
      is_non_credential_field_without_suggestions);
  AccessoryController* controller = GetControllerForTabType(type);
  if (!controller) {
    return;  // Controller not available anymore.
  }
  controller->OnFillingTriggered(last_focused_field_id_, selection);
  view_->SwapSheetWithKeyboard();  // Soft-close the keyboard.
}

void ManualFillingControllerImpl::OnPasskeySelected(
    AccessoryTabType type,
    const std::vector<uint8_t>& passkey_id) {
  AccessoryController* controller = GetControllerForTabType(type);
  if (!controller) {
    return;  // Controller not available anymore.
  }
  controller->OnPasskeySelected(passkey_id);
  view_->Hide();  // Close the sheet since the passkey sheet will be triggered.
}

void ManualFillingControllerImpl::OnOptionSelected(
    AccessoryAction selected_action) const {
  bool is_non_credential_field_without_suggestions =
      last_focused_field_type_ != FocusedFieldType::kFillableUsernameField &&
      last_focused_field_type_ != FocusedFieldType::kFillablePasswordField &&
      !available_sources_.contains(FillingSource::AUTOFILL);
  UMA_HISTOGRAM_BOOLEAN(
      kUmaAccessoryActionSelectedForNonCredentialFieldWithoutSuggestions,
      is_non_credential_field_without_suggestions);
  UMA_HISTOGRAM_ENUMERATION("KeyboardAccessory.AccessoryActionSelected2",
                            selected_action, AccessoryAction::COUNT);
  AccessoryController* controller = GetControllerForAction(selected_action);
  if (!controller) {
    return;  // Controller not available anymore.
  }
  controller->OnOptionSelected(selected_action);
}

void ManualFillingControllerImpl::OnToggleChanged(
    AccessoryAction toggled_action,
    bool enabled) const {
  AccessoryController* controller = GetControllerForAction(toggled_action);
  if (!controller) {
    return;  // Controller not available anymore.
  }
  controller->OnToggleChanged(toggled_action, enabled);
}

void ManualFillingControllerImpl::RequestAccessorySheet(
    autofill::AccessoryTabType tab_type,
    base::OnceCallback<void(autofill::AccessorySheetData)> callback) {
  // TODO(crbug.com/40165275): Consider to execute this async to reduce jank.
  std::optional<AccessorySheetData> sheet =
      GetControllerForTabType(tab_type)->GetSheetData();
  // After they were loaded, all currently existing sheet types always return a
  // value and will always result in a called callback.
  // The only case where they are not available is before their first load (so
  // if a user entered a tab but didn't focus any fields yet). In that case, the
  // update is unnecessary since the first focus will push the correct sheet.
  // TODO(crbug.com/40165275): Consider sending a null or default sheet to cover
  // future cases where we can't rely on a sheet always being available.
  if (sheet.has_value()) {
    std::move(callback).Run(sheet.value());
  }
}

gfx::NativeView ManualFillingControllerImpl::container_view() const {
  // While a const_cast is not ideal. The Autofill API uses const in various
  // spots and the content public API doesn't have const accessors. So the const
  // cast is the lesser of two evils.
  return const_cast<content::WebContents&>(GetWebContents()).GetNativeView();
}

// Returns a weak pointer for this object.
base::WeakPtr<ManualFillingController>
ManualFillingControllerImpl::AsWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void ManualFillingControllerImpl::Initialize() {
  DCHECK(FromWebContents(&GetWebContents())) << "Don't call from constructor!";
  RegisterObserverForAllowedSources();
  if (address_controller_) {
    address_controller_->RefreshSuggestions();
  }
}

ManualFillingControllerImpl::ManualFillingControllerImpl(
    content::WebContents* web_contents)
    : content::WebContentsUserData<ManualFillingControllerImpl>(*web_contents) {
  pwd_controller_ = ChromePasswordManagerClient::FromWebContents(web_contents)
                        ->GetOrCreatePasswordAccessory()
                        ->AsWeakPtr();
  DCHECK(pwd_controller_);

  address_controller_ =
      AddressAccessoryController::GetOrCreate(web_contents)->AsWeakPtr();
  DCHECK(address_controller_);

  payment_method_controller_ =
      PaymentMethodAccessoryController::GetOrCreate(web_contents)->AsWeakPtr();
  DCHECK(payment_method_controller_);

  at_memory_controller_ =
      AtMemoryAccessoryController::GetOrCreate(web_contents)->AsWeakPtr();
  DCHECK(at_memory_controller_);
}

ManualFillingControllerImpl::ManualFillingControllerImpl(
    content::WebContents* web_contents,
    base::WeakPtr<PasswordAccessoryController> pwd_controller,
    base::WeakPtr<AddressAccessoryController> address_controller,
    base::WeakPtr<PaymentMethodAccessoryController> payment_method_controller,
    base::WeakPtr<AtMemoryAccessoryController> at_memory_controller,
    std::unique_ptr<ManualFillingViewInterface> view)
    : content::WebContentsUserData<ManualFillingControllerImpl>(*web_contents),
      pwd_controller_(std::move(pwd_controller)),
      address_controller_(std::move(address_controller)),
      payment_method_controller_(std::move(payment_method_controller)),
      at_memory_controller_(std::move(at_memory_controller)),
      view_(std::move(view)) {}

ManualFillingControllerImpl::~ManualFillingControllerImpl() = default;

bool ManualFillingControllerImpl::ShouldShowAccessoryForLastFocusedFieldType()
    const {
  switch (last_focused_field_type_) {
    // If there are suggestions, show on usual form fields.
    case FocusedFieldType::kFillablePasswordField:
    case FocusedFieldType::kFillableUsernameField:
    case FocusedFieldType::kFillableWebauthnTaggedField:
    case FocusedFieldType::kFillableNonSearchField:
      return !available_sources_.empty();

    case FocusedFieldType::kFillableSearchField:
      if (base::FeatureList::IsEnabled(
              autofill::features::
                  kAutofillEnableKeyboardAccessoryOnSearchFields)) {
        return !available_sources_.empty();
      }
      return available_sources_.contains(FillingSource::AUTOFILL);

    // Even if there are suggestions, don't show on textareas.
    case FocusedFieldType::kFillableTextArea:
      return false;

    // Sometimes autocomplete entries may be set when the focus is on an unknown
    // or unfillable field.
    case FocusedFieldType::kUnfillableElement:
    case FocusedFieldType::kUnknown:
      return available_sources_.contains(FillingSource::AUTOFILL);

    // AtMemory suggestions are supported for contenteditable fields on Android.
    // Unlike database-backed fallback sheets (passwords, addresses, payments)
    // which signal availability asynchronously via `available_sources_`,
    // AtMemory availability on contenteditables is purely focus-driven and
    // only requires that the `AtMemoryAccessoryController` is available.
    // Note: `PasswordAutofillAgent` verifies the feature enablement.
    case FocusedFieldType::kContenteditableField:
      return at_memory_controller_ &&
             at_memory_controller_->IsAtMemoryAvailable();
  }
}

void ManualFillingControllerImpl::UpdateVisibility() {
  TRACE_EVENT0("passwords", "ManualFillingControllerImpl::UpdateVisibility");
  if (ShouldShowAccessoryForLastFocusedFieldType()) {
    for (const FillingSource& source : available_sources_) {
      if (source == FillingSource::AUTOFILL) {
        continue;  // Autofill suggestions have no sheet.
      }
      // On contenteditable elements, only AtMemory is supported. Suppress
      // pushing fallback sheet data (passwords, addresses, payments).
      if (last_focused_field_type_ == FocusedFieldType::kContenteditableField &&
          source != FillingSource::AT_MEMORY) {
        continue;
      }
      AccessoryController* controller = GetControllerForFillingSource(source);
      if (!controller) {
        continue;  // Most-likely, the controller was cleaned up already.
      }
      std::optional<AccessorySheetData> sheet = controller->GetSheetData();
      if (sheet.has_value()) {
        view_->OnItemsAvailable(std::move(sheet.value()));
      }
    }

    view_->Show(
        ManualFillingViewInterface::WaitForKeyboard(
            last_focused_field_type_ != FocusedFieldType::kUnfillableElement &&
            last_focused_field_type_ != FocusedFieldType::kUnknown),
        ManualFillingViewInterface::ShouldShowOnLargeFormFactor(
            last_focused_field_type_ ==
                FocusedFieldType::kFillableUsernameField ||
            last_focused_field_type_ ==
                FocusedFieldType::kFillablePasswordField ||
            last_focused_field_type_ ==
                FocusedFieldType::kContenteditableField ||
            available_sources_.contains(FillingSource::AUTOFILL)),
        ManualFillingViewInterface::IsContentEditable(
            last_focused_field_type_ ==
                FocusedFieldType::kContenteditableField));
  } else {
    view_->Hide();
  }
}

void ManualFillingControllerImpl::RegisterObserverForAllowedSources() {
  for (FillingSource source : kAllowedFillingSources) {
    AccessoryController* sheet_controller =
        GetControllerForFillingSource(source);
    if (!sheet_controller) {
      continue;  // Ignore disallowed sheets.
    }
    sheet_controller->RegisterFillingSourceObserver(base::BindRepeating(
        &ManualFillingControllerImpl::OnSourceAvailabilityChanged,
        weak_factory_.GetWeakPtr(), source));
  }
}

void ManualFillingControllerImpl::OnSourceAvailabilityChanged(
    FillingSource source,
    AccessoryController* source_controller,
    AccessoryController::IsFillingSourceAvailable is_source_available) {
  TRACE_EVENT0("passwords",
               "ManualFillingControllerImpl::OnSourceAvailabilityChanged");
  std::optional<AccessorySheetData> sheet = source_controller->GetSheetData();
  bool show_filling_source = sheet.has_value() && is_source_available;
  // TODO(crbug.com/40165275): Remove once all sheets pull this information
  // instead of waiting to get it pushed.
  if (sheet.has_value()) {
    // Suppress forwarding fallback sheet data for contenteditable elements
    // so only the AtMemory sheet is made available to the view.
    if (last_focused_field_type_ != FocusedFieldType::kContenteditableField ||
        source == FillingSource::AT_MEMORY) {
      view_->OnItemsAvailable(std::move(sheet.value()));
    }
  }
  UpdateSourceAvailability(source, show_filling_source);
}

AccessoryController* ManualFillingControllerImpl::GetControllerForTabType(
    AccessoryTabType type) const {
  switch (type) {
    case AccessoryTabType::ADDRESSES:
      return address_controller_.get();
    case AccessoryTabType::PASSWORDS:
      return pwd_controller_.get();
    case AccessoryTabType::CREDIT_CARDS:
      return payment_method_controller_.get();
    case AccessoryTabType::OBSOLETE_TOUCH_TO_FILL:
    case AccessoryTabType::ALL:
    case AccessoryTabType::COUNT:
      NOTREACHED() << "Controller not defined for tab: "
                   << static_cast<int>(type);
  }
}

AccessoryController* ManualFillingControllerImpl::GetControllerForAction(
    AccessoryAction action) const {
  switch (action) {
    case AccessoryAction::GENERATE_PASSWORD_MANUAL:
    case AccessoryAction::MANAGE_PASSWORDS:
    case AccessoryAction::USE_OTHER_PASSWORD:
    case AccessoryAction::GENERATE_PASSWORD_AUTOMATIC:
    case AccessoryAction::TOGGLE_SAVE_PASSWORDS:
    case AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY:
    case AccessoryAction::CROSS_DEVICE_PASSKEY:
    case AccessoryAction::RETRIEVE_TRUSTED_VAULT_KEY:
      return pwd_controller_.get();
    case AccessoryAction::MANAGE_ADDRESSES:
      return address_controller_.get();
    case AccessoryAction::MANAGE_CREDIT_CARDS:
    case AccessoryAction::MANAGE_LOYALTY_CARDS:
      return payment_method_controller_.get();
    case AccessoryAction::SHOW_AT_MEMORY_BOTTOMSHEET:
      return at_memory_controller_.get();
    case AccessoryAction::AUTOFILL_SUGGESTION:
    case AccessoryAction::DISMISS:
    case AccessoryAction::AUTOFILL_SUGGESTION_FROM_ACCESSORY_SHEET:
    case AccessoryAction::COUNT:
      NOTREACHED() << "Controller not defined for action: "
                   << static_cast<int>(action);
  }
}

AccessoryController* ManualFillingControllerImpl::GetControllerForFillingSource(
    const FillingSource& filling_source) const {
  switch (filling_source) {
    case FillingSource::PASSWORD_FALLBACKS:
      return pwd_controller_.get();
    case FillingSource::CREDIT_CARD_FALLBACKS:
      return payment_method_controller_.get();
    case FillingSource::ADDRESS_FALLBACKS:
      return address_controller_.get();
    case FillingSource::AT_MEMORY:
      return at_memory_controller_.get();
    case FillingSource::AUTOFILL:
      NOTREACHED() << "Controller not defined for filling source: "
                   << static_cast<int>(filling_source);
  }
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(ManualFillingControllerImpl);
