// 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/extensions/preference/preference_helpers.h"

#include <memory>
#include <utility>

#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_helper.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/permissions/permissions_data.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {
namespace preference_helpers {

namespace {

constexpr char kNotControllable[] = "not_controllable";
constexpr char kControlledByOtherExtensions[] =
    "controlled_by_other_extensions";
constexpr char kControllableByThisExtension[] =
    "controllable_by_this_extension";
constexpr char kControlledByThisExtension[] = "controlled_by_this_extension";

constexpr char kLevelOfControlKey[] = "levelOfControl";

}  // namespace

using LevelOfControlGetter =
    base::RepeatingCallback<const char*(Profile*,
                                        const ExtensionId& extension_id,
                                        const std::string& browser_pref,
                                        bool incognito)>;

PrefService* GetProfilePrefService(Profile* profile, bool incognito) {
  if (incognito) {
    if (profile->HasPrimaryOTRProfile()) {
      return profile->GetPrimaryOTRProfile(/*create_if_needed=*/false)
          ->GetPrefs();
    }
    return profile->GetReadOnlyOffTheRecordPrefs();
  }

  return profile->GetPrefs();
}

const char* GetLevelOfControl(Profile* profile,
                              const ExtensionId& extension_id,
                              const std::string& browser_pref,
                              bool incognito) {
  PrefService* prefs = GetProfilePrefService(profile, incognito);
  bool from_incognito = false;
  bool* from_incognito_ptr = incognito ? &from_incognito : nullptr;
  const PrefService::Preference* pref = prefs->FindPreference(browser_pref);
  if (!pref->IsExtensionModifiable())
    return kNotControllable;

  if (ExtensionPrefsHelper::Get(profile)->DoesExtensionControlPref(
          extension_id, browser_pref, from_incognito_ptr)) {
    return kControlledByThisExtension;
  }

  if (ExtensionPrefsHelper::Get(profile)->CanExtensionControlPref(
          extension_id, browser_pref, incognito)) {
    return kControllableByThisExtension;
  }

  return kControlledByOtherExtensions;
}

void DispatchEventToExtensionsImpl(Profile* profile,
                                   events::HistogramValue histogram_value,
                                   const std::string& event_name,
                                   base::ListValue args,
                                   mojom::APIPermissionID permission,
                                   bool incognito,
                                   const std::string& browser_pref,
                                   const LevelOfControlGetter level_getter) {
  EventRouter* router = EventRouter::Get(profile);
  if (!router || !router->HasEventListener(event_name))
    return;

  for (const scoped_refptr<const extensions::Extension>& extension :
       ExtensionRegistry::Get(profile)->enabled_extensions()) {
    // TODO(bauerb): Only iterate over registered event listeners.
    if (router->ExtensionHasEventListener(extension->id(), event_name) &&
        extension->permissions_data()->HasAPIPermission(permission) &&
        (!incognito || util::IsIncognitoEnabled(extension->id(), profile))) {
      // Inject level of control key-value.
      DCHECK(!args.empty());
      DCHECK(args[0].is_dict());

      std::string level_of_control =
          level_getter.Run(profile, extension->id(), browser_pref, incognito);

      args[0].GetDict().Set(kLevelOfControlKey, level_of_control);

      // If the extension is in incognito split mode,
      // a) incognito pref changes are visible only to the incognito tabs
      // b) regular pref changes are visible only to the incognito tabs if the
      //    incognito pref has not already been set
      Profile* restrict_to_profile = nullptr;
      if (IncognitoInfo::IsSplitMode(extension.get())) {
        if (incognito) {  // Handle case a).
          // If off the record profile does not exist, there should be no
          // extensions running in incognito at this time, and consequentially
          // no need to dispatch an event restricted to an incognito extension.
          // Furthermore, avoid calling GetPrimaryOTRProfile() if the profile
          // does not exist. Unnecessarily creating off the record profile is
          // undesirable, and can lead to a crash if incognito is disallowed for
          // the current profile (see https://crbug.com/41361877).
          if (!profile->HasPrimaryOTRProfile())
            continue;
          restrict_to_profile =
              profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
        } else {  // Handle case b).
          bool controlled_from_incognito = false;
          bool controlled_by_extension =
              ExtensionPrefsHelper::Get(profile)->DoesExtensionControlPref(
                  extension->id(), browser_pref, &controlled_from_incognito);
          if (controlled_by_extension && controlled_from_incognito)
            restrict_to_profile = profile;
        }
      }

      base::ListValue args_copy = args.Clone();
      auto event =
          std::make_unique<Event>(histogram_value, event_name,
                                  std::move(args_copy), restrict_to_profile);
      router->DispatchEventToExtension(extension->id(), std::move(event));
    }
  }
}

void DispatchEventToExtensions(Profile* profile,
                               events::HistogramValue histogram_value,
                               const std::string& event_name,
                               base::ListValue args,
                               mojom::APIPermissionID permission,
                               bool incognito,
                               const std::string& browser_pref) {
  DispatchEventToExtensionsImpl(
      profile, histogram_value, event_name, std::move(args), permission,
      incognito, browser_pref, base::BindRepeating(GetLevelOfControl));
}
}  // namespace preference_helpers
}  // namespace extensions
