// 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/api/extension_action/extension_action_api.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/extensions/extension_action_dispatcher.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "content/public/common/color_parser.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/prefs_helper.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/icon_util.h"
#include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/image_util.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "url/origin.h"

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
#include "base/logging.h"
#include "ohos_nweb/src/cef_delegate/nweb_extension_action_cef_delegate.h"
#include "ohos_nweb/src/capi/nweb_extension_action_icon.h"
#endif

using content::WebContents;

namespace extensions {

namespace {

// Errors.
const char kNoExtensionActionError[] =
    "This extension has no action specified.";
const char kNoTabError[] = "No tab with id: *.";

bool g_report_error_for_invisible_icon = false;

// Returns true if the color values provided could be parsed into a color
// object out param.
bool ParseColor(const base::Value& color_value, SkColor& color) {
  if (color_value.is_string()) {
    return content::ParseCssColorString(color_value.GetString(), &color);
  }

  if (!color_value.is_list()) {
    return false;
  }

  const base::Value::List& color_list = color_value.GetList();
  if (color_list.size() != 4 ||
      std::ranges::any_of(color_list,
                          [](const auto& color) { return !color.is_int(); })) {
    return false;
  }

  color = SkColorSetARGB(color_list[3].GetInt(), color_list[0].GetInt(),
                         color_list[1].GetInt(), color_list[2].GetInt());
  return true;
}

}  // namespace

//
// ExtensionActionFunction
//

ExtensionActionFunction::ExtensionActionFunction()
    : details_(nullptr),
      tab_id_(ExtensionAction::kDefaultTabId),
      contents_(nullptr),
      extension_action_(nullptr) {}

ExtensionActionFunction::~ExtensionActionFunction() = default;

ExtensionFunction::ResponseAction ExtensionActionFunction::Run() {
  ExtensionActionManager* manager =
      ExtensionActionManager::Get(browser_context());
  extension_action_ = manager->GetExtensionAction(*extension());
  if (!extension_action_) {
    // TODO(kalman): Ideally the browserAction/pageAction APIs wouldn't even
    // exist for extensions that don't have one declared. This should come as
    // part of the Feature system.
    return RespondNow(Error(kNoExtensionActionError));
  }

  // Populates the tab_id_ and details_ members.
  EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());

  // Find the WebContents that contains this tab id if one is required.
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    content::WebContents* contents_out_param = nullptr;
    ExtensionTabUtil::GetTabById(tab_id_, browser_context(),
                                 include_incognito_information(),
                                 &contents_out_param);
    if (!contents_out_param) {
      return RespondNow(Error(kNoTabError, base::NumberToString(tab_id_)));
    }
    contents_ = contents_out_param;
  } else {
    // Page actions do not have a default tabId.
    EXTENSION_FUNCTION_VALIDATE(extension_action_->action_type() !=
                                ActionInfo::Type::kPage);
  }
  return RunExtensionAction();
}

bool ExtensionActionFunction::ExtractDataFromArguments() {
  // There may or may not be details (depends on the function).
  // The tabId might appear in details (if it exists), as the first
  // argument besides the action type (depends on the function), or be omitted
  // entirely.
  if (args().empty()) {
    return true;
  }

  const base::Value& first_arg = args()[0];

  switch (first_arg.type()) {
    case base::Value::Type::INTEGER:
      tab_id_ = first_arg.GetInt();
      break;

    case base::Value::Type::DICT: {
      // Found the details argument.
      details_ = &first_arg.GetDict();
      // Still need to check for the tabId within details.
      if (const base::Value* tab_id_value = details_->Find("tabId")) {
        switch (tab_id_value->type()) {
          case base::Value::Type::NONE:
            // OK; tabId is optional, leave it default.
            return true;
          case base::Value::Type::INTEGER:
            tab_id_ = tab_id_value->GetInt();
            return true;
          default:
            // Boom.
            return false;
        }
      }
      // Not found; tabId is optional, leave it default.
      break;
    }

    case base::Value::Type::NONE:
      // The tabId might be an optional argument.
      break;

    default:
      return false;
  }

  return true;
}

void ExtensionActionFunction::NotifyChange() {
  ExtensionActionDispatcher::Get(browser_context())
      ->NotifyChange(extension_action_, contents_, browser_context());
}

void ExtensionActionFunction::SetVisible(bool visible) {
  if (extension_action_->GetIsVisible(tab_id_) == visible) {
    return;
  }
  extension_action_->SetIsVisible(tab_id_, visible);
  NotifyChange();
}

ExtensionFunction::ResponseAction
ExtensionActionShowFunction::RunExtensionAction() {
  SetVisible(true);
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  std::optional<int> tab_id;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    tab_id = tab_id_;
  }
  LOG(INFO) << "ExtensionActionShowFunction::RunExtensionAction";
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnEnable(
      extension()->id(), tab_id,
      OHOS::NWeb::GetExtensionContextType(browser_context()),
      include_incognito_information());
#endif  // ARKWEB_ARKWEB_EXTENSIONS
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionHideFunction::RunExtensionAction() {
  SetVisible(false);
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  std::optional<int> tab_id;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    tab_id = tab_id_;
  }
  LOG(INFO) << "ExtensionActionHideFunction::RunExtensionAction";
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnDisable(
      extension()->id(), tab_id,
      OHOS::NWeb::GetExtensionContextType(browser_context()),
      include_incognito_information());
#endif  // ARKWEB_ARKWEB_EXTENSIONS
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ActionIsEnabledFunction::RunExtensionAction() {
  return RespondNow(WithArguments(
      extension_action_->GetIsVisibleIgnoringDeclarative(tab_id_)));
}

// static
void ExtensionActionSetIconFunction::SetReportErrorForInvisibleIconForTesting(
    bool value) {
  g_report_error_for_invisible_icon = value;
}

ExtensionFunction::ResponseAction
ExtensionActionSetIconFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);

  // setIcon can take a variant argument: either a dictionary of canvas
  // ImageData, or an icon index.
  const base::Value::Dict* canvas_set = details_->FindDict("imageData");
  if (canvas_set) {
    gfx::ImageSkia icon;

    extensions::IconParseResult parse_result =
        extensions::ParseIconFromCanvasDictionary(*canvas_set, &icon);
    EXTENSION_FUNCTION_VALIDATE(parse_result ==
                                extensions::IconParseResult::kSuccess);

    if (icon.isNull()) {
      return RespondNow(Error("Icon invalid."));
    }

    gfx::Image icon_image(icon);
    const SkBitmap bitmap = icon_image.AsBitmap();
    const bool is_visible = image_util::IsIconSufficientlyVisible(bitmap);
    if (!is_visible && g_report_error_for_invisible_icon) {
      return RespondNow(Error("Icon not sufficiently visible."));
    }

    extension_action_->SetIcon(tab_id_, icon_image);

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
    LOG(INFO) << "ExtensionActionSetIconFunction::RunExtensionAction";
    OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnSetIcon(
        extension()->id(), icon_image, tab_id_,
        OHOS::NWeb::GetExtensionContextType(browser_context()),
        include_incognito_information());
#endif
  } else if (details_->FindInt("iconIndex")) {
    // Obsolete argument: ignore it.
    return RespondNow(NoArguments());
  } else {
    EXTENSION_FUNCTION_VALIDATE(false);
  }

  NotifyChange();
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionSetTitleFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);
  const std::string* title = details_->FindString("title");
  EXTENSION_FUNCTION_VALIDATE(title);
  extension_action_->SetTitle(tab_id_, *title);
  NotifyChange();
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  NWebExtensionActionSetTitleDetails details;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    details.tabId = tab_id_;
  }
  details.title = *title;
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnSetTitle(
      extension()->id(), details,
      OHOS::NWeb::GetExtensionContextType(browser_context()),
      include_incognito_information());
#endif
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionSetPopupFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);
  const std::string* popup_string = details_->FindString("popup");
  EXTENSION_FUNCTION_VALIDATE(popup_string);
  GURL popup_url;

  // If an empty string is passed, remove the explicitly set popup. Setting it
  // back to an empty string (URL) will cause it to fall back to the default set
  // in the manifest.
  if (!popup_string->empty()) {
    popup_url = extension()->ResolveExtensionURL(*popup_string);
    if (!popup_url.is_valid()) {
      return RespondNow(Error(manifest_errors::kInvalidExtensionPopupPath));
    }
  }

  extension_action_->SetPopupUrl(tab_id_, popup_url);
  NotifyChange();
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  NWebExtensionActionSetPopupDetails details;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    details.tabId = tab_id_;
  }
  details.popup = *popup_string;
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnSetPopup(
      extension()->id(), details,
      OHOS::NWeb::GetExtensionContextType(browser_context()),
      include_incognito_information());
#endif
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);

  const std::string* badge_text = details_->FindString("text");
  if (badge_text) {
    extension_action_->SetBadgeText(tab_id_, *badge_text);
  } else {
    extension_action_->ClearBadgeText(tab_id_);
  }

  NotifyChange();
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);
  const base::Value* color_value = details_->Find("color");
  EXTENSION_FUNCTION_VALIDATE(color_value);
  SkColor color = 0;
  if (!ParseColor(*color_value, color)) {
    return RespondNow(Error(extension_misc::kInvalidColorError));
  }
  extension_action_->SetBadgeBackgroundColor(tab_id_, color);
  NotifyChange();
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  NWebExtensionActionSetBadgeBackgroundColorDetails details;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    details.tabId = tab_id_;
  }
  details.color = {SkColorGetR(color), SkColorGetG(color), SkColorGetB(color),
                   SkColorGetA(color)};
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()
      ->OnSetBadgeBackgroundColor(
          extension()->id(), details,
          OHOS::NWeb::GetExtensionContextType(browser_context()),
          include_incognito_information());
#endif
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ActionSetBadgeTextColorFunction::RunExtensionAction() {
  EXTENSION_FUNCTION_VALIDATE(details_);
  const base::Value* color_value = details_->Find("color");
  EXTENSION_FUNCTION_VALIDATE(color_value);
  SkColor color = 0;
  if (!ParseColor(*color_value, color)) {
    return RespondNow(Error(extension_misc::kInvalidColorError));
  }

  if (SkColorGetA(color) == SK_AlphaTRANSPARENT) {
    return RespondNow(Error(extension_misc::kInvalidColorError));
  }
  extension_action_->SetBadgeTextColor(tab_id_, color);
  NotifyChange();
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  NWebExtensionActionSetBadgeTextColorDetails details;
  if (tab_id_ != ExtensionAction::kDefaultTabId) {
    details.tabId = tab_id_;
  }
  details.color = {SkColorGetR(color), SkColorGetG(color), SkColorGetB(color),
                   SkColorGetA(color)};
  OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()
      ->OnSetBadgeTextColor(
          extension()->id(), details,
          OHOS::NWeb::GetExtensionContextType(browser_context()),
          include_incognito_information());
#endif
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
ExtensionActionGetTitleFunction::RunExtensionAction() {
  return RespondNow(WithArguments(extension_action_->GetTitle(tab_id_)));
}

ExtensionFunction::ResponseAction
ExtensionActionGetPopupFunction::RunExtensionAction() {
  return RespondNow(
      WithArguments(extension_action_->GetPopupUrl(tab_id_).spec()));
}

ExtensionFunction::ResponseAction
ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
  declarative_net_request::PrefsHelper helper(
      *ExtensionPrefs::Get(browser_context()));
  bool is_dnr_action_count_active =
      helper.GetUseActionCountAsBadgeText(extension_id()) &&
      !extension_action_->HasBadgeText(tab_id_);

  // Ensure that the placeholder string is returned if this extension is
  // displaying action counts for the badge labels and the extension doesn't
  // have permission to view the action count for this tab. Note that
  // tab-specific badge text takes priority over the action count.
  if (is_dnr_action_count_active &&
      !declarative_net_request::HasDNRFeedbackPermission(extension(),
                                                         tab_id_)) {
    return RespondNow(WithArguments(
        std::move(declarative_net_request::kActionCountPlaceholderBadgeText)));
  }

  return RespondNow(
      WithArguments(extension_action_->GetDisplayBadgeText(tab_id_)));
}

ExtensionFunction::ResponseAction
ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
  base::Value::List list;
  SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
  list.Append(static_cast<int>(SkColorGetR(color)));
  list.Append(static_cast<int>(SkColorGetG(color)));
  list.Append(static_cast<int>(SkColorGetB(color)));
  list.Append(static_cast<int>(SkColorGetA(color)));
  return RespondNow(WithArguments(std::move(list)));
}

ExtensionFunction::ResponseAction
ActionGetBadgeTextColorFunction::RunExtensionAction() {
  base::Value::List list;
  SkColor color = extension_action_->GetBadgeTextColor(tab_id_);
  list.Append(static_cast<int>(SkColorGetR(color)));
  list.Append(static_cast<int>(SkColorGetG(color)));
  list.Append(static_cast<int>(SkColorGetB(color)));
  list.Append(static_cast<int>(SkColorGetA(color)));
  return RespondNow(WithArguments(std::move(list)));
}

ActionGetUserSettingsFunction::ActionGetUserSettingsFunction() = default;
ActionGetUserSettingsFunction::~ActionGetUserSettingsFunction() = default;

ExtensionFunction::ResponseAction ActionGetUserSettingsFunction::Run() {
  DCHECK(extension());
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  std::optional<NWebExtensionActionUserSettings> user_settings =
      OHOS::NWeb::NWebExtensionActionCefDelegate::GetInstance()->OnGetUserSettings(extension()->id());
  bool is_pinned = false;
  if (user_settings) {
    is_pinned = user_settings->isOnToolbar;
  }
#else
  ExtensionActionManager* const action_manager =
      ExtensionActionManager::Get(browser_context());
  ExtensionAction* const action =
      action_manager->GetExtensionAction(*extension());

  // This API is only available to extensions with the "action" key in the
  // manifest, so they should always have an action.
  DCHECK(action);
  DCHECK_EQ(ActionInfo::Type::kAction, action->action_type());

  const bool is_pinned =
      ToolbarActionsModel::Get(Profile::FromBrowserContext(browser_context()))
          ->IsActionPinned(extension_id());

  // TODO(crbug.com/360916928): Today, no action APIs are compiled.
  // Unfortunately, this means we miss out on the compiled types, which would be
  // rather helpful here.
#endif
  base::Value::Dict ui_settings;
  ui_settings.Set("isOnToolbar", is_pinned);

  return RespondNow(WithArguments(std::move(ui_settings)));
}

// ActionOpenPopupFunction and BrowserActionOpenPopupFunction have separate
// Android and non-Android implementations. See extension_action_api_android.cc
// and extension_action_api_non_android.cc.

}  // namespace extensions