// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/install_tracker.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/observer_list.h"
#include "components/prefs/pref_change_registrar.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/pref_names.h"
#include "extensions/buildflags/buildflags.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

InstallTracker::InstallTracker(content::BrowserContext* browser_context,
                               ExtensionPrefs* prefs)
    : browser_context_(browser_context) {
  extension_registry_observation_.Observe(
      ExtensionRegistry::Get(browser_context));

  // Prefs may be null in tests.
  if (prefs) {
    pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
    pref_change_registrar_->Init(prefs->pref_service());
    pref_change_registrar_->Add(
        pref_names::kExtensions,
        base::BindRepeating(&InstallTracker::OnExtensionPrefChanged,
                            base::Unretained(this)));
  }
}

InstallTracker::~InstallTracker() = default;

void InstallTracker::AddObserver(InstallObserver* observer) {
  observers_.AddObserver(observer);
}

void InstallTracker::RemoveObserver(InstallObserver* observer) {
  observers_.RemoveObserver(observer);
}

const ActiveInstallData* InstallTracker::GetActiveInstall(
    const std::string& extension_id) const {
  auto install_data = active_installs_.find(extension_id);
  if (install_data == active_installs_.end())
    return nullptr;
  else
    return &(install_data->second);
}

void InstallTracker::AddActiveInstall(const ActiveInstallData& install_data) {
  DCHECK(!install_data.extension_id.empty());
  DCHECK(active_installs_.find(install_data.extension_id) ==
         active_installs_.end());
  active_installs_.insert(
      std::make_pair(install_data.extension_id, install_data));
}

void InstallTracker::RemoveActiveInstall(const std::string& extension_id) {
  active_installs_.erase(extension_id);
}

void InstallTracker::OnBeginExtensionInstall(
    const InstallObserver::ExtensionInstallParams& params) {
  auto install_data = active_installs_.find(params.extension_id);
  if (install_data == active_installs_.end()) {
    ActiveInstallData active_install_data(params.extension_id);
    active_installs_.insert(
        std::make_pair(params.extension_id, active_install_data));
  }

  for (auto& observer : observers_)
    observer.OnBeginExtensionInstall(browser_context_, params);
}

void InstallTracker::OnBeginExtensionDownload(const std::string& extension_id) {
  for (auto& observer : observers_)
    observer.OnBeginExtensionDownload(browser_context_, extension_id);
}

void InstallTracker::OnDownloadProgress(const std::string& extension_id,
                                        int percent_downloaded) {
  auto install_data = active_installs_.find(extension_id);
  if (install_data != active_installs_.end()) {
    install_data->second.percent_downloaded = percent_downloaded;
  } else {
    DUMP_WILL_BE_NOTREACHED();
  }

  for (auto& observer : observers_) {
    observer.OnDownloadProgress(browser_context_, extension_id,
                                percent_downloaded);
  }
}

void InstallTracker::OnBeginCrxInstall(const std::string& extension_id) {
  for (auto& observer : observers_) {
    observer.OnBeginCrxInstall(browser_context_, extension_id);
  }
}

void InstallTracker::OnFinishCrxInstall(const base::FilePath& source_file,
                                        const std::string& extension_id,
                                        const Extension* extension,
                                        bool success) {
  for (auto& observer : observers_) {
    observer.OnFinishCrxInstall(browser_context_, source_file, extension_id,
                                extension, success);
  }
}

void InstallTracker::OnInstallFailure(
    const std::string& extension_id) {
  RemoveActiveInstall(extension_id);
}

void InstallTracker::Shutdown() {
  // Note: tests may call this method prematurely to avoid shutdown ordering
  // issues. Make sure observers don't need to handle this awkward complexity by
  // clearing them here and making this method idempotent.
  for (auto& observer : observers_)
    observer.OnShutdown();
  observers_.Clear();
  pref_change_registrar_.reset();
  browser_context_ = nullptr;
}

void InstallTracker::OnExtensionInstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    bool is_update) {
  RemoveActiveInstall(extension->id());
}

void InstallTracker::OnAppsReordered(
    const std::optional<std::string>& extension_id) {
  for (auto& observer : observers_)
    observer.OnAppsReordered(browser_context_, extension_id);
}

void InstallTracker::OnExtensionPrefChanged() {
  OnAppsReordered(std::nullopt);
}

}  // namespace extensions
