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

#ifndef IOS_CHROME_BROWSER_AUTOFILL_MODEL_BOTTOM_SHEET_AUTOFILL_BOTTOM_SHEET_TAB_HELPER_H_
#define IOS_CHROME_BROWSER_AUTOFILL_MODEL_BOTTOM_SHEET_AUTOFILL_BOTTOM_SHEET_TAB_HELPER_H_

#import <memory>

#import "base/memory/raw_ptr.h"
#import "base/memory/weak_ptr.h"
#import "base/scoped_multi_source_observation.h"
#import "components/autofill/core/browser/field_types.h"
#import "components/autofill/core/browser/foundations/autofill_manager.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/password_manager/ios/password_generation_provider.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/save_card_bottom_sheet_model.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/virtual_card_enrollment_callbacks.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/web_state_observer.h"
#import "ios/web/public/web_state_user_data.h"
#import "url/origin.h"

namespace autofill {
class AutofillBottomSheetObserver;
class CardUnmaskAuthenticationSelectionDialogControllerImpl;
struct FormActivityParams;
class VirtualCardEnrollUiModel;
}  // namespace autofill

namespace web {
class ScriptMessage;
}  // namespace web

@protocol AutofillCommands;
@class CommandDispatcher;
@protocol FormInputSuggestionsProvider;
@class FormSuggestion;

// This class manages state and events relating to the showing of various bottom
// sheets for Autofill/Password Manager.
//
// Some bottom sheets show in response to browser-layer interactions. These can
// be instantiated directly using public Show... methods.
//
// Others show in response to JS-layer interactions. In these cases, this class
// attaches/detaches listeners in the document, and shows the appropriate bottom
// sheet when these listeners are triggered.
class AutofillBottomSheetTabHelper
    : public web::WebFramesManager::Observer,
      public web::WebStateObserver,
      public web::WebStateUserData<AutofillBottomSheetTabHelper>,
      public autofill::AutofillManager::Observer {
 public:
  // Maximum number of times the credential bottom sheet can be
  // dismissed before it gets disabled.
  static constexpr int kCredentialBottomSheetMaxDismissCount = 3;

  // Maximum number of times the password generation bottom sheet can be
  // dismissed before it gets disabled.
  static constexpr int kPasswordGenerationBottomSheetMaxDismissCount = 3;

  AutofillBottomSheetTabHelper(const AutofillBottomSheetTabHelper&) = delete;
  AutofillBottomSheetTabHelper& operator=(const AutofillBottomSheetTabHelper&) =
      delete;

  ~AutofillBottomSheetTabHelper() override;

  // Observer registration methods.
  void AddObserver(autofill::AutofillBottomSheetObserver* observer);
  void RemoveObserver(autofill::AutofillBottomSheetObserver* observer);

  // Shows the card unmask authentication selection bottom sheet. The
  // `model_controller` are stored and retrieved later by the appropriate
  // coordinator.
  void ShowCardUnmaskAuthenticationSelection(
      std::unique_ptr<
          autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>
          model_controller);

  // Send a command to show save card bottomsheet.
  void ShowSaveCardBottomSheet(
      std::unique_ptr<autofill::SaveCardBottomSheetModel> model);

  // Send a command to show the VCN enrollment Bottom Sheet.
  void ShowVirtualCardEnrollmentBottomSheet(
      std::unique_ptr<autofill::VirtualCardEnrollUiModel> model,
      autofill::VirtualCardEnrollmentCallbacks callbacks);

  // Send a command to show the bottom sheet to edit an address.
  // The address to be shown/worked upon in this bottom sheet is fetched via the
  // `AutofillSaveUpdateAddressProfileDelegateIOS` delegate.
  void ShowEditAddressBottomSheet();

  // Handler for JavaScript messages. Dispatch to more specific handler.
  void OnFormMessageReceived(const web::ScriptMessage& message);

  // Sets the bottom sheet CommandDispatcher.
  void SetAutofillBottomSheetHandler(id<AutofillCommands> commands_handler);

  // Sets the password generation provider used for proactive password
  // generation.
  void SetPasswordGenerationProvider(
      id<PasswordGenerationProvider> generation_provider);

  // Prepare bottom sheet using data from the password form prediction.
  void AttachPasswordListeners(
      const std::vector<autofill::FieldRendererId>& renderer_ids,
      const std::string& frame_id);

  // Prepare proactive password generation bottom sheet using data from the
  // password form prediction.
  void AttachPasswordGenerationListeners(
      const std::vector<autofill::FieldRendererId>& renderer_ids,
      const std::string& frame_id);

  // Detach the password listeners, which will deactivate the credential bottom
  // sheet on the provided frame.
  void DetachPasswordListeners(const std::string& frame_id, bool refocus);

  // Detaches the password listeners, which will deactivate the credential
  // bottom sheet on all frames. Refocuses on the last field that triggered a
  // bottom sheet if `refocus` is true, which can be a login field or any other
  // field associated with a bottom sheet. The last element is reset after
  // focusing, meaning that refocusing multiple times will be no op until a new
  // sheet is presented.
  void DetachPasswordListenersForAllFrames(bool refocus);

  // Detach the password generation listeners, which will deactivate the
  // proactive password generation bottom sheet on all frames.
  void DetachPasswordGenerationListenersForAllFrames();

  // Detach the payments listeners, which will deactivate the payments bottom
  // sheet on the provided frame.
  void DetachPaymentsListeners(const std::string& frame_id, bool refocus);

  // Detach the payments listeners, which will deactivate the payments bottom
  // sheet on all frames.
  void DetachPaymentsListenersForAllFrames(bool refocus);

  // Detach the Ambient Autofill notice listeners, which will deactivate the
  // Ambient Autofill notice bottom sheet on all frames.
  void DetachAmbientAutofillNoticeListenersForAllFrames(bool refocus);

  // Refocuses on the last field that triggered a bottom sheet (which can be a
  // login field or any other field associated with a bottom sheet) and restores
  // first responder focus to the web view once JavaScript refocusing completes.
  void RefocusElementIfNeeded(const std::string& frame_id);

  // WebStateObserver:
  void DidFinishNavigation(web::WebState* web_state,
                           web::NavigationContext* navigation_context) override;
  void WebStateDestroyed(web::WebState* web_state) override;

  // web::WebFramesManager::Observer:
  void WebFrameBecameAvailable(web::WebFramesManager* web_frames_manager,
                               web::WebFrame* web_frame) override;

  // autofill::AutofillManager::Observer:
  void OnAutofillManagerStateChanged(
      autofill::AutofillManager& manager,
      autofill::AutofillManager::LifecycleState old_state,
      autofill::AutofillManager::LifecycleState new_state) override;
  void OnFieldTypesDetermined(autofill::AutofillManager& manager,
                              autofill::FormGlobalId form_id,
                              FieldTypeSource source,
                              bool small_forms_were_parsed) override;

  // Returns the controller for authentication selection.
  // The caller takes ownership and subsequent calls will return nullptr until
  // another instance of the dialog is shown again by calling
  // ShowCardUnmaskAuthenticationSelection().
  std::unique_ptr<
      autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>
  GetCardUnmaskAuthenticationSelectionDialogController();

  // Returns the model for save card bottomsheet. The caller takes ownership and
  // subsequent calls will return nullptr until another instance of the
  // bottomsheet is shown again by calling ShowSaveCardBottomSheet().
  std::unique_ptr<autofill::SaveCardBottomSheetModel>
  GetSaveCardBottomSheetModel();

  // Used to get the callbacks to be run on completion of the VCN enrollment UI.
  // This value is moved and should only be retrieved once per bottom sheet.
  autofill::VirtualCardEnrollmentCallbacks GetVirtualCardEnrollmentCallbacks();

  // Updates the listeners for the payments form corresponding to `form_id`.
  // Only attaches the listeners on newly discovered payment fields when
  // `only_new` is true, or attaches the listeners on all payment fields
  // otherwise. In V3, it also detaches the listeners for the fields that are no
  // longer related to payments (from later discovery).
  void UpdateListenersForPaymentsForm(autofill::AutofillManager& manager,
                                      autofill::FormGlobalId form_id,
                                      bool only_new);

  // Updates the page listeners for the Ambient Autofill notice. If the user is
  // eligible to see the notice, this method scans the fields of the form
  // specified by `form_id` and attaches focus event listeners to the fields
  // that have Autofill AI types. Tapping or focusing on these fields will
  // trigger the notice bottom sheet.
  void UpdateListenersForAmbientAutofillForm(autofill::AutofillManager& manager,
                                             autofill::FormGlobalId form_id,
                                             bool only_new);

 private:
  friend class web::WebStateUserData<AutofillBottomSheetTabHelper>;

  explicit AutofillBottomSheetTabHelper(web::WebState* web_state);

  // Check whether the credential bottom sheet has been dismissed too many times
  // by the user.
  bool HasReachedCredentialBottomSheetDismissLimit();

  // Check whether the password generation bottom sheet has been dismissed
  // too many times by the user.
  bool HasReachedPasswordGenerationDismissLimit();

  // Prepare bottom sheet using data from the form prediction.
  void AttachListeners(
      const std::vector<autofill::FieldRendererId>& renderer_ids,
      std::set<autofill::FieldRendererId>& registered_renderer_ids,
      const std::string& frame_id,
      bool allow_autofocus,
      bool only_new);

  // Detach listeners, which will deactivate the associated bottom sheet.
  void DetachListenersForFrame(
      const std::string& frame_id,
      const std::set<autofill::FieldRendererId>& renderer_ids,
      bool refocus);

  // Send command to show the Credential Bottom Sheet.
  void ShowCredentialBottomSheet(const autofill::FormActivityParams& params);

  // Send command to show the Ambient Autofill notice bottom sheet.
  void ShowAmbientAutofillNotice(const autofill::FormActivityParams& params);

  // Conditionally show the payments bottom sheet based on the Autofill
  // suggestions that can be retrieved for the form that corresponds to
  // `params`.
  void MaybeShowPaymentsBottomSheet(const autofill::FormActivityParams params);

  // Send command to show the Payments Bottom Sheet. Detach all listeners if
  // `detach`.
  void ShowPaymentsBottomSheet(const autofill::FormActivityParams& params,
                               bool detach);

  // Send command to show the scan save and fill Bottom Sheet.
  void ShowScanCardSaveAndFillBottomSheet(
      const autofill::FormActivityParams& params);

  // Called when the suggestions are retrieved for the payments bottom sheet.
  void OnSuggestionsRetrievedForPaymentsBottomSheet(
      const autofill::FormActivityParams& params,
      base::TimeTicks start_timestamp,
      NSArray<FormSuggestion*>* suggestions,
      id<FormInputSuggestionsProvider> provider);

  // Shows the password generation suggestion view controller.
  void ShowProactivePasswordGenerationBottomSheet(
      const autofill::FormActivityParams& params);

  // Password generation provider used to trigger proactive password generation
  id<PasswordGenerationProvider> generation_provider_;

  // Handler used to request showing the Autofill bottom sheet.
  __weak id<AutofillCommands> commands_handler_;

  // The WebState with which this object is associated.
  raw_ptr<web::WebState> web_state_;

  // TODO(crbug.com/40266699): Remove once this class uses FormGlobalIds.
  base::ScopedObservation<web::WebFramesManager,
                          web::WebFramesManager::Observer>
      frames_manager_observation_{this};

  base::ScopedMultiSourceObservation<autofill::AutofillManager,
                                     autofill::AutofillManager::Observer>
      autofill_manager_observations_{this};

  // List of credential bottom sheet related renderer ids, mapped to a frame id.
  // TODO(crbug.com/40266699): Maybe migrate to FieldGlobalIds.
  std::map<std::string, std::set<autofill::FieldRendererId>>
      registered_password_renderer_ids_;

  // List of payments bottom sheet related renderer ids, mapped to a frame id.
  // TODO(crbug.com/40266699): Migrate to FieldGlobalIds.
  std::map<std::string, std::set<autofill::FieldRendererId>>
      registered_payments_renderer_ids_;

  // List of Ambient Autofill notice bottom sheet related renderer ids, mapped
  // to a frame id.
  std::map<std::string, std::set<autofill::FieldRendererId>>
      registered_ambient_renderer_ids_;

  // Set of proactive password generation bottom sheet related renderer ids,
  // mapped to their frame id.
  // TODO(crbug.com/40266699): Migrate to FieldGlobalIds.
  std::map<std::string, std::set<autofill::FieldRendererId>>
      registered_password_generation_renderer_ids_;

  base::ObserverList<autofill::AutofillBottomSheetObserver>::Unchecked
      observers_;

  // A controller for the authentication selection. This will be reset once
  // GetCardUnmaskAuthenticationSelectionDialogController is called.
  std::unique_ptr<
      autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>
      card_unmask_authentication_selection_controller_;

  // Model providing resources and callbacks for save card bottomsheet. This
  // will be reset once GetSaveCardBottomSheetModel() is called.
  std::unique_ptr<autofill::SaveCardBottomSheetModel>
      save_card_bottom_sheet_model_;

  // Callbacks to be run when the virtual card enrollment bottom sheet UI has
  // completed.
  autofill::VirtualCardEnrollmentCallbacks virtual_card_enrollment_callbacks_;

  base::WeakPtrFactory<AutofillBottomSheetTabHelper> weak_factory_{this};
};

#endif  // IOS_CHROME_BROWSER_AUTOFILL_MODEL_BOTTOM_SHEET_AUTOFILL_BOTTOM_SHEET_TAB_HELPER_H_
