// Copyright 2022 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_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UI_CONTROLLER_H_
#define CHROME_BROWSER_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UI_CONTROLLER_H_

#include <optional>
#include <set>

#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/download/bubble/download_bubble_update_service.h"
#include "chrome/browser/download/bubble/download_display_controller.h"
#include "chrome/browser/download/download_warning_desktop_hats_utils.h"
#include "chrome/browser/download/offline_item_model.h"
#include "chrome/browser/metrics/browser_activity_watcher.h"
#include "components/download/content/public/all_download_item_notifier.h"
#include "components/offline_items_collection/core/offline_content_aggregator.h"
#include "components/offline_items_collection/core/offline_content_provider.h"

class BrowserWindowInterface;
class Profile;

namespace offline_items_collection {
struct ContentId;
}

// This handles the window-level logic for controlling the download bubble.
// There is one instance of this class per browser window, and it is owned by
// the download toolbar button.
class DownloadBubbleUIController {
 public:
  // Get a valid controller for the given `download`. In the case of web apps,
  // this will always be the web app window's controller. For regular downloads,
  // this could be the controller for the most recently active window associated
  // with this profile.
  static DownloadBubbleUIController* GetForDownload(
      download::DownloadItem* download);

  explicit DownloadBubbleUIController(BrowserWindowInterface* browser);
  // Used to inject a custom DownloadBubbleUpdateService for testing. Prefer
  // the constructor above which uses that of the profile.
  DownloadBubbleUIController(BrowserWindowInterface* browser,
                             DownloadBubbleUpdateService* update_service);

  DownloadBubbleUIController(const DownloadBubbleUIController&) = delete;
  DownloadBubbleUIController& operator=(const DownloadBubbleUIController&) =
      delete;
  virtual ~DownloadBubbleUIController();

  // These methods are called to notify the UI of new events.
  // |may_show_animation| is whether the window this controller belongs to may
  // show the animation. (Whether the animation is actually shown may depend on
  // the download and the device's graphics capabilities.) We don't show an
  // animation for offline items. Notifications for created/added download items
  // generally come from the DownloadUIController(Delegate) (except for crx
  // downloads, which come via the DownloadBubbleUpdateService), and the rest
  // are called from DownloadBubbleUpdateService.
  void OnDownloadItemAdded(download::DownloadItem* item,
                           bool may_show_animation);
  void OnDownloadItemUpdated(download::DownloadItem* item);
  void OnDownloadItemRemoved(download::DownloadItem* item);
  void OnOfflineItemsAdded(
      const OfflineContentProvider::OfflineItemList& items);
  void OnOfflineItemUpdated(const OfflineItem& item);
  void OnOfflineItemRemoved(const ContentId& id);
  void OnOfflineItemsInitialized();

  // Get the entries for the main view of the Download Bubble. The main view
  // contains all the recent downloads (finished within the last 24 hours).
  // Virtual for testing.
  virtual std::vector<DownloadUIModel::DownloadUIModelPtr> GetMainView();

  // Get the entries for the partial view of the Download Bubble. The partial
  // view contains in-progress and uninteracted downloads, meant to capture the
  // user's recent tasks. This can only be opened by the browser in the event of
  // new downloads, and user action only creates a main view.
  // Virtual for testing.
  virtual std::vector<DownloadUIModel::DownloadUIModelPtr> GetPartialView();

  void SetLastPartialViewShownTimeForTesting(std::optional<base::Time> time);

  // Process button press on the bubble.
  // May launch a HaTS survey if the action applies to a download warning.
  // TODO(chlily): `is_main_view` should be named `is_primary_view`. It
  // distinguishes the primary page from the (security) subpage, not the main vs
  // partial flavors of the primary view.
  void ProcessDownloadButtonPress(base::WeakPtr<DownloadUIModel> model,
                                  DownloadCommands::Command command,
                                  bool is_main_view);

  // Notify when a download toolbar button (in any window) is pressed.
  void HandleButtonPressed();

  // Schedules the ephemeral warning download to be hidden from the bubble, and
  // subsequently canceled. It will only be canceled if it continues to be an
  // ephemeral warning that hasn't been acted on when the scheduled time
  // arrives.
  void ScheduleCancelForEphemeralWarning(const std::string& guid);

  // Force the controller to hide the download UI entirely, including the bubble
  // and the toolbar icon. This function should only be called if the event is
  // triggered outside of normal download events that are not listened by
  // observers.
  void HideDownloadUi();

  // Records that a dangerous download was shown to the user. This only
  // records the fact that an interaction occurred, and should not be
  // used quantitatively to count the number of such interactions.
  void RecordDangerousDownloadShownToUser(download::DownloadItem* download);

  // Returns the DownloadDisplayController. Should always return a valid
  // controller.
  DownloadDisplayController* GetDownloadDisplayController() {
    return display_controller_;
  }

  void SetDownloadDisplayController(DownloadDisplayController* controller) {
    display_controller_ = controller;
  }

  DownloadBubbleUpdateService* update_service() { return update_service_; }

  // See comment on member below. This may not be correct/meaningful, do not
  // rely on this for anything important. This is not meaningful if the partial
  // view is not enabled.
  bool last_primary_view_was_partial() const {
    return last_primary_view_was_partial_;
  }

  base::WeakPtr<DownloadBubbleUIController> GetWeakPtr();

 private:
  friend class DownloadBubbleUIControllerTest;
  friend class DownloadBubbleUIControllerIncognitoTest;

  // Common method for getting main and partial views.
  std::vector<DownloadUIModel::DownloadUIModelPtr> GetDownloadUIModels(
      bool is_main_view);

  // Kick off retrying an eligible interrupted download.
  void RetryDownload(DownloadUIModel* model, DownloadCommands::Command command);

  // Stamps the PSD for HaTS surveys with the extra info specific to the
  // download bubble triggers.
  void CompleteHatsPsd(DownloadWarningHatsProductSpecificData& psd);

  // Callback for `browser_activity_observer_`.
  void OnBrowserActivity();

  raw_ptr<BrowserWindowInterface, DanglingUntriaged> browser_;
  raw_ptr<Profile, DanglingUntriaged> profile_;
  raw_ptr<DownloadBubbleUpdateService, DanglingUntriaged> update_service_;
  raw_ptr<OfflineItemModelManager, DanglingUntriaged> offline_manager_;

  // DownloadDisplayController and DownloadBubbleUIController have the same
  // lifetime. Both are owned, constructed together, and destructed together by
  // DownloadToolbarButtonView. If one is valid, so is the other.
  raw_ptr<DownloadDisplayController, AcrossTasksDanglingUntriaged>
      display_controller_;

  std::optional<base::Time> last_partial_view_shown_time_;

  // Tracks whether the last time we provided models was for a partial view
  // (true) or a main view (false). This is an approximation for whether the
  // last incarnation of the download bubble that the user saw or interacted
  // with was a partial or main view. (It's only an approximation because there
  // are controllers for other browsers that are not accounted for here.) In
  // most cases, this should be correct and meaningful if queried immediately
  // after the user interacted with / clicked on the bubble. This value might
  // be bogus if the download bubble is shown on multiple browsers at the same
  // time, or if the primary view is bypassed altogether (e.g. by clicking on
  // a desktop notification on ChromeOS to go to the security view directly).
  bool last_primary_view_was_partial_ = false;

  // Used for showing HaTS surveys when download warnings are delayed.
  // Nullptr when the user is not eligible for download bubble warning ignored
  // surveys.
  std::unique_ptr<DelayedDownloadWarningHatsLauncher> delayed_hats_launcher_;
  std::unique_ptr<BrowserActivityWatcher> browser_activity_watcher_;

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

#endif  // CHROME_BROWSER_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UI_CONTROLLER_H_
