// 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.

#include "chrome/browser/ui/webui/settings/safety_hub_handler.h"

#include <memory>
#include <string_view>

#include "base/check.h"
#include "base/json/values_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_checker.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/cws_info_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/desktop_to_mobile_promos/ios_promo_trigger_service.h"
#include "chrome/browser/ui/desktop_to_mobile_promos/ios_promo_trigger_service_factory.h"
#include "chrome/browser/ui/safety_hub/extensions_result.h"
#include "chrome/browser/ui/safety_hub/menu_notification_service_factory.h"
#include "chrome/browser/ui/safety_hub/notification_permission_review_service.h"
#include "chrome/browser/ui/safety_hub/notification_permission_review_service_factory.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service_factory.h"
#include "chrome/browser/ui/safety_hub/revoked_permissions_service.h"
#include "chrome/browser/ui/safety_hub/revoked_permissions_service_factory.h"
#include "chrome/browser/ui/safety_hub/safe_browsing_result.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_hats_service.h"
#include "chrome/browser/ui/safety_hub/safety_hub_hats_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_result.h"
#include "chrome/browser/ui/safety_hub/safety_hub_util.h"
#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
#include "chrome/browser/ui/webui/version/version_ui.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "components/desktop_to_mobile_promos/features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/permissions/constants.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "extensions/browser/cws_info_service.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/manifest.h"
#include "safety_hub_handler.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "url/gurl.h"

using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using safety_hub::SafetyHubCardState;

namespace {

const char kRevocationTypeKey[] = "revocation_type";

// Get values from |UnusedSitePermissions| object in
// safety_hub_browser_proxy.ts.
PermissionsData GetUnusedSitePermissionsFromDict(
    const base::DictValue& unused_site_permissions) {
  PermissionsData permissions_data;
  const std::string* origin_str =
      unused_site_permissions.FindString(site_settings::kOrigin);
  CHECK(origin_str);
  permissions_data.primary_pattern =
      ContentSettingsPattern::FromString(*origin_str);

  const base::ListValue* permissions =
      unused_site_permissions.FindList(site_settings::kPermissions);
  CHECK(permissions);
  for (const auto& permission : *permissions) {
    CHECK(permission.is_dict());
    const std::string* type_string =
        permission.GetDict().FindString(site_settings::kType);
    CHECK(type_string);
    const base::Value* setting_value =
        permission.GetDict().Find(site_settings::kSettingValue);
    ContentSettingsType type =
        site_settings::ContentSettingsTypeFromGroupName(*type_string);
    CHECK(type != ContentSettingsType::DEFAULT)
        << type_string << " is not expected to have a UI representation.";
    permissions_data.permissions.insert(std::make_pair(
        type, setting_value ? setting_value->Clone() : base::Value()));
  }

  // Handle expiration and lifetime for revoked permission.
  const base::Value* js_expiration =
      unused_site_permissions.Find(safety_hub::kExpirationKey);
  CHECK(js_expiration);
  base::Time expiration = base::ValueToTime(js_expiration).value();

  const base::Value* js_lifetime =
      unused_site_permissions.Find(safety_hub::kLifetimeKey);
  base::TimeDelta lifetime = content_settings::RuleMetaData::ComputeLifetime(
      /*lifetime=*/
      base::ValueToTimeDelta(js_lifetime).value_or(base::TimeDelta()),
      /*expiration=*/expiration);
  permissions_data.constraints =
      content_settings::ContentSettingConstraints(expiration - lifetime);
  permissions_data.constraints.set_lifetime(lifetime);

  permissions_data.revocation_type = static_cast<PermissionsRevocationType>(
      unused_site_permissions.FindInt(kRevocationTypeKey).value_or(0));

  return permissions_data;
}

// Returns true if the card dict indicates there is something actionable for the
// user.
bool CardHasRecommendations(base::DictValue card_data) {
  std::optional<int> state = card_data.FindInt(safety_hub::kCardStateKey);
  CHECK(state.has_value());
  SafetyHubCardState card_state =
      static_cast<SafetyHubCardState>(state.value());

  return card_state == SafetyHubCardState::kWarning ||
         card_state == SafetyHubCardState::kWeak;
}

void AppendModuleNameToString(std::u16string& str,
                              int uppercase_id,
                              int lowercase_id = 0) {
  if (str.empty()) {
    str.append(l10n_util::GetStringUTF16(uppercase_id));
    return;
  }

  if (lowercase_id == 0) {
    lowercase_id = uppercase_id;
  }

  str.append(
      l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR));
  str.append(u" ");
  str.append(l10n_util::GetStringUTF16(lowercase_id));
}

// Converts the entry point data into a base::DictValue.
base::DictValue EntryPointDataToValue(bool has_recommendations,
                                      std::string header,
                                      std::string subheader) {
  base::DictValue dict_data;

  dict_data.Set("hasRecommendations", has_recommendations);
  dict_data.Set("header", header);
  dict_data.Set("subheader", subheader);

  return dict_data;
}
}  // namespace

SafetyHubHandler::SafetyHubHandler(Profile* profile)
    : profile_(profile), clock_(base::DefaultClock::GetInstance()) {
  prefs_observation_.Observe(ExtensionPrefs::Get(profile_));
  extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_));
  pref_change_registrar_.Init(profile_->GetPrefs());
  pref_change_registrar_.Add(
      prefs::kSafeBrowsingEnhanced,
      base::BindRepeating(&SafetyHubHandler::OnSafeBrowsingEnhancedChanged,
                          base::Unretained(this)));
}
SafetyHubHandler::~SafetyHubHandler() = default;

// static
std::unique_ptr<SafetyHubHandler> SafetyHubHandler::GetForProfile(
    Profile* profile) {
  return std::make_unique<SafetyHubHandler>(profile);
}

void SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList(
    const base::ListValue& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const base::Value& callback_id = args[0];

  base::ListValue result = PopulateUnusedSitePermissionsData();

  ResolveJavascriptCallback(callback_id, base::Value(std::move(result)));
}

void SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  CHECK(args[0].is_string());
  const std::string& origin_str = args[0].GetString();

  RevokedPermissionsService* service =
      RevokedPermissionsServiceFactory::GetForProfile(profile_);
  CHECK(service);

  url::Origin origin = url::Origin::Create(GURL(origin_str));

  service->RegrantPermissionsForOrigin(origin);
  SendUnusedSitePermissionsReviewList();
}

void SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  CHECK(args[0].is_dict());

  PermissionsData permissions_data =
      GetUnusedSitePermissionsFromDict(args[0].GetDict());
  RevokedPermissionsService* service =
      RevokedPermissionsServiceFactory::GetForProfile(profile_);
  CHECK(service);

  service->UndoRegrantPermissionsForOrigin(permissions_data);

  SendUnusedSitePermissionsReviewList();
}

void SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList(
    const base::ListValue& args) {
  RevokedPermissionsService* service =
      RevokedPermissionsServiceFactory::GetForProfile(profile_);
  CHECK(service);
  service->ClearRevokedPermissionsList();

  SendUnusedSitePermissionsReviewList();
}

void SafetyHubHandler::HandleUndoAcknowledgeRevokedUnusedSitePermissionsList(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  CHECK(args[0].is_list());

  const base::ListValue& unused_site_permissions_list = args[0].GetList();
  RevokedPermissionsService* service =
      RevokedPermissionsServiceFactory::GetForProfile(profile_);
  CHECK(service);

  std::vector<PermissionsData> permissions_data_list;
  for (const auto& unused_site_permissions_js : unused_site_permissions_list) {
    CHECK(unused_site_permissions_js.is_dict());
    permissions_data_list.push_back(
        GetUnusedSitePermissionsFromDict(unused_site_permissions_js.GetDict()));
  }
  service->RestoreDeletedRevokedPermissionsList(permissions_data_list);

  SendUnusedSitePermissionsReviewList();
}

base::ListValue SafetyHubHandler::PopulateUnusedSitePermissionsData() {
  base::ListValue result;
  RevokedPermissionsService* service =
      RevokedPermissionsServiceFactory::GetForProfile(profile_);
  CHECK(service);
  std::unique_ptr<RevokedPermissionsResult> service_result =
      service->GetRevokedPermissions();
  for (const auto& permissions_data : service_result->GetRevokedPermissions()) {
    base::DictValue revoked_permission_value;
    revoked_permission_value.Set(site_settings::kOrigin,
                                 permissions_data.primary_pattern.ToString());

    base::ListValue permissions_value_list;
    for (const auto& [type, value] : permissions_data.permissions) {
      std::string_view permission_str =
          site_settings::ContentSettingsTypeToGroupName(type);
      if (!permission_str.empty()) {
        base::DictValue dict;
        dict.Set(site_settings::kType, permission_str);
        dict.Set(site_settings::kSettingValue, value.Clone());
        permissions_value_list.Append(std::move(dict));
      }
    }

    // Some permissions have no readable name, although Safety Hub revokes them.
    // To prevent crashes, if there is no permission to be shown in the UI, the
    // origin will not be added to the revoked permissions list.
    // TODO(crbug.com/40066645): Remove this after adding check for
    // ContentSettingsTypeToGroupName.
    if (permissions_value_list.empty()) {
      continue;
    }

    revoked_permission_value.Set(
        site_settings::kPermissions,
        base::Value(std::move(permissions_value_list)));

    revoked_permission_value.Set(
        safety_hub::kExpirationKey,
        base::TimeToValue(permissions_data.constraints.expiration()));

    revoked_permission_value.Set(
        safety_hub::kLifetimeKey,
        base::TimeDeltaToValue(permissions_data.constraints.lifetime()));

    revoked_permission_value.Set(
        kRevocationTypeKey, static_cast<int>(permissions_data.revocation_type));

    result.Append(std::move(revoked_permission_value));
  }

  return result;
}

void SafetyHubHandler::HandleGetNotificationPermissionReviewList(
    const base::ListValue& args) {
  AllowJavascript();

  const base::Value& callback_id = args[0];

  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  DCHECK(service);

  if (!service) {
    RejectJavascriptCallback(callback_id, base::Value());
  }

  base::ListValue result = service->PopulateNotificationPermissionReviewData();

  ResolveJavascriptCallback(callback_id, base::Value(std::move(result)));
}

void SafetyHubHandler::HandleIgnoreOriginsForNotificationPermissionReview(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  const base::ListValue& origins = args[0].GetList();

  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(service);

  for (const auto& origin : origins) {
    const ContentSettingsPattern primary_pattern =
        ContentSettingsPattern::FromString(origin.GetString());
    service->AddPatternToNotificationPermissionReviewBlocklist(
        primary_pattern, ContentSettingsPattern::Wildcard());
  }

  SendNotificationPermissionReviewList();
}

void SafetyHubHandler::HandleResetNotificationPermissionForOrigins(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  const base::ListValue& origins = args[0].GetList();

  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(service);

  for (const auto& origin : origins) {
    service->SetNotificationPermissionsForOrigin(origin.GetString(),
                                                 CONTENT_SETTING_DEFAULT);
  }

  SendNotificationPermissionReviewList();
}

void SafetyHubHandler::HandleDismissActiveMenuNotification(
    const base::ListValue& args) {
  SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_)
      ->DismissActiveNotification();
}

void SafetyHubHandler::HandleDismissPasswordMenuNotification(
    const base::ListValue& args) {
  SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_)
      ->DismissActiveNotificationOfModule(
          safety_hub::SafetyHubModuleType::PASSWORDS);
}

void SafetyHubHandler::HandleDismissExtensionsMenuNotification(
    const base::ListValue& args) {
  SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_)
      ->DismissActiveNotificationOfModule(
          safety_hub::SafetyHubModuleType::EXTENSIONS);
}

void SafetyHubHandler::HandleBlockNotificationPermissionForOrigins(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  const base::ListValue& origins = args[0].GetList();

  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(service);

  for (const auto& origin : origins) {
    service->SetNotificationPermissionsForOrigin(origin.GetString(),
                                                 CONTENT_SETTING_BLOCK);
  }

  SendNotificationPermissionReviewList();
}

void SafetyHubHandler::HandleAllowNotificationPermissionForOrigins(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  const base::ListValue& origins = args[0].GetList();

  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(service);

  for (const auto& origin : origins) {
    service->SetNotificationPermissionsForOrigin(origin.GetString(),
                                                 CONTENT_SETTING_ALLOW);
  }

  SendNotificationPermissionReviewList();
}

void SafetyHubHandler::HandleUndoIgnoreOriginsForNotificationPermissionReview(
    const base::ListValue& args) {
  CHECK_EQ(1U, args.size());
  const base::ListValue& origins = args[0].GetList();
  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(service);

  for (const auto& origin : origins) {
    const ContentSettingsPattern& primary_pattern =
        ContentSettingsPattern::FromString(origin.GetString());
    service->RemovePatternFromNotificationPermissionReviewBlocklist(
        primary_pattern, ContentSettingsPattern::Wildcard());
  }
  SendNotificationPermissionReviewList();
}

void SafetyHubHandler::HandleGetSafeBrowsingCardData(
    const base::ListValue& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const base::Value& callback_id = args[0];

  ResolveJavascriptCallback(
      callback_id, SafetyHubSafeBrowsingResult::GetSafeBrowsingCardData(
                       profile_->GetPrefs()));
}

void SafetyHubHandler::HandleGetNumberOfExtensionsThatNeedReview(
    const base::ListValue& args) {
  const base::Value& callback_id = args[0];
  AllowJavascript();
  ResolveJavascriptCallback(callback_id,
                            base::Value(GetNumberOfExtensionsThatNeedReview()));
}

void SafetyHubHandler::HandleGetPasswordCardData(const base::ListValue& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const base::Value& callback_id = args[0];

  PasswordStatusCheckService* service =
      PasswordStatusCheckServiceFactory::GetForProfile(profile_);
  CHECK(service);

  ResolveJavascriptCallback(callback_id,
                            base::Value(service->GetPasswordCardData()));
}

void SafetyHubHandler::HandleGetVersionCardData(const base::ListValue& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const base::Value& callback_id = args[0];

  ResolveJavascriptCallback(callback_id,
                            base::Value(safety_hub_util::GetVersionCardData()));
}

void SafetyHubHandler::HandleGetSafetyHubEntryPointData(
    const base::ListValue& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const base::Value& callback_id = args[0];

  std::set<SafetyHubModule> modules = GetSafetyHubModulesWithRecommendations();

  // If there is no module that needs attention, a static string will be used
  // for the subheader.
  if (modules.empty()) {
    ResolveJavascriptCallback(
        callback_id,
        base::Value(EntryPointDataToValue(
            false, "",
            l10n_util::GetStringUTF8(
                IDS_SETTINGS_SAFETY_HUB_ENTRY_POINT_NOTHING_TO_DO))));
    return;
  }

  // Modules in subheader should be added in the following order: Passwords,
  // Version, Safe Browsing, Extensions, Notifications, Permissions.
  std::u16string subheader = u"";

  if (modules.contains(SafetyHubModule::kPasswords)) {
    AppendModuleNameToString(subheader,
                             IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME);
  }

  if (modules.contains(SafetyHubModule::kVersion)) {
    AppendModuleNameToString(
        subheader, IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_UPPERCASE_NAME,
        IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_LOWERCASE_NAME);
  }

  if (modules.contains(SafetyHubModule::kSafeBrowsing)) {
    AppendModuleNameToString(subheader,
                             IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MODULE_NAME);
  }

  if (modules.contains(SafetyHubModule::kExtensions)) {
    AppendModuleNameToString(
        subheader, IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_UPPERCASE_NAME,
        IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_LOWERCASE_NAME);
  }

  if (modules.contains(SafetyHubModule::kNotifications)) {
    AppendModuleNameToString(
        subheader, IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_UPPERCASE_NAME,
        IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_LOWERCASE_NAME);
  }

  if (modules.contains(SafetyHubModule::kUnusedSitePermissions)) {
    AppendModuleNameToString(
        subheader, IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_UPPERCASE_NAME,
        IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_LOWERCASE_NAME);
  }

  ResolveJavascriptCallback(
      callback_id,
      base::Value(EntryPointDataToValue(
          true,
          l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_ENTRY_POINT_HEADER),
          base::UTF16ToUTF8(subheader))));
}

std::set<SafetyHubHandler::SafetyHubModule>
SafetyHubHandler::GetSafetyHubModulesWithRecommendations() {
  std::set<SafetyHubModule> modules;

  // Passwords module
  PasswordStatusCheckService* psc_service =
      PasswordStatusCheckServiceFactory::GetForProfile(profile_);
  CHECK(psc_service);
  if (CardHasRecommendations(psc_service->GetPasswordCardData())) {
    modules.insert(SafetyHubModule::kPasswords);
  }
  // Version module
  if (CardHasRecommendations(safety_hub_util::GetVersionCardData())) {
    modules.insert(SafetyHubModule::kVersion);
  }
  // SafeBrowsing module
  if (CardHasRecommendations(
          SafetyHubSafeBrowsingResult::GetSafeBrowsingCardData(
              profile_->GetPrefs()))) {
    modules.insert(SafetyHubModule::kSafeBrowsing);
  }
  // Extensions module
  if (GetNumberOfExtensionsThatNeedReview() > 0) {
    modules.insert(SafetyHubModule::kExtensions);
  }
  // Notifications module
  NotificationPermissionsReviewService* npr_service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  CHECK(npr_service);
  if (!npr_service->PopulateNotificationPermissionReviewData().empty()) {
    modules.insert(SafetyHubModule::kNotifications);
  }
  // Unused site permission module
  if (!PopulateUnusedSitePermissionsData().empty()) {
    modules.insert(SafetyHubModule::kUnusedSitePermissions);
  }

  return modules;
}

void SafetyHubHandler::HandleRecordSafetyHubVisit(const base::ListValue& args) {
  if (SafetyHubHatsService* hats_service =
          SafetyHubHatsServiceFactory::GetForProfile(profile_)) {
    hats_service->SafetyHubVisited();
  }
}

void SafetyHubHandler::HandleRecordSafetyHubInteraction(
    const base::ListValue& args) {
  if (SafetyHubHatsService* hats_service =
          SafetyHubHatsServiceFactory::GetForProfile(profile_)) {
    hats_service->SafetyHubModuleInteracted();
  }
}

void SafetyHubHandler::RegisterMessages() {
  // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and
  // won't release ownership until destruction.
  web_ui()->RegisterMessageCallback(
      "getRevokedUnusedSitePermissionsList",
      base::BindRepeating(
          &SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "allowPermissionsAgainForUnusedSite",
      base::BindRepeating(
          &SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "undoAllowPermissionsAgainForUnusedSite",
      base::BindRepeating(
          &SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "acknowledgeRevokedUnusedSitePermissionsList",
      base::BindRepeating(
          &SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "undoAcknowledgeRevokedUnusedSitePermissionsList",
      base::BindRepeating(
          &SafetyHubHandler::
              HandleUndoAcknowledgeRevokedUnusedSitePermissionsList,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getNotificationPermissionReview",
      base::BindRepeating(
          &SafetyHubHandler::HandleGetNotificationPermissionReviewList,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "ignoreNotificationPermissionReviewForOrigins",
      base::BindRepeating(
          &SafetyHubHandler::HandleIgnoreOriginsForNotificationPermissionReview,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "resetNotificationPermissionForOrigins",
      base::BindRepeating(
          &SafetyHubHandler::HandleResetNotificationPermissionForOrigins,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "dismissActiveMenuNotification",
      base::BindRepeating(
          &SafetyHubHandler::HandleDismissActiveMenuNotification,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "dismissSafetyHubPasswordMenuNotification",
      base::BindRepeating(
          &SafetyHubHandler::HandleDismissPasswordMenuNotification,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "dismissSafetyHubExtensionsMenuNotification",
      base::BindRepeating(
          &SafetyHubHandler::HandleDismissExtensionsMenuNotification,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "blockNotificationPermissionForOrigins",
      base::BindRepeating(
          &SafetyHubHandler::HandleBlockNotificationPermissionForOrigins,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "allowNotificationPermissionForOrigins",
      base::BindRepeating(
          &SafetyHubHandler::HandleAllowNotificationPermissionForOrigins,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "undoIgnoreNotificationPermissionReviewForOrigins",
      base::BindRepeating(
          &SafetyHubHandler::
              HandleUndoIgnoreOriginsForNotificationPermissionReview,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getSafeBrowsingCardData",
      base::BindRepeating(&SafetyHubHandler::HandleGetSafeBrowsingCardData,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPasswordCardData",
      base::BindRepeating(&SafetyHubHandler::HandleGetPasswordCardData,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getVersionCardData",
      base::BindRepeating(&SafetyHubHandler::HandleGetVersionCardData,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getSafetyHubEntryPointData",
      base::BindRepeating(&SafetyHubHandler::HandleGetSafetyHubEntryPointData,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getNumberOfExtensionsThatNeedReview",
      base::BindRepeating(
          &SafetyHubHandler::HandleGetNumberOfExtensionsThatNeedReview,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "recordSafetyHubPageVisit",
      base::BindRepeating(&SafetyHubHandler::HandleRecordSafetyHubVisit,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "recordSafetyHubInteraction",
      base::BindRepeating(&SafetyHubHandler::HandleRecordSafetyHubInteraction,
                          base::Unretained(this)));
}

void SafetyHubHandler::SendUnusedSitePermissionsReviewList() {
  // Notify observers that the unused site permission review list could have
  // changed. Note that the list is not guaranteed to have changed. In places
  // where determining whether the list has changed is cause for performance
  // concerns, an unchanged list may be sent.
  FireWebUIListener("unused-permission-review-list-maybe-changed",
                    PopulateUnusedSitePermissionsData());
}

void SafetyHubHandler::SendNotificationPermissionReviewList() {
  NotificationPermissionsReviewService* service =
      NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
  if (!service) {
    return;
  }

  // Notify observers that the permission review list could have changed. Note
  // that the list is not guaranteed to have changed.
  FireWebUIListener(
      site_settings::kNotificationPermissionsReviewListMaybeChangedEvent,
      service->PopulateNotificationPermissionReviewData());
}

void SafetyHubHandler::InitSafetyHubExtensionResults() {
  std::optional<std::unique_ptr<SafetyHubResult>> sh_result =
      SafetyHubExtensionsResult::GetResult(profile_, false);
  if (sh_result.has_value()) {
    extension_sh_result_ = std::make_unique<SafetyHubExtensionsResult>(
        *static_cast<SafetyHubExtensionsResult*>(sh_result->get()));
  }
}

int SafetyHubHandler::GetNumberOfExtensionsThatNeedReview() {
  if (!extension_sh_result_) {
    InitSafetyHubExtensionResults();
  }
  if (extension_sh_result_) {
    return extension_sh_result_->GetNumTriggeringExtensions();
  } else {
    return 0;
  }
}

void SafetyHubHandler::UpdateNumberOfExtensionsThatNeedReview(
    int num_extension_need_review_before,
    int num_extension_need_review_after) {
  if (num_extension_need_review_before != num_extension_need_review_after) {
    AllowJavascript();
    FireWebUIListener("extensions-review-list-maybe-changed",
                      num_extension_need_review_after);
  }
}

void SafetyHubHandler::OnExtensionPrefsUpdated(
    const std::string& extension_id) {
  if (!extension_sh_result_) {
    return;
  }
  int num_extension_need_review_before = GetNumberOfExtensionsThatNeedReview();
  extension_sh_result_->OnExtensionPrefsUpdated(extension_id, profile_);
  int num_extension_need_review_after = GetNumberOfExtensionsThatNeedReview();
  UpdateNumberOfExtensionsThatNeedReview(num_extension_need_review_before,
                                         num_extension_need_review_after);
}

void SafetyHubHandler::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const extensions::Extension* extension,
    extensions::UninstallReason reason) {
  if (!extension_sh_result_) {
    return;
  }
  int num_extension_need_review_before = GetNumberOfExtensionsThatNeedReview();
  extension_sh_result_->OnExtensionUninstalled(browser_context, extension,
                                               reason);
  int num_extension_need_review_after = GetNumberOfExtensionsThatNeedReview();
  UpdateNumberOfExtensionsThatNeedReview(num_extension_need_review_before,
                                         num_extension_need_review_after);
}

void SafetyHubHandler::OnExtensionPrefsWillBeDestroyed(ExtensionPrefs* prefs) {
  DCHECK(prefs_observation_.IsObservingSource(prefs));
  prefs_observation_.Reset();
}

void SafetyHubHandler::OnShutdown(extensions::ExtensionRegistry* registry) {
  extension_registry_observation_.Reset();
}

void SafetyHubHandler::SetClockForTesting(base::Clock* clock) {
  clock_ = clock;
}

void SafetyHubHandler::ClearExtensionResultsForTesting() {
  GetNumberOfExtensionsThatNeedReview();
  if (extension_sh_result_) {
    extension_sh_result_->ClearTriggeringExtensionsForTesting();  // IN-TEST
  }
}

void SafetyHubHandler::SetTriggeringExtensionForTesting(
    std::string extension_id) {
  GetNumberOfExtensionsThatNeedReview();
  if (extension_sh_result_) {
    extension_sh_result_->SetTriggeringExtensionForTesting(  // IN-TEST
        extension_id);                                       // IN-TEST
  }
}

void SafetyHubHandler::OnJavascriptAllowed() {}

void SafetyHubHandler::OnJavascriptDisallowed() {}

void SafetyHubHandler::OnSafeBrowsingEnhancedChanged() {
  if (profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnhanced) &&
      MobilePromoOnDesktopTypeEnabled(
          MobilePromoOnDesktopPromoType::kESBPromo)) {
    IOSPromoTriggerService* service =
        IOSPromoTriggerServiceFactory::GetForProfile(profile_);
    if (service) {
      service->NotifyPromoShouldBeShown(
          desktop_to_mobile_promos::PromoType::kEnhancedBrowsing);
    }
  }
}
