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

#ifndef CHROME_BROWSER_UI_VIEWS_FIND_BAR_HOST_H_
#define CHROME_BROWSER_UI_VIEWS_FIND_BAR_HOST_H_

#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ui/find_bar/find_bar.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/views/find_bar_owner.h"
#include "chrome/browser/ui/views/find_bar_view.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/external_focus_tracker.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/widget/widget_delegate.h"

class BrowserView;
class FindBarController;
class FindInPageTest;

namespace find_in_page {
class FindNotificationDetails;
}

namespace views {
class Widget;
}
////////////////////////////////////////////////////////////////////////////////
//
// The FindBarHost implements the container widget for the find-in-page
// functionality. It is responsible for showing, hiding, closing, and moving the
// widget if needed, for example if the widget is obscuring the selection
// results. It also receives notifications about the search results and
// communicates that to the view. There is one FindBarHost per BrowserView, and
// its state is updated whenever the selected Tab is changed. The FindBarHost is
// created when the BrowserView is attached to the frame's Widget for the first
// time.
//
////////////////////////////////////////////////////////////////////////////////
class FindBarHost : public FindBar,
                    public FindBarTesting,
                    public views::FocusChangeListener,
                    public ui::AcceleratorTarget,
                    public views::AnimationDelegateViews,
                    public views::WidgetDelegate {
 public:
  explicit FindBarHost(FindBarOwner* find_bar_owner);

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

  ~FindBarHost() override;

  // Forwards selected key events to the renderer. This is useful to make sure
  // that arrow keys and PageUp and PageDown result in scrolling, instead of
  // being eaten because the FindBar has focus. Returns true if the keystroke
  // was forwarded, false if not.
  bool MaybeForwardKeyEventToWebpage(const ui::KeyEvent& key_event);

  // Ensures the owner widget is active if it is not already, since the find bar
  // is not activatable. See crbug.com/40616214.
  void ActivateOwnerWidgetIfNecessary();

  // Returns true if the find bar view is visible, or false otherwise.
  bool IsVisible() const;

  FindBarOwner* find_bar_owner() { return find_bar_owner_; }

  // Get the host widget.
  views::Widget* GetHostWidget() override;

  // FindBar implementation:
  FindBarController* GetFindBarController() const override;
  void SetFindBarController(FindBarController* find_bar_controller) override;
  void Show(bool animate, bool focus) override;
  void Hide(bool animate) override;
  void SetFocusAndSelection() override;
  void ClearResults(
      const find_in_page::FindNotificationDetails& results) override;
  void StopAnimation() override;
  void MoveWindowIfNecessary() override;
  void SetFindTextAndSelectedRange(const std::u16string& find_text,
                                   const gfx::Range& selected_range) override;
  std::u16string_view GetFindText() const override;
  gfx::Range GetSelectedRange() const override;
  void UpdateUIForFindResult(
      const find_in_page::FindNotificationDetails& result,
      const std::u16string& find_text) override;
  void AudibleAlert() override;
  bool IsFindBarVisible() const override;
  void RestoreSavedFocus() override;
  bool HasGlobalFindPasteboard() const override;
  void UpdateFindBarForChangedWebContents() override;
  bool CanPopulateFromSelectedText() override;
  const FindBarTesting* GetFindBarTesting() const override;
  bool HasFocus() const override;
  void CloseOverlappingBubbles() override;

  // Overridden from ui::AcceleratorTarget
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
  bool CanHandleAccelerators() const override;

  // FindBarTesting implementation:
  bool GetFindBarWindowInfo(gfx::Point* position,
                            bool* fully_visible) const override;
  std::u16string_view GetFindSelectedText() const override;
  std::u16string_view GetMatchCountText() const override;
  int GetContentsWidth() const override;
  size_t GetAudibleAlertCount() const override;

  // views::WidgetDelegate:
  std::u16string GetAccessibleWindowTitle() const override;

  FindBarView* GetFindBarViewForTesting();

  // static
  static base::AutoReset<bool> SetEnableAnimationsForTesting(
      bool enable_animation);

 private:
  friend class FindInPageTest;
  friend class LegacyFindInPageTest;

  // Return the current web contents.
  content::WebContents* web_contents() {
    return find_bar_controller_ ? find_bar_controller_->web_contents()
                                : nullptr;
  }

  // Allows implementation to tweak widget position.
  void GetWidgetPositionNative(gfx::Rect* avoid_overlapping_rect);

  // If the find bar obscures the search results we need to move the window. To
  // do that we need to know what is selected on the page. We simply calculate
  // where it would be if we place it on the left of the selection and if it
  // doesn't fit on the screen we try the right side. The parameter
  // |selection_rect| is expected to have coordinates relative to the top of
  // the web page area.
  void MoveWindowIfNecessaryWithRect(const gfx::Rect& selection_rect);

  // Saves the focus tracker for potential restoration later during a
  // WebContents change.
  void SaveFocusTracker();

  // Takes the focus tracker from a WebContents and restores it to the
  // FindBarHost. If no focus tracker is set, creates one.
  void RestoreOrCreateFocusTracker();

  // Call when the find bar gains or loses focus on current tab. Used to restore
  // focus state in tab switching. i.e. the focus state should not change after
  // switching away and then back to the current tab.
  void SetFindBarIsFocusedOnCurrentTab(bool focused);

  // Called when `is_visible_` changes.
  void OnVisibilityChanged();

  // Registers this class as the handler for when Escape is pressed. Once we
  // loose focus we will unregister Escape and (any accelerators the derived
  // classes registers by using overrides of RegisterAccelerators). See also:
  // SetFocusChangeListener().
  void RegisterAccelerators();

  // When we lose focus, we unregister all accelerator handlers. See also:
  // SetFocusChangeListener().
  void UnregisterAccelerators();

  // Returns the rectangle representing where to position the find bar. It uses
  // GetDialogBounds and positions itself within that, either to the left (if an
  // InfoBar is present) or to the right (no InfoBar). If
  // |avoid_overlapping_rect| is specified, the return value will be a rectangle
  // located immediately to the left of |avoid_overlapping_rect|, as long as
  // there is enough room for the dialog to draw within the bounds. If not, the
  // dialog position returned will overlap |avoid_overlapping_rect|.
  // Note: |avoid_overlapping_rect| is expected to use coordinates relative to
  // the top of the page area, (it will be converted to coordinates relative to
  // the top of the browser window, when comparing against the dialog
  // coordinates). The returned value is relative to the browser window.
  gfx::Rect GetDialogPosition(gfx::Rect avoid_overlapping_rect);
  // Moves the dialog window to the provided location, moves it to top in the
  // z-order (HWND_TOP, not HWND_TOPMOST) and shows the window (if hidden).
  void SetDialogPosition(const gfx::Rect& new_pos);

  // views::FocusChangeListener:
  void OnWillChangeFocus(views::View* focused_before,
                         views::View* focused_now) override;

  // views::AnimationDelegateViews:
  void AnimationProgressed(const gfx::Animation* animation) override;
  void AnimationEnded(const gfx::Animation* animation) override;

  // Our view, which is responsible for drawing the UI.
  raw_ptr<FindBarView, DanglingUntriaged> view_ = nullptr;

  // The animation class to use when opening the FindBar widget.
  std::unique_ptr<gfx::SlideAnimation> animation_;

  // Host is the Widget implementation that is created and maintained by the
  // find bar. It contains the find bar view.
  std::unique_ptr<views::Widget> host_;

  // A pointer back to the owning controller.
  raw_ptr<FindBarController> find_bar_controller_ = nullptr;

  // The number of audible alerts issued.
  size_t audible_alerts_ = 0;

  // A flag to manually manage visibility. GTK/X11 is asynchronous and
  // the state of the widget can be out of sync.
  bool is_visible_ = false;

  // The FindBarOwner that created us.
  const raw_ptr<FindBarOwner, DanglingUntriaged> find_bar_owner_;

  // The focus manager we register with to keep track of focus changes.
  raw_ptr<views::FocusManager, DanglingUntriaged> focus_manager_ = nullptr;

  // True if the accelerator target for Esc key is registered.
  bool esc_accel_target_registered_ = false;

  // Tracks and stores the last focused view which is not the FindBarHost's view
  // or any of its children. Used to restore focus once the FindBarHost's view
  // is closed.
  std::unique_ptr<views::ExternalFocusTracker> focus_tracker_;

  // Observation over the host's FocusManager.
  base::ScopedObservation<views::FocusManager, views::FocusChangeListener>
      focus_manager_observation_{this};
};

#endif  // CHROME_BROWSER_UI_VIEWS_FIND_BAR_HOST_H_
