// Copyright 2023 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_HATS_HATS_SERVICE_DESKTOP_H_
#define CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_

#include <optional>
#include <set>
#include <string>
#include <string_view>

#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "content/public/browser/web_contents_observer.h"

class BrowserWindowInterface;
class PrefService;

// Key-value mapping type for survey's product specific bits data.
typedef std::map<std::string, bool> SurveyBitsData;

// Key-value mapping type for survey's product specific string data.
typedef std::map<std::string, std::string> SurveyStringData;

// The name of the histogram which records if a survey was shown, or if not, the
// reason why not.
extern const char kHatsShouldShowSurveyReasonHistogram[];

// This class provides the client side logic for determining if a
// survey should be shown for any trigger based on input from a finch
// configuration. It is created on a per profile basis.
class HatsServiceDesktop : public HatsService {
 public:
  class DelayedSurveyTask : public content::WebContentsObserver {
   public:
    DelayedSurveyTask(HatsServiceDesktop* hats_service,
                      std::string trigger,
                      content::WebContents* web_contents,
                      const SurveyBitsData& product_specific_bits_data,
                      const SurveyStringData& product_specific_string_data,
                      NavigationBehavior navigation_behavior,
                      base::OnceClosure success_callback,
                      base::OnceClosure failure_callback,
                      std::optional<std::string_view> supplied_trigger_id);

    // Not copyable or movable
    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;

    ~DelayedSurveyTask() override;

    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
    // |web_contents_|.
    void Launch();

    // content::WebContentsObserver
    void DidFinishNavigation(
        content::NavigationHandle* navigation_handle) override;
    void WebContentsDestroyed() override;

    // Returns a weak pointer to this object.
    virtual base::WeakPtr<DelayedSurveyTask> GetWeakPtr();

    const std::string& trigger() const { return trigger_; }

    bool operator<(const HatsServiceDesktop::DelayedSurveyTask& other) const {
      return trigger_ < other.trigger_ ? true
                                       : web_contents() < other.web_contents();
    }

   private:
    raw_ptr<HatsServiceDesktop> hats_service_;

    std::string trigger_;
    SurveyBitsData product_specific_bits_data_;
    SurveyStringData product_specific_string_data_;
    NavigationBehavior navigation_behavior_;
    base::OnceClosure success_callback_;
    base::OnceClosure failure_callback_;
    std::optional<std::string> supplied_trigger_id_;
    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
  };

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class ShouldShowSurveyReasons {
    kYes = 0,
    kNoOffline = 1,
    kNoLastSessionCrashed = 2,
    kNoReceivedSurveyInCurrentMilestone = 3,
    kNoProfileTooNew = 4,
    kNoLastSurveyTooRecent = 5,
    kNoBelowProbabilityLimit = 6,
    kNoTriggerStringMismatch = 7,
    kNoWrongBrowserType = 8,
    kNoIncognitoDisabled = 9,
    kNoCookiesBlocked = 10,            // Unused.
    kNoThirdPartyCookiesBlocked = 11,  // Unused.
    kNoSurveyUnreachable = 12,
    kNoSurveyOverCapacity = 13,
    kNoSurveyAlreadyInProgress = 14,
    kNoAnyLastSurveyTooRecent = 15,
    kNoRejectedByHatsService = 16,
    kNoLastSurveyCheckTooRecent = 17,
    kMaxValue = kNoLastSurveyCheckTooRecent,
  };

  explicit HatsServiceDesktop(Profile* profile);

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

  ~HatsServiceDesktop() override;

  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);

  using HatsService::LaunchSurvey;
  LaunchError LaunchSurvey(
      const std::string& trigger,
      base::OnceClosure success_callback,
      base::OnceClosure failure_callback,
      const SurveyBitsData& product_specific_bits_data,
      const SurveyStringData& product_specific_string_data,
      const std::optional<std::string>& supplied_trigger_id,
      const SurveyOptions& survey_options) override;

  using HatsService::LaunchSurveyForWebContents;
  LaunchError LaunchSurveyForWebContents(
      const std::string& trigger,
      content::WebContents* web_contents,
      const SurveyBitsData& product_specific_bits_data,
      const SurveyStringData& product_specific_string_data,
      base::OnceClosure success_callback,
      base::OnceClosure failure_callback,
      const std::optional<std::string>& supplied_trigger_id,
      const SurveyOptions& survey_options) override;

  using HatsService::LaunchDelayedSurvey;
  LaunchError LaunchDelayedSurvey(
      const std::string& trigger,
      int timeout_ms,
      const SurveyBitsData& product_specific_bits_data,
      const SurveyStringData& product_specific_string_data) override;

  using HatsService::LaunchDelayedSurveyForWebContents;
  LaunchError LaunchDelayedSurveyForWebContents(
      const std::string& trigger,
      content::WebContents* web_contents,
      int timeout_ms,
      const SurveyBitsData& product_specific_bits_data,
      const SurveyStringData& product_specific_string_data,
      NavigationBehavior navigation_behavior,
      base::OnceClosure success_callback,
      base::OnceClosure failure_callback,
      const std::optional<std::string>& supplied_trigger_id,
      const SurveyOptions& survey_options) override;

  void SetSurveyMetadataForTesting(const HatsService::SurveyMetadata& metadata);
  void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;

  bool HasPendingTasks();

  bool CanShowSurvey(const std::string& trigger) const override;

  bool CanShowAnySurvey(bool user_prompted) const override;

  void RecordSurveyAsShown(std::string trigger_id) override;

  // Indicates to the service that the HaTS Next dialog has been closed.
  virtual void HatsNextDialogClosed();

  // Returns whether a HaTS Next dialog currently exists, regardless of whether
  // it is being shown or not.
  bool hats_next_dialog_exists_for_testing() {
    return hats_next_dialog_exists_;
  }

 protected:
 private:
  FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);

  PrefService* GetPrefsForHatsMetadata() const;

  // Remove |task| from the set of |pending_tasks_|.
  void RemoveTask(const DelayedSurveyTask& task);

  // Returns the launch error for the given trigger and browser, performing all
  // checks.
  LaunchError RunLaunchChecks(BrowserWindowInterface* browser,
                              const std::string& trigger) const;

  // Helper for CanShowSurvey, performing browser-independent checks (except
  // probability).
  LaunchError RunCommonLaunchChecks(const std::string& trigger) const;

  // Returns true if the requested browser type matches the actual browser type.
  bool IsRightBrowserType(
      BrowserWindowInterface* browser,
      hats::SurveyConfig::RequestedBrowserType requested_browser_type) const;

  // Shows the survey after checking all conditions are met.
  LaunchError ShowSurvey(
      BrowserWindowInterface* browser,
      const std::string& trigger,
      base::OnceClosure success_callback,
      base::OnceClosure failure_callback,
      const SurveyBitsData& product_specific_bits_data,
      const SurveyStringData& product_specific_string_data,
      const std::optional<std::string_view>& supplied_trigger_id);

  std::set<DelayedSurveyTask> pending_tasks_;

  // Whether a HaTS Next dialog currently exists (regardless of whether it
  // is being shown to the user).
  bool hats_next_dialog_exists_ = false;

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

#endif  // CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
