// Copyright 2024 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/ui/lens/lens_preselection_bubble.h"

#include <memory>

#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/lens/lens_help_menu_utils.h"
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/grit/branded_strings.h"
#include "components/lens/lens_features.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/menu_source_type.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_provider.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/window/dialog_delegate.h"

namespace lens {
namespace {

// The minimum y value in screen coordinates for the preselection bubble.
const int kPreselectionBubbleMinY = 8;

}  // namespace

DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(LensPreselectionBubble,
                                      kExitButtonElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(LensPreselectionBubble,
                                      kCancelButtonElementId);

LensPreselectionBubble::LensPreselectionBubble(
    tabs::TabHandle tab_handle,
    views::View* anchor_view,
    bool offline,
    ui::ColorId bubble_background_color,
    const gfx::VectorIcon* icon,
    std::optional<OverlayBaseController::CancelButtonConfig>
        cancel_button_config,
    ExitClickedCallback exit_clicked_callback,
    base::OnceClosure on_cancel_callback)
    : BubbleDialogDelegateView(anchor_view,
                               views::BubbleBorder::NONE,
                               views::BubbleBorder::NO_SHADOW),
      tab_handle_(tab_handle),
      offline_(offline),
      bubble_background_color_(bubble_background_color),
      icon_(icon),
      cancel_button_config_(cancel_button_config),
      exit_clicked_callback_(std::move(exit_clicked_callback)) {
  CHECK(icon_);
  SetShowCloseButton(false);
  set_close_on_deactivate(false);
  DialogDelegate::SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  set_corner_radius(48);
  SetBackgroundColor(bubble_background_color_);
  SetProperty(views::kElementIdentifierKey, kLensPreselectionBubbleElementId);
  SetAccessibleWindowRole(ax::mojom::Role::kAlertDialog);
  SetCancelCallback(std::move(on_cancel_callback));
}

LensPreselectionBubble::~LensPreselectionBubble() = default;

void LensPreselectionBubble::Init() {
  views::BoxLayout* layout =
      SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kHorizontal, gfx::Insets()));
  if (cancel_button_config_.has_value()) {
    set_margins(cancel_button_config_->bubble_margins);
  } else {
    offline_ ? set_margins(gfx::Insets::TLBR(6, 16, 6, 6))
             : set_margins(gfx::Insets::TLBR(12, 16, 12, 16));
  }

  // Set bubble icon and text
  const std::u16string online_toast_text = l10n_util::GetStringUTF16(
      IDS_LENS_OVERLAY_INITIAL_TOAST_MESSAGE_SIMPLIFIED);
  const std::u16string toast_text =
      offline_ ? l10n_util::GetStringUTF16(
                     IDS_LENS_OVERLAY_INITIAL_TOAST_ERROR_MESSAGE)
               : online_toast_text;
  SetAccessibleTitle(toast_text);
  icon_view_ = AddChildView(std::make_unique<views::ImageView>());
  const gfx::VectorIcon* icon;
  if (offline_) {
    icon = &(::features::IsRoundedIconsEnabled()
                 ? vector_icons::kErrorIcon
                 : vector_icons::kErrorOutlineOldIcon);
  } else {
    icon = icon_;
  }
  icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
      *icon, kColorLensOverlayToastForeground, 24));

  label_ = AddChildView(std::make_unique<views::Label>(toast_text));
  label_->SetMultiLine(false);
  label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  label_->SetAllowCharacterBreak(false);
  if (lens::IsLensOverlayContextualSearchboxEnabled(
          tab_handle_.Get()->GetBrowserWindowInterface()->GetProfile())) {
    auto button = views::CreateVectorImageButtonWithNativeTheme(
        base::RepeatingClosure(),
        ::features::IsRoundedIconsEnabled() ? kHelpCustomIcon
                                            : kHelpMenuOldIcon,
        20, kColorLensOverlayToastForeground, kColorLensOverlayToastForeground,
        kColorLensOverlayToastForeground);
    views::HighlightPathGenerator::Install(
        button.get(),
        std::make_unique<views::CircleHighlightPathGenerator>(gfx::Insets()));
    button->SetTooltipText(
        l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_MORE_OPTIONS_BUTTON_LABEL));
    if (!cancel_button_config_.has_value()) {
      more_info_button_ = AddChildView(std::move(button));
      more_info_button_->SetButtonController(
          std::make_unique<views::MenuButtonController>(
              more_info_button_,
              base::BindRepeating(&LensPreselectionBubble::OpenMoreInfoMenu,
                                  base::Unretained(this)),
              std::make_unique<views::Button::DefaultButtonControllerDelegate>(
                  more_info_button_)));
    }
  }
  layout->set_between_child_spacing(8);
  // Need to set this false so label color token doesn't get changed by
  // SetEnabledColor() color mapper. Color tokens provided have enough contrast.
  label_->SetAutoColorReadabilityEnabled(false);
  if (offline_) {
    exit_button_ = AddChildView(std::make_unique<views::MdTextButton>(
        std::move(exit_clicked_callback_),
        l10n_util::GetStringUTF16(
            IDS_LENS_OVERLAY_INITIAL_TOAST_ERROR_EXIT_BUTTON_TEXT)));
    exit_button_->SetProperty(views::kMarginsKey,
                              gfx::Insets::TLBR(0, 8, 0, 0));
    exit_button_->SetPreferredSize(gfx::Size(55, 36));
    exit_button_->SetStyle(ui::ButtonStyle::kProminent);
    exit_button_->SetProperty(views::kElementIdentifierKey,
                              kExitButtonElementId);
  }
  if (cancel_button_config_.has_value()) {
    cancel_button_ = AddChildView(std::make_unique<views::MdTextButton>(
        base::BindRepeating(&LensPreselectionBubble::CancelDialog,
                            // Safe to unretain since this class (and its
                            // underlying Widget) is self-owned.
                            base::Unretained(this)),
        l10n_util::GetStringUTF16(IDS_CANCEL)));
    cancel_button_->SetProperty(views::kMarginsKey,
                                gfx::Insets::TLBR(0, 8, 0, 0));
    cancel_button_->SetStyle(ui::ButtonStyle::kProminent);
    cancel_button_->SetProperty(views::kElementIdentifierKey,
                                kCancelButtonElementId);
  }
  NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert, true);
}

void LensPreselectionBubble::SetLabelText(int string_id) {
  // If the bubble had offline state, we don't want to reset the text.
  if (offline_) {
    return;
  }

  const std::u16string new_toast_text = l10n_util::GetStringUTF16(string_id);
  SetAccessibleTitle(new_toast_text);
  label_->SetText(new_toast_text);
  SizeToContents();
}

void LensPreselectionBubble::SetIcon(const gfx::VectorIcon& icon) {
  icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
      icon, kColorLensOverlayToastForeground, 24));
}

gfx::Rect LensPreselectionBubble::GetBubbleBounds() {
  views::View* anchor_view = GetAnchorView();
  if (!anchor_view) {
    return gfx::Rect();
  }

  const bool is_tab_strip_visible =
      tab_handle_.Get()->GetBrowserWindowInterface()->IsTabStripVisible();
  const gfx::Size bubble_size =
      GetWidget()->GetContentsView()->GetPreferredSize();
  const gfx::Rect anchor_bounds = anchor_view->GetBoundsInScreen();

  const int x =
      anchor_bounds.x() + (anchor_bounds.width() - bubble_size.width()) / 2;
  // Take bubble out of its original bounds to cross "line of death". Since, the
  // preselection bubble is anchored to the overlay, the line of death is above
  // the top of the anchor bounds. However, if not tab strip is visible, and
  // therefore there is no line of death to cross, we instead want to set the
  // preselection bubble to be kPreselectionBubbleMinY from the top of the
  // overlay.
  const int y = is_tab_strip_visible
                    ? anchor_bounds.y() - bubble_size.height() / 2
                    : anchor_bounds.y() + kPreselectionBubbleMinY;
  return gfx::Rect(x, y, bubble_size.width(), bubble_size.height());
}

void LensPreselectionBubble::OnThemeChanged() {
  BubbleDialogDelegateView::OnThemeChanged();
  const auto* color_provider = GetColorProvider();
  label_->SetEnabledColor(
      color_provider->GetColor(kColorLensOverlayToastForeground));

  if (offline_) {
    CHECK(exit_button_);
    exit_button_->SetEnabledTextColors(
        color_provider->GetColor(kColorLensOverlayToastForeground));
    exit_button_->SetBorder(views::CreateRoundedRectBorder(
        1, 48, color_provider->GetColor(kColorLensOverlayToastButtonBorder)));
    exit_button_->SetBgColorIdOverride(bubble_background_color_);
  }

  if (cancel_button_config_.has_value()) {
    CHECK(cancel_button_);
    ui::ColorId color_id = cancel_button_config_->color;
    cancel_button_->SetEnabledTextColors(color_provider->GetColor(color_id));
    cancel_button_->SetBorder(views::CreatePaddedBorder(
        views::CreateRoundedRectBorder(1, 48,
                                       color_provider->GetColor(color_id)),
        cancel_button_config_->padding));
    cancel_button_->SetBgColorIdOverride(bubble_background_color_);
  }
}

void LensPreselectionBubble::OpenMoreInfoMenu() {
  auto menu_model = std::make_unique<ui::SimpleMenuModel>(this);
  menu_model->AddItem(COMMAND_MY_ACTIVITY,
                      l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_MY_ACTIVITY));
  menu_model->AddItem(COMMAND_LEARN_MORE,
                      l10n_util::GetStringUTF16(IDS_LENS_OVERLAY_LEARN_MORE));
  menu_model->AddItem(COMMAND_SEND_FEEDBACK,
                      l10n_util::GetStringUTF16(IDS_LENS_SEND_FEEDBACK));
  more_info_menu_model_ = std::move(menu_model);
  menu_runner_ = std::make_unique<views::MenuRunner>(
      more_info_menu_model_.get(), views::MenuRunner::HAS_MNEMONICS);
  menu_runner_->RunMenuAt(more_info_button_->GetWidget(),
                          static_cast<views::MenuButtonController*>(
                              more_info_button_->button_controller()),
                          more_info_button_->GetAnchorBoundsInScreen(),
                          views::MenuAnchorPosition::kTopRight,
                          ui::mojom::MenuSourceType::kNone);
}

void LensPreselectionBubble::ExecuteCommand(int command_id, int event_flags) {
  CHECK(tab_handle_.Get());
  switch (command_id) {
    case COMMAND_MY_ACTIVITY: {
      ActivityRequestedByEvent(tab_handle_.Get(), event_flags);
      break;
    }
    case COMMAND_LEARN_MORE: {
      InfoRequestedByEvent(tab_handle_.Get(), event_flags);
      break;
    }
    case COMMAND_SEND_FEEDBACK: {
      FeedbackRequestedByEvent(tab_handle_.Get(), event_flags);
      break;
    }
    default: {
      NOTREACHED() << "Unknown option";
    }
  }
}

BEGIN_METADATA(LensPreselectionBubble)
END_METADATA

}  // namespace lens
