// Copyright 2025 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_GLIC_SUGGESTIONS_CONTEXTUAL_CUEING_SERVICE_H_
#define CHROME_BROWSER_GLIC_SUGGESTIONS_CONTEXTUAL_CUEING_SERVICE_H_

#include <optional>
#include <string>
#include <vector>

#include "base/callback_list.h"
#include "base/containers/lru_cache.h"
#include "base/containers/queue.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "chrome/browser/glic/suggestions/contextual_cueing_enums.h"
#include "components/contextual_cueing/nudge_cap_tracker.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/page_content_annotations/content/page_content_extraction_service.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "url/gurl.h"
#include "url/origin.h"

class OptimizationGuideKeyedService;
class PrefService;
class TemplateURLService;

namespace signin {
class IdentityManager;
}

namespace content {
class WebContents;
}  // namespace content

namespace predictors {
class LoadingPredictor;
}  // namespace predictors

namespace glic {

enum class GlicNudgeActivity;
class ZeroStateSuggestionsRequest;

using GlicSuggestionsCallbackList =
    base::OnceCallbackList<void(std::vector<std::string>)>;
using GlicSuggestionsCallback = GlicSuggestionsCallbackList::CallbackType;

class ContextualCueingService
    : public KeyedService,
      page_content_annotations::PageContentExtractionService::Observer {
 public:
  ContextualCueingService(
      page_content_annotations::PageContentExtractionService*
          page_content_extraction_service,
      OptimizationGuideKeyedService* optimization_guide_keyed_service,
      predictors::LoadingPredictor* loading_predictor,
      signin::IdentityManager* identity_manager,
      PrefService* pref_service,
      TemplateURLService* template_url_service);
  ~ContextualCueingService() override;

  // Reports a page load happened to `url`, and is used to keep track of quiet
  // page loads requirement after a cueing UI is shown.
  void ReportPageLoad();

  // Called when cueing nudge activity happens.
  void OnNudgeActivity(base::WeakPtr<content::WebContents> web_contents,
                       base::TimeTicks document_available_time,
                       bool is_dynamic,
                       glic::GlicNudgeActivity activity);

  // Should be called when the cueing UI is shown for the tab with `url`.
  void CueingNudgeShown(const GURL& url);

  // Should be called when the cueing UI is dismissed by the user.
  void CueingNudgeDismissed();

  // Should be called when the nudge is clicked on by the user.
  void CueingNudgeClicked();

  // Returns if a nudge should be shown and is not blocked by feature
  // engagement constraints for navigation to `url`, and if not, why.
  NudgeDecision CanShowNudge(const GURL& url);

  base::WeakPtr<ContextualCueingService> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  // Informs `this` to prepare fetching for zero state suggestions for GLIC.
  // Note that this *will not* actually do the fetch and it is intended for the
  // caller to call `GetContextualGlicZeroStateSuggestions` to actually fetch
  // the suggestions.
  void PrepareToFetchContextualGlicZeroStateSuggestions(
      content::WebContents* web_contents);

  // Returns zero state suggestions for focused tab for GLIC. Virtual for
  // testing.
  virtual void GetContextualGlicZeroStateSuggestionsForFocusedTab(
      content::WebContents* web_contents,
      bool is_fre,
      std::optional<std::vector<std::string>> supported_tools,
      GlicSuggestionsCallback callback);

  // Returns whether suggestions are going to be generated. Invokes `callback`
  // with zero state suggestions for pinned tabs as represented by
  // `pinned_web_contents`. Virtual for testing.
  virtual bool GetContextualGlicZeroStateSuggestionsForPinnedTabs(
      std::vector<raw_ptr<content::WebContents>> pinned_web_contents,
      bool is_fre,
      std::optional<std::vector<std::string>> supported_tools,
      const content::WebContents* focused_tab,
      GlicSuggestionsCallback callback);

  // Returns the pinned tabs that are in an outstanding request if there is one.
  std::optional<std::vector<raw_ptr<content::WebContents>>>
  GetOutstandingPinnedTabsContents();

 private:
  // page_content_annotations::PageContentExtractionService::Observer:
  void OnPageContentExtracted(
      content::Page& page,
      page_content_annotations::PageContent page_content) override;

  // Returns true if nudge should not be shown due to the backoff rule.
  bool IsNudgeBlockedByBackoffRule() const;

  // Returns true if the given url is of a page type eligible for contextual
  // suggestions.
  bool IsPageTypeEligibleForContextualSuggestions(GURL url) const;

  // Utility method to create the initial zero state suggestions request.
  std::unique_ptr<ZeroStateSuggestionsRequest> MakeZeroStateSuggestionsRequest(
      const std::vector<raw_ptr<content::WebContents>>& web_contents_list,
      bool is_fre,
      std::optional<std::vector<std::string>> supported_tools,
      const content::WebContents* focused_tab);

  // Callback invoked when pinned tabs suggestions are received.
  void OnPinnedTabsSuggestionsReceived(
      bool is_fre,
      base::TimeTicks fetch_begin_time,
      base::WeakPtr<ZeroStateSuggestionsRequest> pinned_tabs_request,
      GlicSuggestionsCallback callback,
      std::vector<std::string> suggestions);

  // Tracker to limit the number of nudges shown over a certain duration.
  contextual_cueing::NudgeCapTracker recent_nudge_tracker_;

  // Number of times the cueing nudge has been dismissed (i.e. closed by the
  // user). This count resets to 0 if nudge is clicked on by the user.
  int dismiss_count_ = 0;

  // The end of the backoff period triggered by the last nudge dismissal.
  std::optional<base::TimeTicks> dismiss_backoff_end_time_;

  // The end of the backoff period triggered by the last shown nudge.
  std::optional<base::TimeTicks> shown_backoff_end_time_;

  // A counter for how many subsequent page load events will be prevented from
  // showing a nudge. This is to limit the frequency at which consecutive page
  // loads can trigger nudges.
  size_t remaining_quiet_loads_ = 0;

  // Maintains the recently visited origins along with their nudge cap tracking.
  base::LRUCache<url::Origin, contextual_cueing::NudgeCapTracker>
      recent_visited_origins_;

  // Holds the latest pinned tabs zero state suggestions request.
  std::unique_ptr<ZeroStateSuggestionsRequest>
      pinned_tabs_zero_state_suggestions_request_;

  raw_ptr<page_content_annotations::PageContentExtractionService>
      page_content_extraction_service_ = nullptr;

  raw_ptr<OptimizationGuideKeyedService> optimization_guide_keyed_service_ =
      nullptr;

  raw_ptr<predictors::LoadingPredictor> loading_predictor_ = nullptr;

  raw_ptr<PrefService> pref_service_ = nullptr;

  raw_ptr<TemplateURLService> template_url_service_ = nullptr;

  raw_ptr<signin::IdentityManager> identity_manager_ = nullptr;

  // Stores model execution url to save look up time.
  GURL mes_url_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<ContextualCueingService> weak_ptr_factory_{this};
};

}  // namespace glic

#endif  // CHROME_BROWSER_GLIC_SUGGESTIONS_CONTEXTUAL_CUEING_SERVICE_H_
