// 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 UI_VIEWS_COREWM_TOOLTIP_CONTROLLER_H_
#define UI_VIEWS_COREWM_TOOLTIP_CONTROLLER_H_

#include <map>
#include <memory>
#include <string>
#include <string_view>

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "ui/aura/client/cursor_client_observer.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_tracker.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/corewm/tooltip.h"
#include "ui/views/views_export.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/tooltip_client.h"

namespace aura {
class Window;
}

namespace gfx {
class Rect;
}  // namespace gfx

namespace wm {
class ActivationClient;
class TooltipObserver;
}  // namespace wm

namespace views::corewm {

class Tooltip;
class TooltipStateManager;

namespace test {
class TooltipControllerTestHelper;
}  // namespace test

// TooltipController listens for events that can have an impact on the
// tooltip state.
class VIEWS_EXPORT TooltipController
    : public wm::TooltipClient,
      public ui::EventHandler,
      public aura::client::CursorClientObserver,
      public aura::WindowObserver,
      public wm::ActivationChangeObserver {
 public:
  TooltipController(std::unique_ptr<Tooltip> tooltip,
                    wm::ActivationClient* activation_client);

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

  ~TooltipController() override;

  void AddObserver(wm::TooltipObserver* observer);
  void RemoveObserver(wm::TooltipObserver* observer);

  // Overridden from wm::TooltipClient.
  int GetMaxWidth(const gfx::Point& location) const override;
  void UpdateTooltip(aura::Window* target) override;
  void UpdateTooltipFromKeyboard(const gfx::Rect& bounds,
                                 aura::Window* target) override;
  bool IsTooltipSetFromKeyboard(aura::Window* target) override;
  void SetHideTooltipTimeout(aura::Window* target,
                             base::TimeDelta timeout) override;
  void SetTooltipsEnabled(bool enable) override;

  // Overridden from ui::EventHandler.
  void OnKeyEvent(ui::KeyEvent* event) override;
  void OnMouseEvent(ui::MouseEvent* event) override;
  void OnTouchEvent(ui::TouchEvent* event) override;
  void OnCancelMode(ui::CancelModeEvent* event) override;
  std::string_view GetLogContext() const override;

  // Overridden from aura::client::CursorClientObserver.
  void OnCursorVisibilityChanged(bool is_visible) override;

  // Overridden from aura::WindowObserver.
  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
  void OnWindowDestroying(aura::Window* window) override;
  void OnWindowDestroyed(aura::Window* window) override;
  void OnWindowPropertyChanged(aura::Window* window,
                               const void* key,
                               intptr_t old) override;

  // Overridden from wm::ActivationChangeObserver.
  void OnWindowActivated(ActivationReason reason,
                         aura::Window* gained_active,
                         aura::Window* lost_active) override;

  // Updates tooltip triggered by keyboard with `anchor_point` value.
  // This should be called instead of UpdateTooltipFromKeyboard() when the
  // anchor point is already calculated (e.g. Exo).
  void UpdateTooltipFromKeyboardWithAnchorPoint(const gfx::Point& anchor_point,
                                                aura::Window* target);

  // Sets show tooltip delay for `target` window.
  void SetShowTooltipDelay(aura::Window* target, base::TimeDelta delay);

 private:
  friend class test::TooltipControllerTestHelper;

  // Resets the window and calls `TooltipStateManager::HideAndReset`.
  void HideAndReset();

  // Updates the tooltip if required (if there is any change in the tooltip
  // text, tooltip id or the aura::Window).
  void UpdateIfRequired(TooltipTrigger trigger);

  // Returns true if there's a drag-and-drop in progress.
  bool IsDragDropInProgress() const;

  // Returns true if the cursor is visible.
  bool IsCursorVisible() const;

  // Gets the delay after which the tooltip should be shown/hidden.
  base::TimeDelta GetShowTooltipDelay();
  base::TimeDelta GetHideTooltipDelay();

  // Sets observed window to |target| if it is different from existing window.
  // Calls RemoveObserver on the existing window if it is not NULL.
  // Calls AddObserver on the new window if it is not NULL.
  void SetObservedWindow(aura::Window* target);

  // Returns true if the tooltip id stored on the state manager and the one
  // stored on the window are different.
  bool IsTooltipIdUpdateNeeded() const;

  // Returns true if the tooltip text stored on the state manager and the one
  // stored on the window are different.
  bool IsTooltipTextUpdateNeeded() const;

  // Removes show/hide tooltip delay from `show_tooltip_delay_map_` and
  // `hide_tooltip_timeout_map_`.
  void RemoveTooltipDelayFromMap(aura::Window* window);

  // Stops tracking the window on which the cursor was when the mouse was
  // pressed if we're on another window or if a new tooltip is triggered by
  // keyboard.
  void ResetWindowAtMousePressedIfNeeded(aura::Window* target,
                                         bool force_reset);

  // To prevent the tooltip to show again after a mouse press event, we want
  // to hide it until the cursor moves to another window.
  bool ShouldHideBecauseMouseWasOncePressed();

  // Returns true if the current event is a duplicate event generated by a
  // pen/stylus hovering over the same window.
  bool IsDuplicatePenHoverEvent(ui::EventPointerType pointer_type);

  aura::Window* tooltip_window_at_mouse_press() {
    auto& windows = tooltip_window_at_mouse_press_tracker_.windows();
    return windows.empty() ? nullptr : windows[0];
  }

  // The window on which we are currently listening for events. When there's a
  // keyboard-triggered visible tooltip, its value is set to the tooltip parent
  // window. Otherwise, it's following the cursor.
  raw_ptr<aura::Window> observed_window_ = nullptr;

  // These fields are for tracking state when the user presses a mouse button.
  // The tooltip should stay hidden after a mouse press event on the view until
  // the cursor moves to another view.
  std::u16string tooltip_text_at_mouse_press_;
  // NOTE: this either has zero or one window.
  aura::WindowTracker tooltip_window_at_mouse_press_tracker_;

  // Location of the last events in |tooltip_window_|'s coordinates.
  // |last_mouse_loc_| and |last_focus_loc_| are used to position tooltips
  // triggered by either the mouse or the keyboard, respectively. We also
  // track |last_mouse_loc_| to be able to ignore spurious and/or redundant
  // events.
  gfx::Point last_mouse_loc_;
  gfx::Point last_touch_loc_;
  gfx::Point last_focus_loc_;

  // True if the current event is a duplicate event generated by hovering with a
  // pen/stylus. Hovering with a pen generates a constant stream of move events,
  // so duplicate pen events on the same window should be ignored to prevent the
  // tooltip's show timer from being restarted on each event.
  bool is_duplicate_pen_hover_event_ = false;
  // The last tooltip text that was shown when the pen was hovering.
  // TODO(crbug.com/40246278): Replace this with a unique tooltip identifier
  // when one is implemented. For now, the tooltip text is the closest thing to
  // a tooltip identifier there is.
  std::u16string last_pen_tooltip_text_;

  // Whether tooltips can be displayed or not.
  bool tooltips_enabled_ = true;

  // Whether tooltip should be skip delay before showing.
  // This may be set to true only for testing.
  // Do NOT override this value except from TooltipControllerTestHelper.
  bool skip_show_delay_for_testing_ = false;

  // The show delay before showing tooltip may differ for external app's
  // tooltip. This map specifies the show delay for each target window.
  // TODO(crbug.com/c/374244480): consider removing this when removing lacros
  // support code from //c/exo. This is only used by aura shell to show tooltips
  // with delays for external apps.
  std::map<aura::Window*, base::TimeDelta> show_tooltip_delay_map_;

  // Web content tooltips should be shown indefinitely and those added on Views
  // should be hidden automatically after a timeout. This map stores the timeout
  // value for each aura::Window.
  // TODO(bebeaudr): Currently, all Views tooltips are hidden after the same
  // timeout and all web content views should be shown indefinitely. If this
  // general rule is always true, then we don't need a complex map here. A set
  // of aura::Window* would be enough with an attribute named
  // "disabled_hide_timeout_views_set_" or something like that.
  std::map<aura::Window*, base::TimeDelta> hide_tooltip_timeout_map_;

  // We want to hide tooltips whenever our client window loses focus. This will
  // ensure that no tooltip stays visible when the user navigated away from
  // our client.
  raw_ptr<wm::ActivationClient> activation_client_;

  // The TooltipStateManager is responsible for keeping track of the current
  // tooltip state (its text, position, id, etc.) and to modify it when asked
  // by the TooltipController or the show/hide timers.
  std::unique_ptr<TooltipStateManager> state_manager_;
};

}  // namespace views::corewm

#endif  // UI_VIEWS_COREWM_TOOLTIP_CONTROLLER_H_
