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

#include "chrome/browser/extensions/api/developer_private/developer_private_event_router.h"

#include <memory>
#include <set>
#include <utility>

#include "base/check.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/developer_private/profile_info_generator.h"
#include "chrome/browser/extensions/error_console/error_console.h"
#include "chrome/browser/extensions/extension_allowlist_factory.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/sync/extension_sync_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/developer_private.h"
#include "chrome/common/pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/sync/base/features.h"
#include "content/public/browser/render_frame_host.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_allowlist.h"
#include "extensions/browser/extension_error.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/manifest_v2_handler.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/service_worker/worker_id.h"
#include "extensions/browser/ui_util.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/browser/warning_service.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/command.h"
#include "extensions/common/extension_id.h"

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace developer = api::developer_private;

// static
developer::UserSiteSettings
DeveloperPrivateEventRouter::ConvertToUserSiteSettings(
    const PermissionsManager::UserPermissionsSettings& settings) {
  api::developer_private::UserSiteSettings user_site_settings;
  user_site_settings.permitted_sites.reserve(settings.permitted_sites.size());
  for (const auto& origin : settings.permitted_sites) {
    user_site_settings.permitted_sites.push_back(origin.Serialize());
  }

  user_site_settings.restricted_sites.reserve(settings.restricted_sites.size());
  for (const auto& origin : settings.restricted_sites) {
    user_site_settings.restricted_sites.push_back(origin.Serialize());
  }

  return user_site_settings;
}

DeveloperPrivateEventRouter::DeveloperPrivateEventRouter(Profile* profile)
    : profile_(profile), event_router_(EventRouter::Get(profile_)) {
  extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_));
  error_console_observation_.Observe(ErrorConsole::Get(profile));
  process_manager_observation_.Observe(ProcessManager::Get(profile));
  extension_prefs_observation_.Observe(ExtensionPrefs::Get(profile));
  warning_service_observation_.Observe(WarningService::Get(profile));
  permissions_manager_observation_.Observe(PermissionsManager::Get(profile));
  extension_management_observation_.Observe(
      ExtensionManagementFactory::GetForBrowserContext(profile));
  extension_allowlist_observer_.Observe(
      ExtensionAllowlistFactory::GetForBrowserContext(profile));
  command_service_observation_.Observe(CommandService::Get(profile));
  toolbar_actions_model_observation_.Observe(ToolbarActionsModel::Get(profile));
#if BUILDFLAG(ENABLE_PLATFORM_APPS)
  app_window_registry_observation_.Observe(AppWindowRegistry::Get(profile));
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

  pref_change_registrar_.Init(profile->GetPrefs());
  // The unretained is safe, since the PrefChangeRegistrar unregisters the
  // callback on destruction.
  pref_change_registrar_.Add(
      prefs::kExtensionsUIDeveloperMode,
      base::BindRepeating(&DeveloperPrivateEventRouter::OnProfilePrefChanged,
                          base::Unretained(this)));
  pref_change_registrar_.Add(
      prefs::kExtensionsPinnedByDefault,
      base::BindRepeating(&DeveloperPrivateEventRouter::OnProfilePrefChanged,
                          base::Unretained(this)));
  pref_change_registrar_.Add(
      ManifestV2Handler::kMV2UnsupportedAcknowledgedGloballyPref.name,
      base::BindRepeating(&DeveloperPrivateEventRouter::OnProfilePrefChanged,
                          base::Unretained(this)));

#if BUILDFLAG(IS_CHROMEOS)
  if (base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos))
#endif
  {
    account_extension_tracker_observation_.Observe(
        AccountExtensionTracker::Get(profile));
  }
}

DeveloperPrivateEventRouter::~DeveloperPrivateEventRouter() = default;

void DeveloperPrivateEventRouter::AddExtensionId(
    const ExtensionId& extension_id) {
  extension_ids_.insert(extension_id);
}

void DeveloperPrivateEventRouter::RemoveExtensionId(
    const ExtensionId& extension_id) {
  extension_ids_.erase(extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionLoaded(
    content::BrowserContext* browser_context,
    const Extension* extension) {
  DCHECK(
      profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));
  BroadcastItemStateChanged(developer::EventType::kLoaded, extension->id());
}

void DeveloperPrivateEventRouter::OnExtensionUnloaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UnloadedExtensionReason reason) {
  DCHECK(
      profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));
  BroadcastItemStateChanged(developer::EventType::kUnloaded, extension->id());
}

void DeveloperPrivateEventRouter::OnExtensionInstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    bool is_update) {
  DCHECK(
      profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));
  BroadcastItemStateChanged(developer::EventType::kInstalled, extension->id());
}

void DeveloperPrivateEventRouter::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    extensions::UninstallReason reason) {
  DCHECK(
      profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));
  if (ui_util::ShouldDisplayInExtensionSettings(*extension)) {
    BroadcastItemStateChanged(developer::EventType::kUninstalled,
                              extension->id());
  }
}

void DeveloperPrivateEventRouter::OnErrorAdded(const ExtensionError* error) {
  // We don't want to handle errors thrown by extensions subscribed to these
  // events (currently only the Apps Developer Tool), because doing so risks
  // entering a loop.
  if (extension_ids_.count(error->extension_id())) {
    return;
  }

  BroadcastItemStateChanged(developer::EventType::kErrorAdded,
                            error->extension_id());
}

void DeveloperPrivateEventRouter::OnExtensionConfigurationChanged(
    const ExtensionId& extension_id) {
  BroadcastItemStateChanged(developer::EventType::kConfigurationChanged,
                            extension_id);
}

void DeveloperPrivateEventRouter::OnErrorsRemoved(
    const std::set<ExtensionId>& removed_ids) {
  for (const ExtensionId& id : removed_ids) {
    if (!extension_ids_.count(id)) {
      BroadcastItemStateChanged(developer::EventType::kErrorsRemoved, id);
    }
  }
}

void DeveloperPrivateEventRouter::OnExtensionFrameRegistered(
    const ExtensionId& extension_id,
    content::RenderFrameHost* render_frame_host) {
  BroadcastItemStateChanged(developer::EventType::kViewRegistered,
                            extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionFrameUnregistered(
    const ExtensionId& extension_id,
    content::RenderFrameHost* render_frame_host) {
  BroadcastItemStateChanged(developer::EventType::kViewUnregistered,
                            extension_id);
}

void DeveloperPrivateEventRouter::OnStartedTrackingServiceWorkerInstance(
    const WorkerId& worker_id) {
  BroadcastItemStateChanged(developer::EventType::kServiceWorkerStarted,
                            worker_id.extension_id);
}

void DeveloperPrivateEventRouter::OnStoppedTrackingServiceWorkerInstance(
    content::BrowserContext& browser_context,
    const WorkerId& worker_id) {
  BroadcastItemStateChanged(developer::EventType::kServiceWorkerStopped,
                            worker_id.extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionDisableReasonsChanged(
    const ExtensionId& extension_id,
    DisableReasonSet disable_reasons) {
  BroadcastItemStateChanged(developer::EventType::kPrefsChanged, extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionRuntimePermissionsChanged(
    const ExtensionId& extension_id) {
  BroadcastItemStateChanged(developer::EventType::kPermissionsChanged,
                            extension_id);
}

void DeveloperPrivateEventRouter::ExtensionWarningsChanged(
    const ExtensionIdSet& affected_extensions) {
  for (const ExtensionId& id : affected_extensions) {
    BroadcastItemStateChanged(developer::EventType::kWarningsChanged, id);
  }
}

void DeveloperPrivateEventRouter::OnUserPermissionsSettingsChanged(
    const PermissionsManager::UserPermissionsSettings& settings) {
  developer::UserSiteSettings user_site_settings =
      ConvertToUserSiteSettings(settings);
  base::ListValue args;
  args.Append(user_site_settings.ToValue());

  auto event = std::make_unique<Event>(
      events::DEVELOPER_PRIVATE_ON_USER_SITE_SETTINGS_CHANGED,
      developer::OnUserSiteSettingsChanged::kEventName, std::move(args));
  event_router_->BroadcastEvent(std::move(event));
}

void DeveloperPrivateEventRouter::OnExtensionPermissionsUpdated(
    const Extension& extension,
    const PermissionSet& permissions,
    PermissionsManager::UpdateReason reason) {
  BroadcastItemStateChanged(developer::EventType::kPermissionsChanged,
                            extension.id());
}

void DeveloperPrivateEventRouter::OnExtensionManagementSettingsChanged() {
  base::ListValue args;
  args.Append(CreateProfileInfo(profile_).ToValue());

  auto event = std::make_unique<Event>(
      events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED,
      developer::OnProfileStateChanged::kEventName, std::move(args));
  event_router_->BroadcastEvent(std::move(event));
}

void DeveloperPrivateEventRouter::OnExtensionAllowlistWarningStateChanged(
    const ExtensionId& extension_id,
    bool show_warning) {
  BroadcastItemStateChanged(developer::EventType::kPrefsChanged, extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionCommandAdded(
    const ExtensionId& extension_id,
    const std::string& command_name) {
  BroadcastItemStateChanged(developer::EventType::kCommandAdded, extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionCommandRemoved(
    const ExtensionId& extension_id,
    const std::string& command_name) {
  BroadcastItemStateChanged(developer::EventType::kCommandRemoved,
                            extension_id);
}

void DeveloperPrivateEventRouter::OnExtensionUploadabilityChanged(
    const ExtensionId& id) {
  BroadcastItemStateChanged(developer::EventType::kPrefsChanged, id);
}

void DeveloperPrivateEventRouter::OnExtensionsUploadabilityChanged() {
  const ExtensionSet extensions =
      ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
  for (const auto& extension : extensions) {
    if (sync_util::ShouldSync(profile_, extension.get())) {
      BroadcastItemStateChanged(developer::EventType::kPrefsChanged,
                                extension->id());
    }
  }
}

void DeveloperPrivateEventRouter::OnProfilePrefChanged() {
  base::ListValue args;
  args.Append(CreateProfileInfo(profile_).ToValue());
  auto event = std::make_unique<Event>(
      events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED,
      developer::OnProfileStateChanged::kEventName, std::move(args));
  event_router_->BroadcastEvent(std::move(event));

  // The following properties are updated when dev mode is toggled.
  //   - error_collection.is_enabled
  //   - error_collection.is_active
  //   - runtime_errors
  //   - manifest_errors
  //   - install_warnings
  // An alternative approach would be to factor out the dev mode state from the
  // above properties and allow the UI control what happens when dev mode
  // changes. If the UI rendering performance is an issue, instead of replacing
  // the entire extension info, a diff of the old and new extension info can be
  // made by the UI and only perform a partial update of the extension info.
  const ExtensionSet& extensions =
      ExtensionRegistry::Get(profile_)->enabled_extensions();
  for (const auto& extension : extensions) {
    BroadcastItemStateChanged(developer::EventType::kPrefsChanged,
                              extension->id());
  }
}

void DeveloperPrivateEventRouter::BroadcastItemStateChanged(
    developer::EventType event_type,
    const ExtensionId& extension_id) {
  auto info_generator = std::make_unique<ExtensionInfoGenerator>(profile_);
  ExtensionInfoGenerator* info_generator_weak = info_generator.get();
  info_generator_weak->CreateExtensionInfo(
      extension_id,
      base::BindOnce(
          &DeveloperPrivateEventRouter::BroadcastItemStateChangedHelper,
          weak_factory_.GetWeakPtr(), event_type, extension_id,
          std::move(info_generator)));
}

void DeveloperPrivateEventRouter::BroadcastItemStateChangedHelper(
    developer::EventType event_type,
    const ExtensionId& extension_id,
    std::unique_ptr<ExtensionInfoGenerator> info_generator,
    ExtensionInfoGenerator::ExtensionInfoList infos) {
  DCHECK_LE(infos.size(), 1u);

  developer::EventData event_data;
  event_data.event_type = event_type;
  event_data.item_id = extension_id;
  if (!infos.empty()) {
    event_data.extension_info = std::move(infos[0]);
  }

  base::ListValue args;
  args.Append(event_data.ToValue());
  auto event = std::make_unique<Event>(
      events::DEVELOPER_PRIVATE_ON_ITEM_STATE_CHANGED,
      developer::OnItemStateChanged::kEventName, std::move(args));
  event_router_->BroadcastEvent(std::move(event));
}

void DeveloperPrivateEventRouter::OnToolbarPinnedActionsChanged() {
  // Currently, only enabled extensions are considered since they are the only
  // ones that have extension actions.
  // TODO(crbug.com/40280426): Since pinned info is stored as a pref, include
  // disabled extensions in this event as well.
  const ExtensionSet& extensions =
      ExtensionRegistry::Get(profile_)->enabled_extensions();
  for (const auto& extension : extensions) {
    if (ui_util::ShouldDisplayInExtensionSettings(*extension)) {
      BroadcastItemStateChanged(developer::EventType::kPinnedActionsChanged,
                                extension->id());
    }
  }
}

#if BUILDFLAG(ENABLE_PLATFORM_APPS)
void DeveloperPrivateEventRouter::OnAppWindowAdded(AppWindow* window) {
  BroadcastItemStateChanged(developer::EventType::kViewRegistered,
                            window->extension_id());
}

void DeveloperPrivateEventRouter::OnAppWindowRemoved(AppWindow* window) {
  BroadcastItemStateChanged(developer::EventType::kViewUnregistered,
                            window->extension_id());
}
#endif  // BUILDFLAG(ENABLE_PLATFORM_APPS)

}  // namespace extensions
