// Copyright 2012 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/policy/policy_ui_handler.h"

#include <stddef.h>

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/feature.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/enterprise/reporting/cloud_profile_reporting_service.h"
#include "chrome/browser/enterprise/reporting/cloud_profile_reporting_service_factory.h"
#include "chrome/browser/enterprise/util/affiliation.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/policy/policy_ui_utils.h"
#include "chrome/browser/policy/policy_value_and_status_aggregator.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/schema_registry_service.h"
#include "chrome/browser/policy/status_provider/status_provider_util.h"
#include "chrome/browser/policy/value_provider/chrome_policies_value_provider.h"
#include "chrome/browser/policy/value_provider/value_provider_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/select_file_policy/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/policy/policy_ui.h"
#include "chrome/common/channel_info.h"
#include "chrome/grit/branded_strings.h"
#include "components/crx_file/id_util.h"
#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#include "components/enterprise/browser/reporting/common_pref_names.h"
#include "components/policy/core/browser/configuration_policy_handler_list.h"
#include "components/policy/core/browser/policy_conversions.h"
#include "components/policy/core/browser/webui/json_generation.h"
#include "components/policy/core/browser/webui/policy_webui_constants.h"
#include "components/policy/core/browser/webui/statistics_collector.h"
#include "components/policy/core/common/cloud/cloud_policy_manager.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
#include "components/policy/core/common/cloud/enterprise_metrics.h"
#include "components/policy/core/common/local_test_policy_loader.h"
#include "components/policy/core/common/local_test_policy_provider.h"
#include "components/policy/core/common/policy_details.h"
#include "components/policy/core/common/policy_logger.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/policy/core/common/policy_scheduler.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/policy_utils.h"
#include "components/policy/core/common/remote_commands/remote_commands_fetch_reason.h"
#include "components/policy/core/common/remote_commands/remote_commands_service.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/core/common/schema_map.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "extensions/buildflags/buildflags.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/webui/webui_util.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
#include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/policy/off_hours/device_off_hours_controller.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "components/user_manager/user_manager.h"
#else
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#endif

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/enterprise/identifiers/profile_id_service_factory.h"
#include "components/enterprise/browser/identifiers/profile_id_service.h"
#endif  // !BUILDFLAG(IS_ANDROID)

// LINT.IfChange

namespace {

// Key under which extension policies are grouped in JSON policy exports.
constexpr char kExtensionsKey[] = "extensions";

}  // namespace

PolicyUIHandler::PolicyUIHandler(Profile* profile) : profile_(*profile) {}

PolicyUIHandler::PolicyUIHandler(
    mojo::PendingReceiver<policy::mojom::PolicyPageHandler> receiver,
    mojo::PendingRemote<policy::mojom::PolicyPageClient> client,
    Profile* profile)
    : receiver_(this, std::move(receiver)),
      client_(std::move(client)),
      profile_(*profile) {
  policy_value_and_status_aggregator_ = policy::PolicyValueAndStatusAggregator::
      CreateDefaultPolicyValueAndStatusAggregator(&profile_.get());
}

PolicyUIHandler::~PolicyUIHandler() {
  policy::RecordPolicyUIButtonUsage(reload_policies_count_,
                                    export_to_json_count_, copy_to_json_count_,
                                    upload_report_count_);
}

void PolicyUIHandler::AddCommonLocalizedStringsToSource(
    content::WebUIDataSource* source) {
  source->AddLocalizedStrings(policy::kPolicySources);

  static constexpr webui::LocalizedString kStrings[] = {
      {"conflict", IDS_POLICY_LABEL_CONFLICT},
      {"superseding", IDS_POLICY_LABEL_SUPERSEDING},
      {"conflictValue", IDS_POLICY_LABEL_CONFLICT_VALUE},
      {"supersededValue", IDS_POLICY_LABEL_SUPERSEDED_VALUE},
      {"headerLevel", IDS_POLICY_HEADER_LEVEL},
      {"headerName", IDS_POLICY_HEADER_NAME},
      {"headerScope", IDS_POLICY_HEADER_SCOPE},
      {"headerSource", IDS_POLICY_HEADER_SOURCE},
      {"headerStatus", IDS_POLICY_HEADER_STATUS},
      {"headerValue", IDS_POLICY_HEADER_VALUE},
      {"warning", IDS_POLICY_HEADER_WARNING},
      {"levelMandatory", IDS_POLICY_LEVEL_MANDATORY},
      {"levelRecommended", IDS_POLICY_LEVEL_RECOMMENDED},
      {"error", IDS_POLICY_LABEL_ERROR},
      {"deprecated", IDS_POLICY_LABEL_DEPRECATED},
      {"future", IDS_POLICY_LABEL_FUTURE},
      {"info", IDS_POLICY_LABEL_INFO},
      {"ignored", IDS_POLICY_LABEL_IGNORED},
      {"ignoredByExtension", IDS_POLICY_IGNORED_EXTENSION},
      {"notSpecified", IDS_POLICY_NOT_SPECIFIED},
      {"ok", IDS_POLICY_OK},
      {"restartRequired", IDS_POLICY_RESTART_REQUIRED},
      {"scopeDevice", IDS_POLICY_SCOPE_DEVICE},
      {"scopeUser", IDS_POLICY_SCOPE_USER},
      {"scopeAllUsers", IDS_POLICY_SCOPE_ALL_USERS},
      {"title", IDS_POLICY_TITLE},
      {"unknown", IDS_POLICY_UNKNOWN},
      {"unset", IDS_POLICY_UNSET},
      {"value", IDS_POLICY_LABEL_VALUE},
      {"sourceDefault", IDS_POLICY_SOURCE_DEFAULT},
      {"reloadingPolicies", IDS_POLICY_RELOADING_POLICIES},
      {"reloadPoliciesDone", IDS_POLICY_RELOAD_POLICIES_DONE},
      {"copyPoliciesDone", IDS_COPY_POLICIES_DONE},
      {"exportPoliciesDone", IDS_EXPORT_POLICIES_JSON_DONE},
      {"sort", IDS_POLICY_TABLE_COLUMN_SORT},
      {"sortAscending", IDS_POLICY_TABLE_COLUMN_SORT_ASCENDING},
      {"sortDescending", IDS_POLICY_TABLE_COLUMN_SORT_DESCENDING},
#if !BUILDFLAG(IS_CHROMEOS)
      {"reportUploading", IDS_REPORT_UPLOADING},
      {"reportUploaded", IDS_REPORT_UPLOADED},
#endif  // !BUILDFLAG(IS_CHROMEOS)
  };
  source->AddLocalizedStrings(kStrings);

  source->UseStringsJs();
}

void PolicyUIHandler::RegisterMessages() {
  auto update_callback(base::BindRepeating(&PolicyUIHandler::SendStatus,
                                           base::Unretained(this)));
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(g_browser_process->local_state());
  pref_change_registrar_->Add(
      enterprise_reporting::kLastUploadSucceededTimestamp, update_callback);

  policy_value_and_status_aggregator_ = policy::PolicyValueAndStatusAggregator::
      CreateDefaultPolicyValueAndStatusAggregator(&profile_.get());
  policy_value_and_status_observation_.Observe(
      policy_value_and_status_aggregator_.get());

  const auto* policy_schema_registry_service =
      profile_->GetPolicySchemaRegistryService();
  // In case web_ui() represents an OffTheRecordProfileImpl object (like in a
  // guest session), there's no PolicySchemaRegistryService, so nothing to
  // observe there. The profile has no policies anyway.
  if (policy_schema_registry_service) {
    schema_registry_observation_.Observe(
        policy_schema_registry_service->registry());
  }

  web_ui()->RegisterMessageCallback(
      "listenPoliciesUpdates",
      base::BindRepeating(&PolicyUIHandler::HandleListenPoliciesUpdates,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "reloadPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleReloadPolicies,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "setLocalTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleSetLocalTestPolicies,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "revertLocalTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleRevertLocalTestPolicies,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "getPolicyLogs",
      base::BindRepeating(&PolicyUIHandler::HandleGetPolicyLogs,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "restartBrowser",
      base::BindRepeating(&PolicyUIHandler::HandleRestartBrowser,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setUserAffiliation",
      base::BindRepeating(&PolicyUIHandler::HandleSetUserAffiliated,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getAppliedTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleGetAppliedTestPolicies,
                          base::Unretained(this)));

#if !BUILDFLAG(IS_ANDROID)
  web_ui()->RegisterMessageCallback(
      "shouldShowPromotion",
      base::BindRepeating(&PolicyUIHandler::HandleShouldShowPromotion,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "setBannerDismissed",
      base::BindRepeating(&PolicyUIHandler::HandleSetBannerDismissed,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "recordBannerRedirected",
      base::BindRepeating(&PolicyUIHandler::HandleRecordBannerRedirected,
                          base::Unretained(this)));
#endif

  web_ui()->RegisterMessageCallback(
      "getPoliciesJson",
      base::BindRepeating(&PolicyUIHandler::HandleGetPoliciesJson,
                          base::Unretained(this)));
#if !BUILDFLAG(IS_CHROMEOS)
  web_ui()->RegisterMessageCallback(
      "uploadReport", base::BindRepeating(&PolicyUIHandler::HandleUploadReport,
                                          base::Unretained(this)));
#endif  // !BUILDFLAG(IS_CHROMEOS)
}

void PolicyUIHandler::OnPolicyValueAndStatusChanged() {
  SendPolicies();
  // Send also the status to UI because when policy value is updated, policy
  // status also might be updated and PolicyStatusProviders may not be listening
  // this change.
  SendStatus();
}

void PolicyUIHandler::OnSchemaRegistryUpdated(bool has_new_schemas) {
  SendSchema();
}

void PolicyUIHandler::SendSchema() {
  if (!IsJavascriptAllowed() ||
      !PolicyUI::ShouldLoadTestPage(&profile_.get())) {
    return;
  }
  FireWebUIListener("schema-updated", PolicyUI::GetSchema(&profile_.get()));
}

void PolicyUIHandler::HandleListenPoliciesUpdates(const base::ListValue& args) {
  // Send initial policy values and status to UI page.
  AllowJavascript();
  SendSchema();
  SendPolicies();
  SendStatus();
}

void PolicyUIHandler::HandleReloadPolicies(const base::ListValue&) {
  reload_policies_count_ += 1;
#if BUILDFLAG(IS_CHROMEOS)
  // Allow user to manually fetch remote commands. Useful for testing or when
  // the invalidation service is not working properly.
  policy::CloudPolicyManager* const device_manager =
      g_browser_process->platform_part()
          ->browser_policy_connector_ash()
          ->GetDeviceCloudPolicyManager();
  policy::CloudPolicyManager* const user_manager =
      profile_->GetUserCloudPolicyManagerAsh();

  // Fetch both device and user remote commands.
  for (policy::CloudPolicyManager* manager : {device_manager, user_manager}) {
    // Active Directory management has no CloudPolicyManager.
    if (manager) {
      policy::RemoteCommandsService* const remote_commands_service =
          manager->core()->remote_commands_service();
      if (remote_commands_service) {
        remote_commands_service->FetchRemoteCommands(
            policy::RemoteCommandsFetchReason::kUserRequest);
      }
    }
  }
#endif
  policy_value_and_status_aggregator_->Refresh();
}

void PolicyUIHandler::HandleSetLocalTestPolicies(const base::ListValue& args) {
  CHECK_EQ(args.size(), 3u);
  const std::string& policies = args[1].GetString();
  const std::string& profile_separation_policy_response = args[2].GetString();
  SetLocalTestPoliciesImpl(policies, profile_separation_policy_response);
  AllowJavascript();
  ResolveJavascriptCallback(args[0], true);
}

void PolicyUIHandler::SetLocalTestPolicies(
    const std::string& policies,
    const std::string& profile_separation_policy_response,
    SetLocalTestPoliciesCallback callback) {
  SetLocalTestPoliciesImpl(policies, profile_separation_policy_response);
  std::move(callback).Run();
}

void PolicyUIHandler::SetLocalTestPoliciesImpl(
    const std::string& policies,
    const std::string& profile_separation_policy_response) {
  if (!PolicyUI::ShouldLoadTestPage(&profile_.get())) {
    return;
  }

  policy::LocalTestPolicyProvider* local_test_provider =
      static_cast<policy::LocalTestPolicyProvider*>(
          g_browser_process->browser_policy_connector()
              ->local_test_policy_provider());
  CHECK(local_test_provider);

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
  profile_->GetPrefs()->ClearPref(
      prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage);
  profile_->GetPrefs()->SetDefaultPrefValue(
      prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage,
      base::Value(profile_separation_policy_response));
#endif

  profile_->GetProfilePolicyConnector()->UseLocalTestPolicyProvider();

  local_test_provider->LoadJsonPolicies(policies);
}

void PolicyUIHandler::HandleRevertLocalTestPolicies(
    const base::ListValue& args) {
  RevertLocalTestPolicies();
}

void PolicyUIHandler::RevertLocalTestPolicies() {
  if (!PolicyUI::ShouldLoadTestPage(&profile_.get())) {
    return;
  }
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
  profile_->GetPrefs()->ClearPref(
      prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage);
  profile_->GetPrefs()->SetDefaultPrefValue(
      prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage,
      base::Value(std::string()));
#endif
  profile_->GetProfilePolicyConnector()->RevertUseLocalTestPolicyProvider();
}

void PolicyUIHandler::HandleRestartBrowser(const base::ListValue& args) {
  CHECK_EQ(args.size(), 1u);
  const std::string& policies = args[0].GetString();
  RestartBrowser(policies);
}

void PolicyUIHandler::RestartBrowser(const std::string& policies) {
  if (!PolicyUI::ShouldLoadTestPage(&*profile_)) {
    return;
  }

  // Set policies to preference
  PrefService* prefs = g_browser_process->local_state();
  prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
                   policies);

  // Restart browser
  chrome::AttemptRestart();
}

void PolicyUIHandler::HandleSetUserAffiliated(const base::ListValue& args) {
  CHECK_EQ(args.size(), 2u);
  bool affiliated = args[1].GetBool();
  SetUserAffiliatedImpl(affiliated);
  AllowJavascript();
  ResolveJavascriptCallback(args[0], true);
}

void PolicyUIHandler::SetUserAffiliated(bool affiliated,
                                        SetUserAffiliatedCallback callback) {
  SetUserAffiliatedImpl(affiliated);
  std::move(callback).Run();
}

void PolicyUIHandler::SetUserAffiliatedImpl(bool affiliated) {
  auto* local_test_provider = static_cast<policy::LocalTestPolicyProvider*>(
      g_browser_process->browser_policy_connector()
          ->local_test_policy_provider());
  local_test_provider->SetUserAffiliated(affiliated);
}

void PolicyUIHandler::HandleGetAppliedTestPolicies(
    const base::ListValue& args) {
  CHECK_EQ(args.size(), 1u);
  AllowJavascript();
  ResolveJavascriptCallback(args[0], GetAppliedTestPoliciesImpl());
}

void PolicyUIHandler::GetAppliedTestPolicies(
    GetAppliedTestPoliciesCallback callback) {
  std::move(callback).Run(GetAppliedTestPoliciesImpl());
}

const std::string& PolicyUIHandler::GetAppliedTestPoliciesImpl() {
  auto* local_test_provider = static_cast<policy::LocalTestPolicyProvider*>(
      g_browser_process->browser_policy_connector()
          ->local_test_policy_provider());
  return local_test_provider->GetPolicies();
}

void PolicyUIHandler::HandleGetPolicyLogs(const base::ListValue& args) {
  AllowJavascript();
  ResolveJavascriptCallback(args[0],
                            policy::PolicyLogger::GetInstance()->GetAsList());
}

void PolicyUIHandler::GetPolicyLogs(GetPolicyLogsCallback callback) {
  std::move(callback).Run(policy::PolicyLogger::GetInstance()->GetAsMojoList());
}

#if !BUILDFLAG(IS_CHROMEOS)
void PolicyUIHandler::HandleUploadReport(const base::ListValue& args) {
  upload_report_count_ += 1;
  DCHECK_EQ(1u, args.size());
  const std::string& callback_id = args[0].GetString();
  auto* report_scheduler = g_browser_process->browser_policy_connector()
                               ->chrome_browser_cloud_management_controller()
                               ->report_scheduler();

  auto* cloud_profile_reporting_service =
      enterprise_reporting::CloudProfileReportingServiceFactory::GetForProfile(
          Profile::FromWebUI(web_ui()));
  auto* profile_report_scheduler =
      cloud_profile_reporting_service
          ? cloud_profile_reporting_service->report_scheduler()
          : nullptr;

  if (report_scheduler && profile_report_scheduler) {
    const auto on_report_uploaded = base::BarrierClosure(
        2, base::BindOnce(&PolicyUIHandler::OnReportUploaded,
                          weak_factory_.GetWeakPtr(), callback_id));
    report_scheduler->UploadReport(on_report_uploaded);
    profile_report_scheduler->UploadReport(on_report_uploaded);
    return;
  }

  if (report_scheduler) {
    report_scheduler->UploadReport(
        base::BindOnce(&PolicyUIHandler::OnReportUploaded,
                       weak_factory_.GetWeakPtr(), callback_id));
    return;
  }

  if (profile_report_scheduler) {
    profile_report_scheduler->UploadReport(
        base::BindOnce(&PolicyUIHandler::OnReportUploaded,
                       weak_factory_.GetWeakPtr(), callback_id));
    return;
  }

  // TODO(335639255): Consider disable the button when neither report
  // scheduler are ready. On at least show an error message to ask people
  // to try again.
  OnReportUploaded(callback_id);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

void PolicyUIHandler::SendPolicies() {
  if (!IsJavascriptAllowed()) {
    return;
  }
  FireWebUIListener(
      "policies-updated",
      base::Value(
          policy_value_and_status_aggregator_->GetAggregatedPolicyNames()),
      base::Value(
          policy_value_and_status_aggregator_->GetAggregatedPolicyValues()));
}

void PolicyUIHandler::SendStatus() {
  if (IsMojoMigrationEnabled()) {
    client_->StatusUpdated(
        policy_value_and_status_aggregator_->GetAggregatedPolicyStatusMojo());
  } else {
    if (!IsJavascriptAllowed()) {
      return;
    }

    FireWebUIListener(
        "status-updated",
        policy_value_and_status_aggregator_->GetAggregatedPolicyStatus());
  }
}

#if !BUILDFLAG(IS_ANDROID)
void PolicyUIHandler::OnPromotionEligibilityFetchedWebUiWrapper(
    base::Value callback_id,
    bool response) {
  AllowJavascript();
  ResolveJavascriptCallback(callback_id, response);
}

void PolicyUIHandler::HandleShouldShowPromotion(const base::ListValue& args) {
  CheckPromotionEligibility(base::BindOnce(
      &PolicyUIHandler::OnPromotionEligibilityFetchedWebUiWrapper,
      weak_factory_.GetWeakPtr(), args[0].Clone()));
}

void PolicyUIHandler::CheckPromotionEligibility(
    CheckPromotionEligibilityCallback callback) {
  bool dismissed_banner_pref = profile_->GetPrefs()->GetBoolean(
      policy::policy_prefs::kHasDismissedPolicyPagePromotionBanner);

  promotion_eligibility_checker_ = policy::CreatePromotionEligibilityChecker(
      &profile_.get(), dismissed_banner_pref, /*feature_enabled=*/true);
  if (!promotion_eligibility_checker_) {
    OnPromotionEligibilityFetched(
        std::move(callback),
        enterprise_management::GetUserEligiblePromotionsResponse());
    return;
  }
  promotion_eligibility_checker_->MaybeCheckPromotionEligibility(
      base::BindOnce(&PolicyUIHandler::OnPromotionEligibilityFetched,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
  return;
}

void PolicyUIHandler::HandleSetBannerDismissed(const base::ListValue& args) {
  SetBannerDismissed();
}

void PolicyUIHandler::SetBannerDismissed() {
  base::UmaHistogramEnumeration(
      "Enterprise.PolicyPromotionBannerAction",
      policy::PolicyPromotionBannerAction::kBannerDismissed);
  profile_->GetPrefs()->SetBoolean(
      policy::policy_prefs::kHasDismissedPolicyPagePromotionBanner, true);
}

void PolicyUIHandler::HandleRecordBannerRedirected(
    const base::ListValue& args) {
  RecordBannerRedirected();
}

void PolicyUIHandler::RecordBannerRedirected() {
  base::UmaHistogramEnumeration(
      "Enterprise.PolicyPromotionBannerAction",
      policy::PolicyPromotionBannerAction::kBannerRedirected);
}
#endif  // !BUILDFLAG(IS_ANDROID)

#if !BUILDFLAG(IS_CHROMEOS)
void PolicyUIHandler::OnReportUploaded(const std::string& callback_id) {
  if (!IsJavascriptAllowed()) {
    return;
  }
  ResolveJavascriptCallback(base::Value(callback_id),
                            /*response=*/base::Value());
  SendStatus();
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

#if !BUILDFLAG(IS_ANDROID)
void PolicyUIHandler::OnPromotionEligibilityFetched(
    CheckPromotionEligibilityCallback callback,
    enterprise_management::GetUserEligiblePromotionsResponse response) {
  bool should_show_promotion = response.promotions().policy_page_promotion() ==
                               enterprise_management::CHROME_ENTERPRISE_CORE;
  // Log the UMA metric for the promotion banner displayed.
  base::UmaHistogramBoolean("Enterprise.PolicyPromotionBannerDisplayed",
                            should_show_promotion);
  std::move(callback).Run(should_show_promotion);
}
#endif  // !BUILDFLAG(IS_ANDROID)

std::string PolicyUIHandler::GetPoliciesJsonImpl(
    policy::mojom::GetPoliciesReason reason) {
  if (reason == policy::mojom::GetPoliciesReason::kCopy) {
    copy_to_json_count_ += 1;
  } else if (reason == policy::mojom::GetPoliciesReason::kExport) {
    export_to_json_count_ += 1;
  }

  base::DictValue policy_values =
      policy_value_and_status_aggregator_->GetAggregatedPolicyValues();
  policy_values.Remove(policy::kPolicyIdsKey);
  base::DictValue* extensions_dict =
      policy_values.FindDict(policy::kPolicyValuesKey)
          ->EnsureDict(kExtensionsKey);

  // Iterate through all policy headings to identify extension policies.
  for (auto entry : *policy_values.FindDict(policy::kPolicyValuesKey)) {
    if (crx_file::id_util::IdIsValid(entry.first)) {
      extensions_dict->Set(entry.first, base::DictValue());
    }
  }

  // Extract identified extension policies into their own category.
  for (auto entry : *extensions_dict) {
    extensions_dict->Set(entry.first,
                         policy_values.FindDict(policy::kPolicyValuesKey)
                             ->Extract(entry.first)
                             .value_or(base::Value()));
  }

  return policy::GenerateJson(
      std::move(policy_values),
      policy_value_and_status_aggregator_->GetAggregatedPolicyStatus(),
      /*params=*/
      policy::GetChromeMetadataParams(
          /*application_name=*/l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)));
}

void PolicyUIHandler::GetDebugString(GetDebugStringCallback callback) {
  std::move(callback).Run("Migrating chrome://policy to mojo!");
}

void PolicyUIHandler::HandleGetPoliciesJson(const base::ListValue& args) {
  policy::mojom::GetPoliciesReason reason =
      static_cast<policy::mojom::GetPoliciesReason>(args[1].GetInt());
  AllowJavascript();
  ResolveJavascriptCallback(args[0], GetPoliciesJsonImpl(reason));
}

void PolicyUIHandler::GetPoliciesJson(policy::mojom::GetPoliciesReason reason,
                                      GetPoliciesJsonCallback callback) {
  std::move(callback).Run(GetPoliciesJsonImpl(reason));
}

// LINT.ThenChange(//ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm)
