// 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 <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include "chrome/browser/extensions/extension_install_error_menu_item_id_provider.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/global_error/global_error.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/image_loader.h"
#include "extensions/browser/ui_util.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/extension.h"
#include "extensions/common/icons/extension_icon_set.h"
#include "extensions/common/permissions/permission_message.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/size.h"

// ExtensionDisabledGlobalError -----------------------------------------------

namespace extensions {

class ExtensionDisabledGlobalError final
    : public GlobalErrorWithStandardBubble,
      public ExtensionUninstallDialog::Delegate,
      public ExtensionRegistryObserver {
 public:
  ExtensionDisabledGlobalError(Profile* profile,
                               const Extension* extension,
                               bool is_remote_install);

  ExtensionDisabledGlobalError(const ExtensionDisabledGlobalError&) = delete;
  ExtensionDisabledGlobalError& operator=(const ExtensionDisabledGlobalError&) =
      delete;

  ~ExtensionDisabledGlobalError() override;

  // GlobalError:
  Severity GetSeverity() override;
  bool HasMenuItem() override;
  int MenuItemCommandID() override;
  std::u16string MenuItemLabel() override;
  void ExecuteMenuItem(BrowserWindowInterface* browser) override;
  std::u16string GetBubbleViewTitle() override;
  std::vector<std::u16string> GetBubbleViewMessages() override;
  std::u16string GetBubbleViewAcceptButtonLabel() override;
  std::u16string GetBubbleViewCancelButtonLabel() override;
  void OnBubbleViewDidClose(BrowserWindowInterface* browser) override {}
  void BubbleViewAcceptButtonPressed(BrowserWindowInterface* browser) override;
  void BubbleViewCancelButtonPressed(BrowserWindowInterface* browser) override;
  base::WeakPtr<GlobalErrorWithStandardBubble> AsWeakPtr() override;
  bool ShouldCloseOnDeactivate() const override;
  bool ShouldShowCloseButton() const override;

  // ExtensionUninstallDialog::Delegate:
  void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
                                        const std::u16string& error) override;

 private:
  // ExtensionRegistryObserver:
  void OnExtensionLoaded(content::BrowserContext* browser_context,
                         const Extension* extension) override;
  void OnExtensionUninstalled(content::BrowserContext* browser_context,
                              const Extension* extension,
                              UninstallReason reason) override;
  void OnShutdown(ExtensionRegistry* registry) override;

  void RemoveGlobalError();

  raw_ptr<Profile, DanglingUntriaged> profile_;
  scoped_refptr<const Extension> extension_;
  bool is_remote_install_;

  std::unique_ptr<ExtensionUninstallDialog> uninstall_dialog_;

  // Helper to get menu command ID assigned for this extension's error.
  ExtensionInstallErrorMenuItemIdProvider id_provider_;

  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
      registry_observation_{this};

  base::WeakPtrFactory<ExtensionDisabledGlobalError> weak_ptr_factory_{this};
};

// TODO(yoz): create error at startup for disabled extensions.
ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
    Profile* profile,
    const Extension* extension,
    bool is_remote_install)
    : profile_(profile),
      extension_(extension),
      is_remote_install_(is_remote_install) {
  registry_observation_.Observe(ExtensionRegistry::Get(profile_));
}

ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() = default;

GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
  return SEVERITY_LOW;
}

bool ExtensionDisabledGlobalError::HasMenuItem() {
  return true;
}

int ExtensionDisabledGlobalError::MenuItemCommandID() {
  return id_provider_.menu_command_id();
}

std::u16string ExtensionDisabledGlobalError::MenuItemLabel() {
  std::u16string extension_name =
      ui_util::GetFixupExtensionNameForUIDisplay(extension_->name());
  // Ampersands need to be escaped to avoid being treated like
  // mnemonics in the menu.
  base::ReplaceChars(extension_name, u"&", u"&&", &extension_name);

  return l10n_util::GetStringFUTF16(
      is_remote_install_ ? IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
                         : IDS_EXTENSION_DISABLED_ERROR_TITLE,
      extension_name);
}

void ExtensionDisabledGlobalError::ExecuteMenuItem(
    BrowserWindowInterface* browser) {
  ShowBubbleView(browser);
}

std::u16string ExtensionDisabledGlobalError::GetBubbleViewTitle() {
  std::u16string extension_name =
      ui_util::GetFixupExtensionNameForUIDisplay(extension_->name());
  return l10n_util::GetStringFUTF16(
      is_remote_install_ ? IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE
                         : IDS_EXTENSION_DISABLED_ERROR_TITLE,
      extension_name);
}

std::vector<std::u16string>
ExtensionDisabledGlobalError::GetBubbleViewMessages() {
  std::vector<std::u16string> messages;

  std::unique_ptr<const PermissionSet> granted_permissions =
      ExtensionPrefs::Get(profile_)->GetGrantedPermissions(extension_->id());
  // The granted-permissions pref may be missing or malformed (observed during
  // install/uninstall/update races and on corrupted profiles). Fall back to an
  // empty set so all current permissions are treated as newly granted, rather
  // than dereferencing a null pointer.
  if (!granted_permissions) {
    granted_permissions = std::make_unique<PermissionSet>();
  }

  PermissionMessages permission_warnings =
      extension_->permissions_data()->GetNewPermissionMessages(
          *granted_permissions);

  if (is_remote_install_) {
    if (!permission_warnings.empty())
      messages.push_back(
          l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
  } else {
    messages.push_back(
        l10n_util::GetStringUTF16(IDS_EXTENSION_DISABLED_ERROR_LABEL));
  }
  for (const PermissionMessage& msg : permission_warnings) {
    messages.push_back(l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE,
                                                  msg.message()));
  }
  return messages;
}

std::u16string ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
  if (is_remote_install_) {
    return l10n_util::GetStringUTF16(
        extension_->is_app()
            ? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
            : IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION);
  }
  return l10n_util::GetStringUTF16(
      IDS_EXTENSION_PROMPT_PERMISSIONS_ACCEPT_BUTTON);
}

std::u16string ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
  return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON);
}

void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
    BrowserWindowInterface* browser) {
  // Delay extension reenabling so this bubble closes properly.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          // Adapt scoped_refptr<> as it can't be bound to const&.
          [](base::WeakPtr<ExtensionRegistrar> registrar,
             const scoped_refptr<const Extension>& extension) {
            if (registrar) {
              CHECK(extension);
              registrar->GrantPermissionsAndEnableExtension(*extension);
            }
          },
          ExtensionRegistrar::Get(profile_)->GetWeakPtr(),
          base::RetainedRef(extension_)));
}

void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
    BrowserWindowInterface* browser) {
  uninstall_dialog_ = ExtensionUninstallDialog::Create(
      profile_, browser->GetWindow()->GetNativeWindow(), this);
  // Delay showing the uninstall dialog, so that this function returns
  // immediately, to close the bubble properly. See crbug.com/40184398.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ExtensionUninstallDialog::ConfirmUninstall,
                                uninstall_dialog_->AsWeakPtr(),
                                base::RetainedRef(extension_),
                                UNINSTALL_REASON_EXTENSION_DISABLED,
                                UNINSTALL_SOURCE_PERMISSIONS_INCREASE));
}

base::WeakPtr<GlobalErrorWithStandardBubble>
ExtensionDisabledGlobalError::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
  // Since this indicates that an extension was disabled, we should definitely
  // have the user acknowledge it, rather than having the bubble disappear when
  // a new window pops up.
  return false;
}

bool ExtensionDisabledGlobalError::ShouldShowCloseButton() const {
  // As we don't close the bubble on deactivation (see ShouldCloseOnDeactivate),
  // we add a close button so the user doesn't *need* to act right away.
  // If the bubble is closed, the error remains in the wrench menu and the user
  // can address it later.
  return true;
}

void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
    bool did_start_uninstall,
    const std::u16string& error) {
  // No need to do anything.
}

void ExtensionDisabledGlobalError::OnExtensionLoaded(
    content::BrowserContext* browser_context,
    const Extension* extension) {
  if (extension->id() != extension_->id()) {
    return;
  }
  RemoveGlobalError();
}

void ExtensionDisabledGlobalError::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UninstallReason reason) {
  if (extension->id() != extension_->id()) {
    return;
  }
  RemoveGlobalError();
}

void ExtensionDisabledGlobalError::OnShutdown(ExtensionRegistry* registry) {
  DCHECK_EQ(ExtensionRegistry::Get(profile_), registry);
  registry_observation_.Reset();
}

void ExtensionDisabledGlobalError::RemoveGlobalError() {
  std::unique_ptr<GlobalError> ptr =
      GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(
          this);
  registry_observation_.Reset();
  // Delete this object after any running tasks, so that the extension dialog
  // still has it as a delegate to finish the current tasks.
  base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                ptr.release());
}

// Globals --------------------------------------------------------------------

void AddExtensionDisabledError(Profile* profile,
                               const Extension* extension,
                               bool is_remote_install) {
  if (extension) {
    GlobalErrorServiceFactory::GetForProfile(profile)->AddGlobalError(
        std::make_unique<ExtensionDisabledGlobalError>(profile, extension,
                                                       is_remote_install));
  }
}

}  // namespace extensions
