// Copyright 2021 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_TABS_HOVERCARD_TAB_HOVER_CARD_CONTROLLER_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_HOVERCARD_TAB_HOVER_CARD_CONTROLLER_H_

#include <memory>

#include "base/callback_list.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/performance_controls/tab_resource_usage_collector.h"
#include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
#include "components/prefs/pref_change_registrar.h"
#include "ui/views/animation/bubble_slide_animator.h"
#include "ui/views/animation/widget_fade_animator.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"

namespace gfx {
class ImageSkia;
}

class HoverCardAnchorTarget;
class TabHoverCardBubbleView;
class TabHoverCardThumbnailObserver;
class TabStripRegionView;
class BrowserWindowInterface;

// Controls how hover cards are shown and hidden for tabs.
class TabHoverCardController : public views::ViewObserver,
                               public TabResourceUsageCollector::Observer {
 public:
  class ScopedHideHoverCardLock {
   public:
    explicit ScopedHideHoverCardLock(TabHoverCardController* controller);
    ScopedHideHoverCardLock(const ScopedHideHoverCardLock&) = delete;
    ScopedHideHoverCardLock& operator=(const ScopedHideHoverCardLock&) = delete;
    ~ScopedHideHoverCardLock();

   private:
    raw_ptr<TabHoverCardController> controller_;
  };

  TabHoverCardController(TabStripRegionView* tab_strip,
                         BrowserWindowInterface* browser_window_interface);
  ~TabHoverCardController() override;

  bool IsHoverCardVisible() const;
  bool IsHoverCardShowingForTab(HoverCardAnchorTarget* anchor_target) const;
  void UpdateHoverCard(HoverCardAnchorTarget* anchor_target,
                       TabSlotController::HoverCardUpdateType update_type);
  void PreventImmediateReshow();

  std::unique_ptr<ScopedHideHoverCardLock> GetHoverCardHideLock();

  const HoverCardAnchorTarget* target_tab() const { return target_tab_.get(); }

  bool hover_card_memory_usage_enabled() const {
    return hover_card_memory_usage_enabled_;
  }

  TabHoverCardBubbleView* hover_card_for_testing() { return hover_card_.get(); }

  size_t hover_cards_seen_count_for_testing() const {
    return hover_cards_seen_count_;
  }

  static void set_disable_animations_for_testing(
      bool disable_animations_for_testing) {
    disable_animations_for_testing_ = disable_animations_for_testing;
  }

 protected:
  TabHoverCardController();  // For testing only

 private:
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest, ShowWrongTabDoesntCrash);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                           SetPreviewWithNoHoverCardDoesntCrash);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest, ShowPreviewsForTab);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest, DisablePreviewsForTab);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardFadeFooterInteractiveUiTest,
                           HoverCardFooterMemoryUsagePrefEnabled);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardFadeFooterInteractiveUiTest,
                           HoverCardFooterMemoryUsagePrefDisabled);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                           HidePreviewsForDiscardedTab);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                           DisableMemoryUsageForTab);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                           ShowPreviewsForDiscardedTabWithThumbnail);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                           ShowPreviewsForCrashedTab);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardPreviewsEnabledPrefTest, DefaultState);
  class EventSniffer;

  enum ThumbnailWaitState {
    kNotWaiting,
    kWaitingWithPlaceholder,
    kWaitingWithoutPlaceholder
  };

  // Returns whether the hover card preview images feature is enabled.
  static bool AreHoverCardImagesEnabled();

  // Returns whether hover card animations should be shown on the current
  // device.
  static bool UseAnimations();

  // views::ViewObserver:
  void OnViewIsDeleting(views::View* observed_view) override;
  void OnViewVisibilityChanged(views::View* observed_view,
                               views::View* starting_view,
                               bool visible) override;

  // TabResourceUsageCollector::Observer:
  void OnTabResourceMetricsRefreshed() override;

  bool ArePreviewsEnabled() const;

  void CreateHoverCard(HoverCardAnchorTarget* anchor_target);
  void UpdateCardContent(HoverCardAnchorTarget* anchor_target);
  void MaybeStartThumbnailObservation(HoverCardAnchorTarget* anchor_target,
                                      bool is_initial_show);
  void StartThumbnailObservation(HoverCardAnchorTarget* anchor_target);

  void UpdateOrShowCard(HoverCardAnchorTarget* anchor_target,
                        TabSlotController::HoverCardUpdateType update_type);
  void ShowHoverCard(bool is_initial, HoverCardAnchorTarget* intended_target);
  void HideHoverCard();

  bool ShouldShowImmediately(const HoverCardAnchorTarget* anchor_target) const;

  // Determines if `target_tab_` is still valid. Call this when entering
  // TabHoverCardController from an asynchronous callback.
  bool TargetTabIsValid() const;

  // Helper for recording when a card becomes fully visible to the user.
  void OnCardFullyVisible();

  // Helper for resetting the cards seen count for testing.
  void ResetCardsSeenCount();

  // Animator events:
  void OnFadeAnimationEnded(views::WidgetFadeAnimator* animator,
                            views::WidgetFadeAnimator::FadeType fade_type);
  void OnSlideAnimationProgressed(views::BubbleSlideAnimator* animator,
                                  double value);
  void OnSlideAnimationComplete(views::BubbleSlideAnimator* animator);

  void OnPreviewImageAvailable(TabHoverCardThumbnailObserver* observer,
                               gfx::ImageSkia thumbnail_image);

  void OnHovercardImagesEnabledChanged();
  void OnHovercardMemoryUsageEnabledChanged();

  bool waiting_for_preview() const {
    return thumbnail_wait_state_ != ThumbnailWaitState::kNotWaiting;
  }

  views::Widget* GetCardWidget() const;

  // Requests that the Widget close. Provides an abstraction for migration to
  // the client-owned widget model.
  void CloseCardWidget();

  //  Resets controller state associated with a HoverCard widget.
  void OnCardClosing();

  void OnLockCreated();
  void OnLockDestroyed();

  bool start_hover_card_fade_ = false;

  // Timestamp of the last time the hover card is hidden by the mouse leaving
  // the tab strip. This is used for reshowing the hover card without delay if
  // the mouse reenters within a given amount of time.
  base::TimeTicks last_mouse_exit_timestamp_;

  raw_ptr<HoverCardAnchorTarget> target_tab_ = nullptr;
  const raw_ptr<TabStripRegionView> tab_strip_;
  raw_ptr<BrowserWindowInterface> browser_window_interface_;
  raw_ptr<TabHoverCardBubbleView> hover_card_ = nullptr;
  base::ScopedObservation<views::View, views::ViewObserver>
      hover_card_observation_{this};
  base::ScopedObservation<views::View, views::ViewObserver>
      target_tab_observation_{this};
  std::unique_ptr<EventSniffer> event_sniffer_;

  // These are used to track when a hover card is shown on a new tab for
  // testing purposes. Counts cards seen from the time the first card is shown
  // to a tab is selected, or the hover card is shown from scratch again.
  raw_ptr<const void> hover_card_last_seen_on_tab_ = nullptr;
  size_t hover_cards_seen_count_ = 0;

  // Fade animations interfere with browser tests so we disable them in tests.
  static bool disable_animations_for_testing_;
  std::unique_ptr<views::WidgetFadeAnimator> fade_animator_;

  // Used to animate the tab hover card's movement between tabs.
  std::unique_ptr<views::BubbleSlideAnimator> slide_animator_;

  std::unique_ptr<TabHoverCardThumbnailObserver> thumbnail_observer_;
  base::CallbackListSubscription thumbnail_subscription_;
  ThumbnailWaitState thumbnail_wait_state_ = ThumbnailWaitState::kNotWaiting;

  base::CallbackListSubscription fade_complete_subscription_;
  base::CallbackListSubscription slide_progressed_subscription_;
  base::CallbackListSubscription slide_complete_subscription_;

  // Ensure that an instance of the TabResourceUsageCollector exists so
  // resources are up to date when we eventually show the hover card.
  raw_ptr<TabResourceUsageCollector> tab_resource_usage_collector_;

  // Observation of `tab_resource_usage_collector_`, active when `hover_card_`
  // exists.
  base::ScopedObservation<TabResourceUsageCollector,
                          TabResourceUsageCollector::Observer>
      tab_resource_usage_collector_observation_{this};

  // Tracks changes to the hover card preferences
  PrefChangeRegistrar pref_change_registrar_;
  bool hover_card_image_previews_enabled_ = false;
  bool hover_card_memory_usage_enabled_ = false;

  int hide_hover_card_lock_count_ = 0;

  // Ensure that this timer is destroyed before anything else is cleaned up.
  base::OneShotTimer delayed_show_timer_;
  base::WeakPtrFactory<TabHoverCardController> weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_HOVERCARD_TAB_HOVER_CARD_CONTROLLER_H_
