// 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/ui/views/bookmarks/bookmark_bar_view.h"

#include <stdint.h>

#include <algorithm>
#include <limits>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/callback_list.h"
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/favicon/favicon_utils.h"
#include "chrome/browser/page_load_metrics/chrome_initiator_location.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h"
#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
#include "chrome/browser/ui/bookmarks/bookmark_ui_operations_helper.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils_desktop.h"
#include "chrome/browser/ui/bookmarks/controllers/bookmark_bar_ui_controller.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_button.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_button_util.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_menu_button_base.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h"
#include "chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/event_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/themed_background.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/bookmarks/managed/managed_bookmark_service.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/metrics/metrics_service.h"
#include "components/prefs/pref_service.h"
#include "components/profile_metrics/browser_profile_type.h"
#include "components/saved_tab_groups/public/features.h"
#include "components/url_formatter/elide_url.h"
#include "components/url_formatter/url_formatter.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model_utils.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/theme_provider.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/window_open_disposition.h"
#include "ui/base/window_open_disposition_utils.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event_constants.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/button_drag_utils.h"
#include "ui/views/cascading_property.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/label_button_border.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/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/drag_utils.h"
#include "ui/views/metrics.h"
#include "ui/views/property_effects.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_constants.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/tooltip_manager.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/frame_view.h"

namespace {

using ::bookmarks::BookmarkModel;
using ::bookmarks::BookmarkNode;
using chrome::GetBookmarkDropOperation;
using ::ui::mojom::DragOperation;
using ::views::Background;
using ::views::Border;
using ::views::LabelButtonBorder;
using ::views::MenuButton;
using PermanentFolderType = BookmarkParentFolder::PermanentFolderType;

// Used to globally disable rich animations.
bool animations_enabled = true;

// Thickness of the separator (|) in dips (density-independent pixels).
constexpr int kBookmarkBarSeparatorThickness = 2;

gfx::ImageSkia* GetImageSkiaNamed(int id) {
  return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(id);
}

std::u16string GetFolderButtonAccessibleName(std::u16string_view folder_title) {
  return folder_title.empty()
             ? l10n_util::GetStringUTF16(IDS_UNNAMED_BOOKMARK_FOLDER)
             : std::u16string(folder_title);
}

// ShortcutButton -------------------------------------------------------------

// Buttons used for the shortcuts on the bookmark bar.

class ShortcutButton : public BookmarkButtonBase {
  METADATA_HEADER(ShortcutButton, BookmarkButtonBase)

 public:
  ShortcutButton(PressedCallback callback, const std::u16string& title)
      : BookmarkButtonBase(std::move(callback), title) {}
  ShortcutButton(const ShortcutButton&) = delete;
  ShortcutButton& operator=(const ShortcutButton&) = delete;
};

BEGIN_METADATA(ShortcutButton)
END_METADATA

// BookmarkFolderButton -------------------------------------------------------

// Buttons used for folders on the bookmark bar, including the 'other folders'
// button.
class BookmarkFolderButton : public BookmarkMenuButtonBase {
  METADATA_HEADER(BookmarkFolderButton, BookmarkMenuButtonBase)

 public:
  // Return true if the callback handles the provided AXActionData.
  using AccessibleActionCallback =
      base::RepeatingCallback<bool(const ui::AXActionData&)>;

  explicit BookmarkFolderButton(PressedCallback callback,
                                AccessibleActionCallback ax_action_callback,
                                IsDraggingCallback is_dragging_callback,
                                std::u16string_view title = {})
      : BookmarkMenuButtonBase(std::move(callback),
                               std::move(is_dragging_callback),
                               title),
        ax_action_callback_(std::move(ax_action_callback)) {
    show_animation_ = std::make_unique<gfx::SlideAnimation>(this);
    if (!animations_enabled) {
      // For some reason during testing the events generated by animating
      // throw off the test. So, don't animate while testing.
      show_animation_->Reset(1);
    } else {
      show_animation_->Show();
    }

    views::FocusRing::Get(this)->SetOutsetFocusRingDisabled(true);

    SetImageLabelSpacing(
        GetLayoutConstant(LayoutConstant::kBookmarkBarButtonImageLabelPadding));

    // ui::EF_MIDDLE_MOUSE_BUTTON opens all bookmarked links in separate tabs.
    SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON |
                             ui::EF_MIDDLE_MOUSE_BUTTON);

    GetViewAccessibility().SetName(GetAccessibleText());
    GetViewAccessibility().SetRoleDescription(l10n_util::GetStringUTF8(
        IDS_ACCNAME_BOOKMARK_FOLDER_BUTTON_ROLE_DESCRIPTION));

    text_changed_callback_ =
        label()->AddTextChangedCallback(base::BindRepeating(
            &BookmarkFolderButton::OnTextChanged, base::Unretained(this)));

    UpdateCachedTooltipText();
  }

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

  void UpdateCachedTooltipText() { SetTooltipText(GetAccessibleText()); }

  bool OnMousePressed(const ui::MouseEvent& event) override {
    if (event.IsOnlyLeftMouseButton()) {
      // TODO(bruthig): The ACTION_PENDING triggering logic should be in
      // MenuButton::OnPressed() however there is a bug with the pressed state
      // logic in MenuButton. See http://crbug.com/41227327.
      views::InkDrop::Get(this)->AnimateToState(
          views::InkDropState::ACTION_PENDING, &event);
    }
    return BookmarkMenuButtonBase::OnMousePressed(event);
  }

  bool HandleAccessibleAction(const ui::AXActionData& action_data) override {
    if (ax_action_callback_.Run(action_data)) {
      return true;
    }
    return BookmarkMenuButtonBase::HandleAccessibleAction(action_data);
  }

  void OnTextChanged() {
    GetViewAccessibility().SetName(GetAccessibleText());
    UpdateCachedTooltipText();
  }

  std::u16string GetAccessibleText() const {
    // If the folder is unnamed, set the name to a default string for unnamed
    // folders; otherwise set the name to the user-supplied folder name.
    return GetText().empty()
               ? l10n_util::GetStringUTF16(IDS_UNNAMED_BOOKMARK_FOLDER)
               : std::u16string(GetText());
  }

 private:
  AccessibleActionCallback ax_action_callback_;
  std::unique_ptr<gfx::SlideAnimation> show_animation_;
  base::CallbackListSubscription text_changed_callback_;
};

BEGIN_METADATA(BookmarkFolderButton)
END_METADATA

// BookmarkTabGroupButton
// -------------------------------------------------------

void RecordAppLaunch(Profile* profile, const GURL& url) {
  const extensions::Extension* extension =
      extensions::ExtensionRegistry::Get(profile)
          ->enabled_extensions()
          .GetAppByURL(url);
  if (!extension) {
    return;
  }

  extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_BOOKMARK_BAR,
                                  extension->GetType());
}

std::vector<raw_ptr<const BookmarkNode, VectorExperimental>> ToRawPtrVector(
    const std::vector<const BookmarkNode*>& nodes) {
  return base::ToVector(nodes, [](const BookmarkNode* node) {
    return raw_ptr<const BookmarkNode, VectorExperimental>(node);
  });
}
}  // namespace

// DropLocation ---------------------------------------------------------------

struct BookmarkBarView::DropLocation {
  bool Equals(const DropLocation& other) {
    return ((other.index == index) && (other.on == on) &&
            (other.button_type == button_type));
  }

  // Index into the model the drop is over. This is relative to the root node.
  std::optional<size_t> index;

  // Drop constants.
  DragOperation operation = DragOperation::kNone;

  // If true, the user is dropping on a folder.
  bool on = false;

  // Type of button.
  DropButtonType button_type = DropButtonType::kDropBookmark;
};

// DropInfo -------------------------------------------------------------------

// Tracks drops on the BookmarkBarView.

struct BookmarkBarView::DropInfo {
  // Whether the data is valid.
  bool valid = false;

  // If true, the menu is being shown.
  bool is_menu_showing = false;

  // Coordinates of the drag (in terms of the BookmarkBarView).
  int x = 0;
  int y = 0;

  // DropData for the drop.
  bookmarks::BookmarkNodeData data;

  DropLocation location;
};

// ButtonSeparatorView  --------------------------------------------------------

class BookmarkBarView::ButtonSeparatorView : public views::Separator {
  METADATA_HEADER(ButtonSeparatorView, views::Separator)

 public:
  ButtonSeparatorView() {
    const int leading_padding = 8;
    const int trailing_padding = 8;
    const gfx::Insets border_insets =
        gfx::Insets::TLBR(0, leading_padding, 0, trailing_padding);
    const ui::ColorId color_id = kColorBookmarkBarSeparatorChromeRefresh;

    SetColorId(color_id);
    UpdateBorderAndPreferredSize(border_insets);

    GetViewAccessibility().SetRole(ax::mojom::Role::kSplitter);
    GetViewAccessibility().SetName(
        l10n_util::GetStringUTF8(IDS_ACCNAME_SEPARATOR));
  }
  ButtonSeparatorView(const ButtonSeparatorView&) = delete;
  ButtonSeparatorView& operator=(const ButtonSeparatorView&) = delete;
  ~ButtonSeparatorView() override = default;

  void UpdateBorderAndPreferredSize(const gfx::Insets& border_insets) {
    const int border_thickness = kBookmarkBarSeparatorThickness / 2;

    // For RoundedRectBorder, Border::GetInsets() is equal to border thickness
    // + paint insets.
    if (GetBorder() && GetBorder()->GetInsets() ==
                           border_insets + gfx::Insets(border_thickness)) {
      return;
    }

    SetPreferredSize(gfx::Size(border_insets.left() +
                                   kBookmarkBarSeparatorThickness +
                                   border_insets.right(),
                               gfx::kFaviconSize));

    SetBorder(views::CreateRoundedRectBorder(
        border_thickness, border_thickness, border_insets,
        kColorBookmarkBarSeparatorChromeRefresh));
  }
};

BEGIN_METADATA(BookmarkBarView, ButtonSeparatorView)
END_METADATA

// BookmarkBarView ------------------------------------------------------------

BookmarkBarView::BookmarkBarView(
    BrowserWindowInterface* browser,
    std::unique_ptr<BookmarkBarUIController> controller,
    BrowserView* browser_view)
    : AnimationDelegateViews(this),
      browser_(browser),
      browser_view_(browser_view),
      controller_(std::move(controller)) {
  SetID(VIEW_ID_BOOKMARK_BAR);
  SetProperty(views::kElementIdentifierKey, kBookmarkBarElementId);

  size_animation_.Reset(1);
  if (!gfx::Animation::ShouldRenderRichAnimation()) {
    animations_enabled = false;
  }

  // May be null for tests.
  if (browser_view) {
    SetBackground(std::make_unique<ThemedBackground>(browser_view));
  }

  views::SetCascadingColorProviderColor(this, views::kCascadingBackgroundColor,
                                        kColorBookmarkBarBackground);
  GetViewAccessibility().SetRole(ax::mojom::Role::kToolbar);
  GetViewAccessibility().SetName(
      l10n_util::GetStringUTF8(IDS_ACCNAME_BOOKMARKS));

  Init();

  if (controller_) {
    controller_->Bind(this);
  }
}

BookmarkBarView::~BookmarkBarView() {
  if (context_menu_) {
    context_menu_->RemoveObserver(this);
  }

  if (bookmark_service_) {
    bookmark_service_->RemoveObserver(this);
  }

  // It's possible for the menu to outlive us, reset the observer to make sure
  // it doesn't have a reference to us.
  if (bookmark_menu_) {
    bookmark_menu_->set_observer(nullptr);
    bookmark_menu_->clear_bookmark_bar();
  }

  StopShowFolderDropMenuTimer();
}

// static
bool BookmarkBarView::GetAnimationsEnabled() {
  return animations_enabled;
}

void BookmarkBarView::DisableAnimationsForTesting(bool disabled) {
  animations_enabled = !disabled;
}

void BookmarkBarView::AddObserver(BookmarkBarViewObserver* observer) {
  observers_.AddObserver(observer);
}

void BookmarkBarView::RemoveObserver(BookmarkBarViewObserver* observer) {
  observers_.RemoveObserver(observer);
}

void BookmarkBarView::SetPageNavigator(content::PageNavigator* navigator) {
  page_navigator_ = navigator;
  if (saved_tab_group_bar_) {
    saved_tab_group_bar_->SetPageNavigator(navigator);
  }
}

void BookmarkBarView::SetInfoBarVisible(bool infobar_visible) {
  if (infobar_visible == infobar_visible_) {
    return;
  }
  infobar_visible_ = infobar_visible;
  OnPropertyChanged(&infobar_visible_, views::PropertyEffects::kLayout);
}

bool BookmarkBarView::GetInfoBarVisible() const {
  return infobar_visible_;
}

void BookmarkBarView::SetBookmarkBarState(
    BookmarkBar::State state,
    BookmarkBar::AnimateChangeType animate_type) {
  if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE && animations_enabled) {
    if (state == BookmarkBar::SHOW) {
      size_animation_.Show();
    } else {
      size_animation_.Hide();
    }
  } else {
    size_animation_.Reset(state == BookmarkBar::SHOW ? 1 : 0);
    if (!animations_enabled) {
      AnimationEnded(&size_animation_);
    }
  }

#if BUILDFLAG(IS_MAC)
  // Ensure screen readers can't traverse bookmark bar children while
  // hidden.
  if (state == BookmarkBar::SHOW) {
    GetViewAccessibility().SetIsLeaf(false);
    NotifyAccessibilityEventDeprecated(ax::mojom::Event::kTreeChanged, true);
  } else {
    GetViewAccessibility().SetIsLeaf(true);
    NotifyAccessibilityEventDeprecated(ax::mojom::Event::kTreeChanged, true);
  }
#endif

  bookmark_bar_state_ = state;
}

std::optional<BookmarkParentFolder>
BookmarkBarView::GetBookmarkFolderForButtonAtLocation(
    const gfx::Point& loc,
    size_t* model_start_index) {
  *model_start_index = 0;

  if (loc.x() < 0 || loc.x() >= width() || loc.y() < 0 || loc.y() >= height()) {
    return std::nullopt;
  }

  gfx::Point adjusted_loc(GetMirroredXInView(loc.x()), loc.y());

  // Check the managed button first.
  if (managed_bookmarks_button_->GetVisible() &&
      managed_bookmarks_button_->bounds().Contains(adjusted_loc)) {
    return BookmarkParentFolder::ManagedFolder();
  }

  // Then check the bookmark buttons.
  for (const auto& button_and_node : bookmark_buttons_) {
    views::View* child = button_and_node.first;
    if (!child->GetVisible()) {
      break;
    }
    if (child->bounds().Contains(adjusted_loc)) {
      return button_and_node.second->is_folder()
                 ? BookmarkParentFolder::FromFolderNode(button_and_node.second)
                 : std::optional<BookmarkParentFolder>(std::nullopt);
    }
  }

  // Then the overflow button.
  if (overflow_button_->GetVisible() &&
      overflow_button_->bounds().Contains(adjusted_loc)) {
    *model_start_index = first_hidden_node_idx_;
    return BookmarkParentFolder::BookmarkBarFolder();
  }

  // And finally the all bookmarks button.
  if (all_bookmarks_button_->GetVisible() &&
      all_bookmarks_button_->bounds().Contains(adjusted_loc)) {
    return BookmarkParentFolder::OtherFolder();
  }

  return std::nullopt;
}

MenuButton* BookmarkBarView::GetMenuButtonForFolder(
    const BookmarkParentFolder& folder) {
  std::optional<PermanentFolderType> type = folder.as_permanent_folder();
  if (type == PermanentFolderType::kManagedNode) {
    return managed_bookmarks_button_;
  } else if (type == PermanentFolderType::kOtherNode) {
    return all_bookmarks_button_;
  } else if (type == PermanentFolderType::kBookmarkBarNode) {
    return overflow_button_;
  }

  // Check if direct child of bookmark bar and is a folder.
  if (const BookmarkNode* node = folder.as_non_permanent_folder();
      node && node->is_folder() &&
      node->parent()->type() == BookmarkNode::Type::BOOKMARK_BAR) {
    auto it = std::ranges::find(bookmark_buttons_, node,
                                &BookmarkButtonAndNode::second);
    CHECK(it != bookmark_buttons_.end());
    return static_cast<MenuButton*>(it->first);
  }

  return nullptr;
}

void BookmarkBarView::GetAnchorPositionForButton(
    MenuButton* button,
    views::MenuAnchorPosition* anchor) {
  using Position = views::MenuAnchorPosition;
  if (button == all_bookmarks_button_ || button == overflow_button_) {
    *anchor = Position::kTopRight;
  } else {
    *anchor = Position::kTopLeft;
  }
}

int BookmarkBarView::GetLeadingMargin() const {
  static constexpr int kBookmarksBarLeadingMarginWithoutSavedTabGroups = 6;
  static constexpr int kBookmarksBarLeadingMarginWithSavedTabGroups = 6;

  if (saved_tab_groups_separator_view_ &&
      saved_tab_groups_separator_view_->GetVisible()) {
    return kBookmarksBarLeadingMarginWithSavedTabGroups;
  }
  return kBookmarksBarLeadingMarginWithoutSavedTabGroups;
}

const views::MenuItemView* BookmarkBarView::GetMenu() const {
  return bookmark_menu_ ? bookmark_menu_->menu() : nullptr;
}

views::MenuItemView* BookmarkBarView::GetContextMenu() {
  return bookmark_menu_ ? bookmark_menu_->context_menu() : nullptr;
}

views::MenuItemView* BookmarkBarView::GetDropMenu() {
  return bookmark_drop_menu_ ? bookmark_drop_menu_->menu() : nullptr;
}

// static
std::u16string BookmarkBarView::CreateToolTipForURLAndTitle(
    int max_width,
    const gfx::FontList& tt_fonts,
    const GURL& url,
    std::u16string_view title) {
  std::u16string result;

  // First the title.
  if (!title.empty()) {
    std::u16string localized_title(title);
    base::i18n::AdjustStringForLocaleDirection(&localized_title);
    result.append(
        gfx::ElideText(localized_title, tt_fonts, max_width, gfx::ELIDE_TAIL));
  }

  // Only show the URL if the url and title differ.
  if (title != base::UTF8ToUTF16(url.spec())) {
    if (!result.empty()) {
      result.push_back('\n');
    }

    // We need to explicitly specify the directionality of the URL's text to
    // make sure it is treated as an LTR string when the context is RTL. For
    // example, the URL "http://www.yahoo.com/" appears as
    // "/http://www.yahoo.com" when rendered, as is, in an RTL context since
    // the Unicode BiDi algorithm puts certain characters on the left by
    // default.
    std::u16string elided_url(
        url_formatter::ElideUrl(url, tt_fonts, max_width));
    elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
    result.append(elided_url);
  }
  return result;
}

gfx::Size BookmarkBarView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Size prefsize;
  int preferred_height = GetLayoutConstant(LayoutConstant::kBookmarkBarHeight);
  prefsize.set_height(
      static_cast<int>(preferred_height * size_animation_.GetCurrentValue()));
  return prefsize;
}

gfx::Size BookmarkBarView::GetMinimumSize() const {
  // The minimum width of the bookmark bar should at least contain the overflow
  // button, by which one can access all the Bookmark Bar items, and the "All
  // Bookmarks" folder, along with appropriate margins and button padding.
  // It should also contain the Managed Bookmarks folder, if it is visible.
  int width = GetLeadingMargin();
  int height = GetLayoutConstant(LayoutConstant::kBookmarkBarHeight);

  const int bookmark_bar_button_padding =
      GetLayoutConstant(LayoutConstant::kBookmarkBarButtonPadding);

  if (managed_bookmarks_button_->GetVisible()) {
    gfx::Size size = managed_bookmarks_button_->GetPreferredSize();
    width += size.width() + bookmark_bar_button_padding;
  }
  if (all_bookmarks_button_->GetVisible()) {
    gfx::Size size = all_bookmarks_button_->GetPreferredSize();
    width += size.width() + bookmark_bar_button_padding;
  }
  if (overflow_button_->GetVisible()) {
    gfx::Size size = overflow_button_->GetPreferredSize();
    width += size.width() + bookmark_bar_button_padding;
  }
  if (bookmarks_separator_view_->GetVisible()) {
    gfx::Size size = bookmarks_separator_view_->GetPreferredSize();
    width += size.width();
  }
  if (saved_tab_groups_separator_view_ &&
      saved_tab_groups_separator_view_->GetVisible()) {
    gfx::Size size = saved_tab_groups_separator_view_->GetPreferredSize();
    width += size.width();
  }
  if (apps_page_shortcut_->GetVisible()) {
    gfx::Size size = apps_page_shortcut_->GetPreferredSize();
    width += size.width() + bookmark_bar_button_padding;
  }

  return gfx::Size(width, height);
}

// TODO(crbug.com/40648316, crbug.com/330763840): Remove Layout override and
// transition BookmarkBarView to use a layout manager. Afterwards, for most if
// not all calls to LayoutAndPaint can be migrated to only schedule paint and
// not invalidate layout because the layout manager will invalidate layout
// automatically for things like adding and removing child views.
void BookmarkBarView::Layout(PassKey) {
  // Skip layout during destruction, when no bookmark service exists.
  if (!bookmark_service_) {
    return;
  }

  int x = GetLeadingMargin();
  static constexpr int kBookmarkBarTrailingMargin = 8;
  int width = View::width() - x - kBookmarkBarTrailingMargin;

  const int button_height =
      GetLayoutConstant(LayoutConstant::kBookmarkBarButtonHeight);

  // Bookmark bar buttons should be centered between the bottom of the location
  // bar and the bottom of the bookmarks bar, which requires factoring in the
  // bottom margin of the toolbar into the button position.
  int toolbar_bottom_margin = 0;
  // Note: |browser_view_| may be null during tests.
  if (browser_view_ && !browser_view_->IsFullscreen()) {
    toolbar_bottom_margin = browser_view_->toolbar()->height() -
                            browser_view_->GetLocationBar()->Bounds().bottom();
  }
  // Center the buttons in the total available space.
  const int total_height = GetContentsBounds().height() + toolbar_bottom_margin;
  const auto center_y = [total_height, toolbar_bottom_margin](int height) {
    const int top_margin = (total_height - height) / 2;
    // Calculate the top inset in the bookmarks bar itself (not counting the
    // space in the toolbar) but do not allow the buttons to leave the bookmarks
    // bar.
    return std::max(0, top_margin - toolbar_bottom_margin);
  };
  const int y = center_y(button_height);

  gfx::Size all_bookmarks_pref = all_bookmarks_button_->GetVisible()
                                     ? all_bookmarks_button_->GetPreferredSize()
                                     : gfx::Size();
  gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
  gfx::Size bookmarks_separator_pref =
      bookmarks_separator_view_->GetPreferredSize();
  gfx::Size apps_page_shortcut_pref =
      apps_page_shortcut_->GetVisible()
          ? apps_page_shortcut_->GetPreferredSize()
          : gfx::Size();

  const int bookmark_bar_button_padding =
      GetLayoutConstant(LayoutConstant::kBookmarkBarButtonPadding);

  int max_x = GetLeadingMargin() + width - overflow_pref.width() -
              bookmarks_separator_pref.width();
  if (all_bookmarks_button_->GetVisible()) {
    max_x -= all_bookmarks_pref.width() + bookmark_bar_button_padding;
  }

  // Start with the apps page shortcut button.
  if (apps_page_shortcut_->GetVisible()) {
    apps_page_shortcut_->SetBounds(x, y, apps_page_shortcut_pref.width(),
                                   button_height);
    x += apps_page_shortcut_pref.width() + bookmark_bar_button_padding;
  }

  // Then comes the managed bookmarks folder, if visible.
  if (managed_bookmarks_button_->GetVisible()) {
    gfx::Size managed_bookmarks_pref =
        managed_bookmarks_button_->GetPreferredSize();
    managed_bookmarks_button_->SetBounds(x, y, managed_bookmarks_pref.width(),
                                         button_height);
    x += managed_bookmarks_pref.width() + bookmark_bar_button_padding;
  }

  const BookmarkParentFolder bookmark_bar_folder =
      BookmarkParentFolder::BookmarkBarFolder();
  const size_t bookmark_bar_children_count =
      bookmark_service_->loaded()
          ? bookmark_service_->GetChildrenCount(bookmark_bar_folder)
          : 0u;

  int saved_tab_group_bar_width = 0;
  if (saved_tab_group_bar_ && saved_tab_group_bar_->GetVisible()) {
    // Manually set the overflow(Everything) button's preferred width to be
    // the `button_height` which will be used to calculate the preferred width
    // of `saved_tab_group_bar_` below. Later the overflow button will be laid
    // out with both width and height the same as `button_height` (i.e. the
    // height of `saved_tab_group_bar_`).
    if (saved_tab_group_bar_->everything_menu_button()) {
      saved_tab_group_bar_->everything_menu_button()->SetPreferredSize(
          gfx::Size(button_height, button_height));
    }
    // Calculate the save tab group width without any restriction.
    int saved_tab_group_max_width =
        saved_tab_group_bar_->CalculatePreferredWidthRestrictedBy(INT_MAX);
    // Estimate the bookmark buttons width with existing buttons because not
    // all the bookmarks have button created.
    int estimate_bookmark_buttons_width = std::accumulate(
        bookmark_buttons_.begin(), bookmark_buttons_.end(), 0,
        [](size_t total, const auto& bookmark_button_and_node) {
          return total +
                 bookmark_button_and_node.first->GetPreferredSize().width();
        });

    int button_count = bookmark_buttons_.size();
    if (button_count > 0) {
      estimate_bookmark_buttons_width *=
          (1.0 * bookmark_bar_children_count / button_count);
    }
    estimate_bookmark_buttons_width +=
        (bookmark_bar_children_count - 1) * bookmark_bar_button_padding;

    // Calculate the maximum size needed for the tab group buttons. Space must
    // be allocated for both saved tab group and bookmarks to prevent one
    // overwhelming the other.
    int saved_tab_groups_bar_available_width =
        GetAvailableWidthForSavedTabGroupsBar(
            saved_tab_group_max_width, estimate_bookmark_buttons_width,
            max_x - x -
                saved_tab_groups_separator_view_->GetPreferredSize().width());

    saved_tab_group_bar_width =
        saved_tab_group_bar_->CalculatePreferredWidthRestrictedBy(
            saved_tab_groups_bar_available_width);
    saved_tab_group_bar_->SetBounds(x, y, saved_tab_group_bar_width,
                                    button_height);

    x += saved_tab_group_bar_width;

    // Add the separator width even if its not shown for correct positioning of
    // the bookmark buttons.
    if (saved_tab_group_bar_width > 0) {
      // Possibly update the paddings of the separator
      constexpr int kSeparatorPadding = 8;
      const int left_padding = saved_tab_group_bar_->IsOverflowButtonVisible()
                                   ? 0
                                   : kSeparatorPadding;
      saved_tab_groups_separator_view_->UpdateBorderAndPreferredSize(
          gfx::Insets::TLBR(0, left_padding, 0, kSeparatorPadding));

      // Update the bounds for the separator.
      gfx::Size saved_tab_groups_separator_view_pref =
          saved_tab_groups_separator_view_->GetPreferredSize();
      saved_tab_groups_separator_view_->SetBounds(
          x, center_y(saved_tab_groups_separator_view_pref.height()),
          saved_tab_groups_separator_view_pref.width(),
          saved_tab_groups_separator_view_pref.height());

      // The right padding of the separator is included in the width.
      x += saved_tab_groups_separator_view_pref.width();
    }
  }

  if (bookmark_bar_children_count) {
    bool can_render_button_bounds = x < max_x;
    size_t button_count = bookmark_buttons_.size();
    BookmarkParentFolderChildren bookmark_bar_children =
        bookmark_service_->GetChildren(bookmark_bar_folder);
    for (size_t i = 0; i <= button_count; ++i) {
      if (i == button_count) {
        // Add another button if there is room for it (and there is another
        // button to load).
        if (!can_render_button_bounds ||
            bookmark_bar_children.size() <= button_count) {
          break;
        }
        InsertBookmarkButtonAtIndex(
            CreateBookmarkButton(bookmark_bar_children[i], i), i);
        button_count = bookmark_buttons_.size();
      }
      views::View* child = bookmark_buttons_[i].first;

      // If the child view can fit in the bookmarks comfortably, make it visible
      // and set its bounds.
      gfx::Size pref = child->GetPreferredSize();
      int next_x = x + pref.width() + bookmark_bar_button_padding;
      can_render_button_bounds = next_x < max_x;
      child->SetVisible(can_render_button_bounds);
      // Only need to set bounds if the view is actually visible.
      if (can_render_button_bounds) {
        child->SetBounds(x, y, pref.width(), button_height);
      }
      x = next_x;
    }
  }

  const bool show_bookmarks_overflow =
      bookmark_service_->loaded() &&
      (bookmark_bar_children_count > bookmark_buttons_.size() ||
       (!bookmark_buttons_.empty() &&
        !bookmark_buttons_.back().first->GetVisible()));

  // Set the visibility of the tab group separator if there are groups and
  // bookmarks.
  if (saved_tab_groups_separator_view_) {
    const bool bookmarks_button_visible =
        !bookmark_buttons_.empty() &&
        bookmark_buttons_.front().first->GetVisible();
    saved_tab_groups_separator_view_->SetVisible(
        saved_tab_group_bar_width > 0 &&
        (bookmarks_button_visible || show_bookmarks_overflow));
  }

  // Layout the right side buttons.
  x = max_x + bookmark_bar_button_padding;

  // The overflow button.
  overflow_button_->SetBounds(x, y, overflow_pref.width(), button_height);
  overflow_button_->SetVisible(show_bookmarks_overflow);
  x += overflow_pref.width();

  // Bookmarks Separator.
  if (bookmarks_separator_view_->GetVisible()) {
    bookmarks_separator_view_->SetBounds(
        x, center_y(bookmarks_separator_pref.height()),
        bookmarks_separator_pref.width(), bookmarks_separator_pref.height());

    x += bookmarks_separator_pref.width();
  }

  // The "All Bookmarks" button.
  if (all_bookmarks_button_->GetVisible()) {
    all_bookmarks_button_->SetBounds(x, y, all_bookmarks_pref.width(),
                                     button_height);
    x += all_bookmarks_pref.width();
  }
}

void BookmarkBarView::ViewHierarchyChanged(
    const views::ViewHierarchyChangedDetails& details) {
  if (details.is_add && details.child == this) {
    // We may get inserted into a hierarchy with a profile - this typically
    // occurs when the bar's contents get populated fast enough that the
    // buttons are created before the bar is attached to a frame.
    UpdateAppearanceForTheme();

    if (height() > 0) {
      // We only layout while parented. When we become parented, if our bounds
      // haven't changed, OnBoundsChanged() won't get invoked and we won't
      // layout. Therefore we always force a layout when added.
      InvalidateLayout();
    }
  }
}

void BookmarkBarView::PaintChildren(const views::PaintInfo& paint_info) {
  View::PaintChildren(paint_info);

  if (drop_info_ && drop_info_->valid &&
      drop_info_->location.operation != DragOperation::kNone &&
      drop_info_->location.index.has_value() &&
      drop_info_->location.button_type != DropButtonType::kDropOverflow &&
      !drop_info_->location.on) {
    size_t index = drop_info_->location.index.value();
    DCHECK_LE(index, bookmark_buttons_.size());
    int x = 0;
    int y = 0;
    int h = height();
    if (index == bookmark_buttons_.size()) {
      if (index != 0) {
        x = bookmark_buttons_[index - 1].first->bounds().right();
      } else if (managed_bookmarks_button_->GetVisible()) {
        x = managed_bookmarks_button_->bounds().right();
      } else if (apps_page_shortcut_->GetVisible()) {
        x = apps_page_shortcut_->bounds().right();
      } else {
        x = GetLeadingMargin();
      }
    } else {
      x = bookmark_buttons_[index].first->x();
    }
    if (!bookmark_buttons_.empty() &&
        bookmark_buttons_.front().first->GetVisible()) {
      y = bookmark_buttons_.front().first->y();
      h = bookmark_buttons_.front().first->height();
    }

    // Since the drop indicator is painted directly onto the canvas, we must
    // make sure it is painted in the right location if the locale is RTL.
    constexpr int kDropIndicatorWidth = 2;
    gfx::Rect indicator_bounds = GetMirroredRect(
        gfx::Rect(x - kDropIndicatorWidth / 2, y, kDropIndicatorWidth, h));

    ui::PaintRecorder recorder(paint_info.context(), size());
    // TODO(sky/glen): make me pretty!
    recorder.canvas()->FillRect(
        indicator_bounds,
        GetColorProvider()->GetColor(kColorBookmarkBarForeground));
  }
}

bool BookmarkBarView::GetDropFormats(
    int* formats,
    std::set<ui::ClipboardFormatType>* format_types) {
  if (!bookmark_service_ || !bookmark_service_->loaded()) {
    return false;
  }
  *formats = ui::OSExchangeData::URL;
  format_types->insert(ui::ClipboardFormatType::BookmarkEntriesType());
  return true;
}

bool BookmarkBarView::AreDropTypesRequired() {
  return true;
}

bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) {
  if (!bookmark_service_ || !bookmark_service_->loaded() ||
      !browser_->GetProfile()->GetPrefs()->GetBoolean(
          bookmarks::prefs::kEditBookmarksEnabled)) {
    return false;
  }

  if (!drop_info_.get()) {
    drop_info_ = std::make_unique<DropInfo>();
  }

  // Only accept drops of 1 node, which is the case for all data dragged from
  // bookmark bar and menus.
  return drop_info_->data.Read(data) && drop_info_->data.size() == 1;
}

void BookmarkBarView::OnDragEntered(const ui::DropTargetEvent& event) {}

int BookmarkBarView::OnDragUpdated(const ui::DropTargetEvent& event) {
  if (!drop_info_.get()) {
    return 0;
  }

  if (drop_info_->valid &&
      (drop_info_->x == event.x() && drop_info_->y == event.y())) {
    // The location of the mouse didn't change, return the last operation.
    return static_cast<int>(drop_info_->location.operation);
  }

  drop_info_->x = event.x();
  drop_info_->y = event.y();

  DropLocation location;
  CalculateDropLocation(event, drop_info_->data, &location);

  if (drop_info_->valid && drop_info_->location.Equals(location)) {
    // The position we're going to drop didn't change, return the last drag
    // operation we calculated. Copy of the operation in case it changed.
    drop_info_->location.operation = location.operation;
    return static_cast<int>(drop_info_->location.operation);
  }

  StopShowFolderDropMenuTimer();

  // TODO(sky): Optimize paint region.
  SchedulePaint();

  drop_info_->location = location;
  drop_info_->valid = true;

  if (drop_info_->is_menu_showing) {
    if (bookmark_drop_menu_) {
      bookmark_drop_menu_->Cancel();
    }
    drop_info_->is_menu_showing = false;
  }

  if (location.button_type == DropButtonType::kDropAllBookmarksFolder) {
    StartShowFolderDropMenuTimer(BookmarkParentFolder::OtherFolder());
  } else if (location.button_type == DropButtonType::kDropOverflow) {
    StartShowFolderDropMenuTimer(BookmarkParentFolder::BookmarkBarFolder());
  } else if (location.on) {
    CHECK_LE(*location.index, bookmark_buttons_.size());
    const BookmarkNode* node = bookmark_buttons_[location.index.value()].second;
    CHECK(node->is_folder());
    StartShowFolderDropMenuTimer(BookmarkParentFolder::FromFolderNode(node));
  }

  return static_cast<int>(drop_info_->location.operation);
}

void BookmarkBarView::OnDragExited() {
  StopShowFolderDropMenuTimer();

  // NOTE: we don't hide the menu on exit as it's possible the user moved the
  // mouse over the menu, which triggers an exit on us.

  if (drop_info_->location.index.has_value()) {
    // TODO(sky): optimize the paint region.
    SchedulePaint();
  }
  drop_info_.reset();
}

views::View::DropCallback BookmarkBarView::GetDropCallback(
    const ui::DropTargetEvent& event) {
  StopShowFolderDropMenuTimer();

  if (bookmark_drop_menu_) {
    bookmark_drop_menu_->Cancel();
  }

  if (!drop_info_ || !drop_info_->valid ||
      drop_info_->location.operation == DragOperation::kNone) {
    return base::NullCallback();
  }

  size_t index = -1;
  const BookmarkParentFolder parent_folder =
      GetParentFolderAndIndexForDrop(index);
  bool copy = drop_info_->location.operation == DragOperation::kCopy;
  bookmarks::BookmarkNodeData drop_data = drop_info_->data;
  drop_info_.reset();
  return base::BindOnce(&BookmarkBarView::PerformDrop,
                        drop_weak_ptr_factory_.GetWeakPtr(),
                        std::move(drop_data), parent_folder, index, copy);
}

void BookmarkBarView::OnThemeChanged() {
  views::AccessiblePaneView::OnThemeChanged();
  UpdateAppearanceForTheme();
}

void BookmarkBarView::VisibilityChanged(View* starting_from, bool is_visible) {
  AccessiblePaneView::VisibilityChanged(starting_from, is_visible);

  if (starting_from == this) {
    observers_.Notify(&BookmarkBarViewObserver::OnBookmarkBarVisibilityChanged);
  }
}

void BookmarkBarView::ChildPreferredSizeChanged(views::View* child) {
  // only rerender
  if (child != saved_tab_group_bar_) {
    return;
  }

  InvalidateDrop();
}

void BookmarkBarView::AddedToWidget() {
  MaybeShowSavedTabGroupsIntroPromo();
}

void BookmarkBarView::AnimationProgressed(const gfx::Animation* animation) {
  // |browser_view_| can be null during tests.
  if (browser_view_) {
    browser_view_->ToolbarSizeChanged(true);
  }
}

void BookmarkBarView::AnimationEnded(const gfx::Animation* animation) {
  // |browser_view_| can be null during tests.
  if (browser_view_) {
    browser_view_->ToolbarSizeChanged(false);
    SchedulePaint();
  }
}

void BookmarkBarView::BookmarkMenuControllerDeleted(
    BookmarkMenuController* controller) {
  if (controller == bookmark_menu_) {
    bookmark_menu_ = nullptr;
  } else if (controller == bookmark_drop_menu_) {
    bookmark_drop_menu_ = nullptr;
  }
}

void BookmarkBarView::BookmarkMergedSurfaceServiceLoaded() {
  // Ensures this method is executed only once, as it can be triggered both
  // directly during initialization and indirectly via a notification from
  // BookmarkMergedSurfaceService. This happens when another
  // BookmarkMergedSurfaceServiceObserver triggers the creation of the bookmark
  // bar, as seen with `BookmarkRestorer::BookmarkMergedSurfaceServiceLoaded`.
  if (bookmark_service_loaded_signal_processed_) {
    return;
  }
  bookmark_service_loaded_signal_processed_ = true;

  // There should be no buttons. If non-zero it means we didn't properly clear
  // things. The actual bookmark buttons are added from Layout().
  DCHECK(bookmark_buttons_.empty());
  const std::u16string all_bookmarks_button_text =
      l10n_util::GetStringUTF16(IDS_BOOKMARKS_ALL_BOOKMARKS);
  all_bookmarks_button_->GetViewAccessibility().SetName(
      all_bookmarks_button_text);
  all_bookmarks_button_->SetText(all_bookmarks_button_text);
  const auto managed_title = managed_->managed_node()->GetTitle();
  managed_bookmarks_button_->GetViewAccessibility().SetName(
      GetFolderButtonAccessibleName(managed_title));
  managed_bookmarks_button_->SetText(managed_title);
  UpdateAppearanceForTheme();
  UpdateOtherAndManagedButtonsVisibility();
  all_bookmarks_button_->SetEnabled(true);
  managed_bookmarks_button_->SetEnabled(true);
  LayoutAndPaint();
}

void BookmarkBarView::BookmarkMergedSurfaceServiceBeingDeleted() {
  NOTREACHED();
}

void BookmarkBarView::BookmarkNodeMoved(const BookmarkParentFolder& old_parent,
                                        size_t old_index,
                                        const BookmarkParentFolder& new_parent,
                                        size_t new_index) {
  // It is extremely rare for the model to mutate during a drop. Rather than
  // trying to validate the location (which may no longer be valid), this takes
  // the simple route of marking the drop as invalid. If the user moves the
  // mouse/touch-device, the location will update accordingly.
  InvalidateDrop();

  if (extensive_bookmarks_changes_ongoing_) {
    // Delays the call to the end of the extensive changes.
    // Assumes that the layout will need an update.
    needs_layout_update_after_extensive_changes_ = true;
    return;
  }

  bool needs_layout_and_paint = BookmarkNodeRemovedImpl(
      old_parent, old_index,
      bookmark_service_->GetNodeAtIndex(new_parent, new_index));
  if (BookmarkNodeAddedImpl(new_parent, new_index)) {
    needs_layout_and_paint = true;
  }
  if (needs_layout_and_paint) {
    LayoutAndPaint();
  }

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkNodeAdded(const BookmarkParentFolder& parent,
                                        size_t index) {
  // See comment in BookmarkNodeMoved() for details on this.
  InvalidateDrop();

  if (extensive_bookmarks_changes_ongoing_) {
    // Delays the call to the end of the extensive changes.
    // Assumes that the layout will need an update.
    needs_layout_update_after_extensive_changes_ = true;
    return;
  }

  if (BookmarkNodeAddedImpl(parent, index)) {
    LayoutAndPaint();
  }

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkNodesRemoved(
    const BookmarkParentFolder& parent,
    const base::flat_set<const bookmarks::BookmarkNode*>& nodes) {
  // See comment in BookmarkNodeMoved() for details on this.
  InvalidateDrop();

  // Close the menu if the menu is showing for the deleted node.
  if (bookmark_menu_) {
    auto bookmark_menu_nodes =
        bookmark_service_->GetUnderlyingNodes(bookmark_menu_->folder());
    // `bookmark_menu_nodes` can only be of size 2 for permanent folders.
    // Permanent folder can't be deleted.
    if (bookmark_menu_nodes.size() == 1u &&
        nodes.find(bookmark_menu_nodes[0]) != nodes.end()) {
      bookmark_menu_->Cancel();
    }
  }

  bool needs_layout = UpdateOtherAndManagedButtonsVisibility();
  if (parent == BookmarkParentFolder::BookmarkBarFolder()) {
    size_t index = 0;
    size_t button_count = bookmark_buttons_.size();
    while (index < bookmark_buttons_.size()) {
      if (nodes.find(bookmark_buttons_[index].second) != nodes.end()) {
        RemoveBookmarkButton(index);
        needs_layout = true;
        CHECK_EQ(--button_count, bookmark_buttons_.size());
      } else {
        ++index;
      }
    }
  }

  if (needs_layout) {
    LayoutAndPaint();
  }

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkAllUserNodesRemoved() {
  // See comment in BookmarkNodeMoved() for details on this.
  InvalidateDrop();

  UpdateOtherAndManagedButtonsVisibility();

  // Remove the existing buttons.
  RemoveAllBookmarkButtons();

  LayoutAndPaint();

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkNodeChanged(const BookmarkNode* node) {
  BookmarkNodeChangedImpl(node);

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkParentFolderChildrenReordered(
    const BookmarkParentFolder& folder) {
  // See comment in BookmarkNodeMoved() for details on this.
  InvalidateDrop();

  if (folder.as_permanent_folder() != PermanentFolderType::kBookmarkBarNode) {
    return;  // We only care about reordering of the bookmark bar node.
  }

  // Remove the existing buttons.
  RemoveAllBookmarkButtons();

  // Create the new buttons.
  BookmarkParentFolderChildren children =
      bookmark_service_->GetChildren(folder);
  for (size_t i = 0; i < children.size(); i++) {
    InsertBookmarkButtonAtIndex(CreateBookmarkButton(children[i], i), i);
  }

  LayoutAndPaint();

  drop_weak_ptr_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::BookmarkNodeFaviconChanged(const BookmarkNode* node) {
  BookmarkNodeChangedImpl(node);
}

void BookmarkBarView::ExtensiveBookmarkChangesBeginning() {
  CHECK(!extensive_bookmarks_changes_ongoing_);
  extensive_bookmarks_changes_ongoing_ = true;
}

void BookmarkBarView::ExtensiveBookmarkChangesEnded() {
  CHECK(extensive_bookmarks_changes_ongoing_);
  extensive_bookmarks_changes_ongoing_ = false;

  if (needs_layout_update_after_extensive_changes_) {
    needs_layout_update_after_extensive_changes_ = false;

    // After extensive changes, the changed (added/removed mainly) bookmarks may
    // not have been updated at the proper index, so remove all existing buttons
    // so that the next layout creates the buttons in the expected order.
    RemoveAllBookmarkButtons();

    UpdateOtherAndManagedButtonsVisibility();

    LayoutAndPaint();
    drop_weak_ptr_factory_.InvalidateWeakPtrs();
  }
}

void BookmarkBarView::WriteDragDataForView(View* sender,
                                           const gfx::Point& press_pt,
                                           ui::OSExchangeData* data) {
  base::RecordAction(base::UserMetricsAction("BookmarkBar_DragButton"));
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(browser_->GetProfile());

  const auto* node = GetNodeForSender(sender);
  ui::ImageModel icon;
  if (node->is_url()) {
    const gfx::Image& image =
        bookmark_service_->bookmark_model()->GetFavicon(node);
    icon = image.IsEmpty()
               ? favicon::GetDefaultFaviconModel(kColorBookmarkBarBackground)
               : ui::ImageModel::FromImage(image);
  } else {
    icon = chrome::GetBookmarkFolderIcon(
        chrome::BookmarkFolderIconType::kNormal, ui::kColorMenuIcon);
  }

  button_drag_utils::SetDragImage(
      node->url(), node->GetTitle(), icon.Rasterize(GetColorProvider()),
      &press_pt, data,
      GetLayoutConstant(LayoutConstant::kBookmarkBarButtonImageLabelPadding));
  WriteBookmarkDragData(node, data);
}

int BookmarkBarView::GetDragOperationsForView(View* sender,
                                              const gfx::Point& p) {
  if (size_animation_.is_animating() ||
      size_animation_.GetCurrentValue() == 0) {
    // Don't let the user drag while animating open or we're closed. This
    // typically is only hit if the user does something to inadvertently trigger
    // DnD such as pressing the mouse and hitting control-b.
    return ui::DragDropTypes::DRAG_NONE;
  }

  return chrome::GetBookmarkDragOperation(browser_->GetProfile(),
                                          GetNodeForSender(sender));
}

bool BookmarkBarView::CanStartDragForView(views::View* sender,
                                          const gfx::Point& press_pt,
                                          const gfx::Point& p) {
  // Check if we have not moved enough horizontally but we have moved downward
  // vertically - downward drag.
  gfx::Vector2d move_offset = p - press_pt;
  gfx::Vector2d horizontal_offset(move_offset.x(), 0);
  if (!View::ExceededDragThreshold(horizontal_offset) && move_offset.y() > 0) {
    // If the folder button was dragged, show the menu instead.
    const auto* node = GetNodeForSender(sender);
    if (node->is_folder()) {
      static_cast<MenuButton*>(sender)->Activate(nullptr);
      return false;
    }
  }
  return true;
}

void BookmarkBarView::AppsPageShortcutPressed(const ui::Event& event) {
  content::OpenURLParams params(GURL(chrome::kChromeUIAppsURL),
                                content::Referrer(),
                                ui::DispositionFromEventFlags(event.flags()),
                                ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);
  page_navigator_->OpenURL(params, /*navigation_handle_callback=*/{});
  RecordBookmarkAppsPageOpen(BookmarkLaunchLocation::kAttachedBar);
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(browser_->GetProfile());
}

void BookmarkBarView::OnButtonPressed(const bookmarks::BookmarkNode* node,
                                      const ui::Event& event) {
  // Only URL nodes have regular buttons on the bookmarks bar; folder clicks
  // are directed to ::OnMenuButtonPressed().
  DCHECK(node->is_url());
  RecordAppLaunch(browser_->GetProfile(), node->url());
  bookmarks::OpenAllIfAllowed(
      browser_, {node}, ui::DispositionFromEventFlags(event.flags()),
      bookmarks::OpenAllBookmarksContext::kNone,
      GetInitiatorLocation(ChromeInitiatorLocation::kBookmarkBar),
      {{BookmarkLaunchLocation::kAttachedBar, base::TimeTicks::Now()}});
  RecordBookmarkLaunch(
      BookmarkLaunchLocation::kAttachedBar,
      profile_metrics::GetBrowserProfileType(browser_->GetProfile()));
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(browser_->GetProfile());
}

void BookmarkBarView::OnMenuButtonPressed(const BookmarkParentFolder& folder,
                                          const ui::Event& event) {
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(browser_->GetProfile());
  // Clicking the middle mouse button or clicking with Control/Command key down
  // opens all bookmarks in the folder in new tabs.
  if ((event.flags() & ui::EF_MIDDLE_MOUSE_BUTTON) ||
      (event.flags() & ui::EF_PLATFORM_ACCELERATOR)) {
    RecordBookmarkFolderLaunch(BookmarkLaunchLocation::kAttachedBar);
    auto nodes = ToRawPtrVector(bookmark_service_->GetUnderlyingNodes(folder));

    bookmarks::OpenAllIfAllowed(
        browser_, nodes, ui::DispositionFromEventFlags(event.flags()),
        bookmarks::OpenAllBookmarksContext::kNone,
        GetInitiatorLocation(ChromeInitiatorLocation::kBookmarkBar),
        {{BookmarkLaunchLocation::kAttachedBar, base::TimeTicks::Now()}});
  } else {
    RecordBookmarkFolderOpen(BookmarkLaunchLocation::kAttachedBar);
    ShowFolderMenuForFolder(folder);
  }
}

bool BookmarkBarView::OnMenuButtonAccessibleAction(
    const BookmarkParentFolder& folder,
    const ui::AXActionData& action_data) {
  if (action_data.action == ax::mojom::Action::kExpand) {
    ShowFolderMenuForFolder(folder);
    return true;
  }
  if (action_data.action == ax::mojom::Action::kCollapse) {
    HideFolderMenuForFolder(folder);
    return true;
  }
  return false;
}

void BookmarkBarView::ShowContextMenuForViewImpl(
    views::View* source,
    const gfx::Point& point,
    ui::mojom::MenuSourceType source_type) {
  if (!bookmark_service_->loaded()) {
    // Don't do anything if the model isn't loaded.
    return;
  }

  views::Button* context_menu_source = nullptr;

  std::vector<const BookmarkNode*> nodes;
  if (source == all_bookmarks_button_) {
    // Do this so the user can open all bookmarks. BookmarkContextMenu makes
    // sure the user can't edit/delete the node in this case.
    nodes = bookmark_service_->GetUnderlyingNodes(
        BookmarkParentFolder::OtherFolder());
    context_menu_source = all_bookmarks_button_;
  } else if (source == managed_bookmarks_button_) {
    nodes = bookmark_service_->GetUnderlyingNodes(
        BookmarkParentFolder::ManagedFolder());
    context_menu_source = managed_bookmarks_button_;
  } else if (source != this && source != apps_page_shortcut_) {
    // User clicked on one of the bookmark buttons, find which one they
    // clicked on, except for the apps page shortcut, which must behave as if
    // the user clicked on the bookmark bar background.
    size_t bookmark_button_index = GetIndexForButton(source);
    DCHECK_NE(static_cast<size_t>(-1), bookmark_button_index);
    CHECK_LT(bookmark_button_index, bookmark_buttons_.size());
    const BookmarkNode* node = bookmark_buttons_[bookmark_button_index].second;
    nodes.push_back(node);
    context_menu_source = bookmark_buttons_[bookmark_button_index].first;
  } else {
    nodes = bookmark_service_->GetUnderlyingNodes(
        BookmarkParentFolder::BookmarkBarFolder());
    if (source == apps_page_shortcut_) {
      context_menu_source = apps_page_shortcut_;
    }
  }

  if (context_menu_source) {
    context_menu_highlight_ = context_menu_source->AddAnchorHighlight();
  }

  std::vector<int64_t> node_ids;
  node_ids.reserve(nodes.size());
  for (const auto* node : nodes) {
    node_ids.push_back(node->id());
  }
  auto parent_folder = BookmarkContextMenuController::GetParentForNewNodes(
      ToRawPtrVector(nodes));
  BookmarkUIOperationsHelperMergedSurfaces(bookmark_service_,
                                           parent_folder.get())
      .CanPasteFromClipboard(base::BindOnce(
          &BookmarkBarView::RunContextMenuAt, weak_ptr_factory_.GetWeakPtr(),
          std::move(node_ids), point, source_type));
}

void BookmarkBarView::RunContextMenuAt(std::vector<int64_t> node_ids,
                                       const gfx::Point& point,
                                       ui::mojom::MenuSourceType source_type,
                                       bool can_paste) {
  // |close_on_remove| only matters for nested menus. We're not nested at this
  // point, so this value has no effect.
  const bool close_on_remove = true;

  auto* bookmark_model =
      BookmarkModelFactory::GetForBrowserContext(browser_->GetProfile());
  std::vector<const BookmarkNode*> nodes;
  for (int64_t node_id : node_ids) {
    const BookmarkNode* node =
        bookmarks::GetBookmarkNodeByID(bookmark_model, node_id);
    if (node) {
      nodes.push_back(node);
    }
  }
  if (nodes.empty()) {
    return;
  }

  context_menu_observation_.Reset();
  context_menu_ = std::make_unique<BookmarkContextMenu>(
      GetWidget(), browser_, browser_->GetProfile(),
      BookmarkLaunchLocation::kAttachedBar, ToRawPtrVector(nodes),
      close_on_remove, can_paste);
  context_menu_observation_.Observe(context_menu_.get());
  context_menu_->RunMenuAt(point, source_type);
}

void BookmarkBarView::OnContextMenuClosed() {
  context_menu_observation_.Reset();
  context_menu_highlight_.reset();
}

// Calculate the available width for the saved tab group bar.
// The following rules are applied to prevent one allocate too much space over
// another.
//
// 1. If saved tab group bar and bookmark buttons can both fit into the space,
// allocate as much as they need to both of them.
//
// 2. When saved tab group bar and bookmark buttons can't both fit into the
// space, if one of them is smaller than half of the available width and the
// other is bigger than it, then allocate enough space to fit the smaller one
// and the rest for the bigger one.
//
// 3. When saved tab group bar and bookmark buttons can't both fit into the
// space, if both of them are bigger than half of the available width, then
// allocate half of the available to each of them.
int BookmarkBarView::GetAvailableWidthForSavedTabGroupsBar(
    int saved_tab_group_bar_width,
    int bookmark_buttons_width,
    int available_width) {
  if (saved_tab_group_bar_width + bookmark_buttons_width <= available_width) {
    return available_width;
  } else if (saved_tab_group_bar_width <= available_width / 2) {
    return available_width;
  } else if (bookmark_buttons_width <= available_width / 2) {
    return available_width - bookmark_buttons_width;
  } else {
    return available_width / 2;
  }
}

void BookmarkBarView::Init() {
  // Note that at this point we're not in a hierarchy so GetColorProvider() will
  // return nullptr.  When we're inserted into a hierarchy, we'll call
  // UpdateAppearanceForTheme(), which will set the appropriate colors for all
  // the objects added in this function.

  // Child views are traversed in the order they are added. Make sure the order
  // they are added matches the visual order.
  apps_page_shortcut_ = AddChildView(CreateAppsPageShortcutButton());

  managed_bookmarks_button_ = AddChildView(CreateManagedBookmarksButton());
  // Also re-enabled when the model is loaded.
  managed_bookmarks_button_->SetEnabled(false);

  if (tab_groups::SavedTabGroupUtils::IsEnabledForProfile(
          browser_->GetProfile())) {
    saved_tab_group_bar_ =
        AddChildView(std::make_unique<tab_groups::SavedTabGroupBar>(
            browser_, animations_enabled));
    saved_tab_group_bar_->SetVisible(
        chrome::ShouldShowTabGroupsInBookmarkBar(browser_->GetProfile()));
    saved_tab_groups_separator_view_ =
        AddChildView(std::make_unique<ButtonSeparatorView>());
    saved_tab_groups_separator_view_->SetVisible(true);
  }

  overflow_button_ = AddChildView(CreateOverflowButton());

  all_bookmarks_button_ = AddChildView(CreateAllBookmarksButton());
  // We'll re-enable when the model is loaded.
  all_bookmarks_button_->SetEnabled(false);



  bookmarks_separator_view_ =
      AddChildView(std::make_unique<ButtonSeparatorView>());
  bookmarks_separator_view_->UpdateBorderAndPreferredSize(
      gfx::Insets::VH(0, 8));

  UpdateBookmarksSeparatorVisibility();

  set_context_menu_controller(this);

  bookmark_service_ = BookmarkMergedSurfaceServiceFactory::GetForProfile(
      browser_->GetProfile());
  managed_ =
      ManagedBookmarkServiceFactory::GetForProfile(browser_->GetProfile());
  if (bookmark_service_) {
    bookmark_service_->AddObserver(this);
    if (bookmark_service_->loaded()) {
      BookmarkMergedSurfaceServiceLoaded();
    }
    // else case: we'll receive notification back from the BookmarkModel when
    // done loading, then we'll populate the bar.
  }
}

size_t BookmarkBarView::GetFirstHiddenNodeIndex() const {
  const auto i = std::ranges::find_if_not(
      bookmark_buttons_, [](const BookmarkButtonAndNode& pair) {
        return pair.first->GetVisible();
      });
  return i - bookmark_buttons_.cbegin();
}

std::unique_ptr<MenuButton> BookmarkBarView::CreateAllBookmarksButton() {
  auto button = CreateMenuButtonForFolder(BookmarkParentFolder::OtherFolder());
  button->SetID(VIEW_ID_ALL_BOOKMARKS);
  button->set_context_menu_controller(this);
  return button;
}

std::unique_ptr<MenuButton> BookmarkBarView::CreateManagedBookmarksButton() {
  // Title is set in Loaded.
  auto button =
      CreateMenuButtonForFolder(BookmarkParentFolder::ManagedFolder());
  button->SetID(VIEW_ID_MANAGED_BOOKMARKS);
  button->set_context_menu_controller(this);
  return button;
}

std::unique_ptr<MenuButton> BookmarkBarView::CreateOverflowButton() {
  auto button = std::make_unique<BookmarkMenuButtonBase>(
      base::BindRepeating(
          [](BookmarkBarView* bar, const ui::Event& event) {
            bar->OnMenuButtonPressed(BookmarkParentFolder::BookmarkBarFolder(),
                                     event);
          },
          base::Unretained(this)),
      base::BindRepeating(&BookmarkBarView::HasDropInfo,
                          base::Unretained(this)));

  // The overflow button's image contains an arrow and therefore it is a
  // direction sensitive image and we need to flip it if the UI layout is
  // right-to-left.
  //
  // By default, menu buttons are not flipped because they generally contain
  // text and flipping the gfx::Canvas object will break text rendering. Since
  // the overflow button does not contain text, we can safely flip it.
  button->SetFlipCanvasOnPaintForRTLUI(true);
  views::FocusRing::Get(button.get())->SetOutsetFocusRingDisabled(true);
  // Make visible as necessary.
  button->SetVisible(false);
  // Set accessibility name.
  button->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON));
  button->SetTooltipText(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OVERFLOW_BUTTON_TOOLTIP));
  return button;
}

std::unique_ptr<views::View> BookmarkBarView::CreateBookmarkButton(
    const BookmarkNode* node,
    size_t index) {
  std::unique_ptr<views::LabelButton> button;
  if (node->is_url()) {
    button = std::make_unique<BookmarkButton>(
        base::BindRepeating(&BookmarkBarView::OnButtonPressed,
                            base::Unretained(this), node),
        node->url(), node->GetTitle(), browser());
    button->GetViewAccessibility().SetDescription(url_formatter::FormatUrl(
        node->url(), url_formatter::kFormatUrlOmitDefaults,
        base::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
  } else {
    CHECK(node->is_folder());
    button = CreateMenuButtonForFolder(
        BookmarkParentFolder::FromFolderNode(node), node->GetTitle());
  }
  ConfigureButton(node, button.get());
  bookmark_buttons_.insert(bookmark_buttons_.cbegin() + index,
                           {button.get(), node});
  button_visibility_changed_callbacks_[button.get()] =
      button->AddVisibleChangedCallback(
          base::BindRepeating(&BookmarkBarView::UpdateFirstHiddenNodeIndex,
                              base::Unretained(this)));
  UpdateFirstHiddenNodeIndex();
  return button;
}

std::unique_ptr<views::MenuButton> BookmarkBarView::CreateMenuButtonForFolder(
    const BookmarkParentFolder& folder,
    std::u16string_view title) {
  return std::make_unique<BookmarkFolderButton>(
      base::BindRepeating(&BookmarkBarView::OnMenuButtonPressed,
                          base::Unretained(this), folder),
      base::BindRepeating(&BookmarkBarView::OnMenuButtonAccessibleAction,
                          base::Unretained(this), folder),
      base::BindRepeating(&BookmarkBarView::HasDropInfo,
                          base::Unretained(this)),
      title);
}

void BookmarkBarView::RemoveBookmarkButton(size_t index) {
  CHECK_LT(index, bookmark_buttons_.size());
  views::LabelButton* button = bookmark_buttons_[index].first;
  bookmark_buttons_.erase(bookmark_buttons_.cbegin() + index);
  button->SetVisible(false);
  button_visibility_changed_callbacks_.erase(button);
  RemoveChildViewT(button);
}

void BookmarkBarView::RemoveAllBookmarkButtons() {
  while (!bookmark_buttons_.empty()) {
    RemoveBookmarkButton(bookmark_buttons_.size() - 1);
  }
}

void BookmarkBarView::UpdateFirstHiddenNodeIndex() {
  size_t prev_idx = first_hidden_node_idx_;
  first_hidden_node_idx_ = GetFirstHiddenNodeIndex();
  if (bookmark_menu_ && prev_idx != first_hidden_node_idx_) {
    bookmark_menu_->BookmarkStartIndexChanged(
        BookmarkParentFolder::BookmarkBarFolder(), first_hidden_node_idx_);
  }
}

std::unique_ptr<views::LabelButton>
BookmarkBarView::CreateAppsPageShortcutButton() {
  auto button = std::make_unique<ShortcutButton>(
      base::BindRepeating(&BookmarkBarView::AppsPageShortcutPressed,
                          base::Unretained(this)),
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME));
  button->SetTooltipText(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP));
  button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);

  ui::ImageModel icon = ui::ImageModel::FromImageSkia(
      *GetImageSkiaNamed(IDR_BOOKMARK_BAR_APPS_SHORTCUT));
  button->SetImageModel(views::Button::STATE_NORMAL, icon);
  button->SetImageModel(
      views::Button::STATE_DISABLED,
      ui::GetDefaultDisabledIconFromImageModel(icon, GetColorProvider()));

  button->set_context_menu_controller(this);
  return button;
}

void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
                                      views::LabelButton* button) {
  button->SetText(node->GetTitle());
  button->GetViewAccessibility().SetName(std::u16string(node->GetTitle()));
  button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);
  // We don't always have a color provider (ui tests, for example).
  SkColor text_color = gfx::kPlaceholderColor;
  const ui::ColorProvider* const cp = GetColorProvider();
  if (cp) {
    text_color = cp->GetColor(kColorBookmarkBarForeground);
    button->SetEnabledTextColors(text_color);
    button->SetTextColor(views::Button::ButtonState::STATE_DISABLED,
                         kColorBookmarkBarForegroundDisabled);
    if (node->is_folder()) {
      ui::ImageModel icon = chrome::GetBookmarkFolderIcon(
          chrome::BookmarkFolderIconType::kNormal, kColorBookmarkFolderIcon);
      button->SetImageModel(views::Button::STATE_NORMAL, icon);
      button->SetImageModel(
          views::Button::STATE_DISABLED,
          chrome::GetBookmarkFolderIcon(chrome::BookmarkFolderIconType::kNormal,
                                        ui::kColorIconDisabled));
    }
  }

  button->set_context_menu_controller(this);
  button->set_drag_controller(this);
  if (node->is_url()) {
    // Themify chrome:// favicons and the default one. This is similar to
    // code in the tabstrip.
    bool themify_icon = favicon::ShouldThemifyFavicon(node->url());
    // TODO(crbug.com/40137576): BookmarkModel::GetFavicon should be updated to
    // support ImageModel.
    auto favicon = ui::ImageModel::FromImage(
        bookmark_service_->bookmark_model()->GetFavicon(node));
    if (favicon.IsEmpty()) {
      if (ui::TouchUiController::Get()->touch_ui() && cp) {
        // This favicon currently does not match the default favicon icon used
        // elsewhere in the codebase.
        // See https://crbug.com/41371804
        const gfx::ImageSkia icon =
            gfx::CreateVectorIcon(kDefaultTouchFaviconCustomIcon, text_color);
        // The color used in `mask` is not relevant as long it is opaque; Only
        // the alpha channel matters.
        const gfx::ImageSkia mask = gfx::CreateVectorIcon(
            kDefaultTouchFaviconMaskCustomIcon, SK_ColorWHITE);
        favicon = ui::ImageModel::FromImageSkia(
            gfx::ImageSkiaOperations::CreateMaskedImage(icon, mask));
      } else {
        favicon = favicon::GetDefaultFaviconModel(kColorBookmarkBarBackground);
      }
      themify_icon = true;
    }

    if (themify_icon && cp) {
      SkColor favicon_color = cp->GetColor(kColorBookmarkFavicon);
      if (favicon_color != SK_ColorTRANSPARENT) {
        favicon = ui::ImageModel::FromImageSkia(
            gfx::ImageSkiaOperations::CreateColorMask(favicon.Rasterize(cp),
                                                      favicon_color));
      }
    }

    button->SetImageModel(
        views::Button::STATE_DISABLED,
        ui::GetDefaultDisabledIconFromImageModel(favicon, GetColorProvider()));
    button->SetImageModel(views::Button::STATE_NORMAL, std::move(favicon));
  }

  button->SetMaxSize(gfx::Size(bookmark_button_util::kMaxButtonWidth, 0));
}

bool BookmarkBarView::BookmarkNodeAddedImpl(const BookmarkParentFolder& parent,
                                            size_t index) {
  CHECK(!extensive_bookmarks_changes_ongoing_);

  const bool needs_layout_and_paint = UpdateOtherAndManagedButtonsVisibility();
  if (parent.as_permanent_folder() != PermanentFolderType::kBookmarkBarNode) {
    return needs_layout_and_paint;
  }

  if (index < bookmark_buttons_.size()) {
    InsertBookmarkButtonAtIndex(
        CreateBookmarkButton(bookmark_service_->GetNodeAtIndex(parent, index),
                             index),
        index);
    return true;
  }
  // If the new node was added after the last button we've created we may be
  // able to fit it. Assume we can by returning true, which forces a layout and
  // creation of the button (if it fits).
  return index == bookmark_buttons_.size();
}

bool BookmarkBarView::BookmarkNodeRemovedImpl(
    const BookmarkParentFolder& old_parent,
    size_t old_index,
    const bookmarks::BookmarkNode* node) {
  const bool needs_layout = UpdateOtherAndManagedButtonsVisibility();
  if (old_parent.as_permanent_folder() !=
      PermanentFolderType::kBookmarkBarNode) {
    return needs_layout;
  }

  if (old_index >= bookmark_buttons_.size()) {
    return needs_layout;
  }

  CHECK_EQ(bookmark_buttons_[old_index].second, node);
  RemoveBookmarkButton(old_index);
  return true;
}

void BookmarkBarView::BookmarkNodeChangedImpl(const BookmarkNode* node) {
  if (node == managed_->managed_node()) {
    // The managed node may have its title updated.
    // If the folder is unnamed, set the name to a default string for unnamed
    // folders; otherwise set the name to the user-supplied folder name.
    const auto managed_title = managed_->managed_node()->GetTitle();
    managed_bookmarks_button_->GetViewAccessibility().SetName(
        GetFolderButtonAccessibleName(managed_title));
    managed_bookmarks_button_->SetText(managed_title);
    return;
  }

  if (node->parent()->type() != BookmarkNode::BOOKMARK_BAR) {
    // We only care about nodes on the bookmark bar.
    return;
  }

  auto it = std::ranges::find(bookmark_buttons_, node,
                              &BookmarkButtonAndNode::second);
  if (it == bookmark_buttons_.end()) {
    return;  // Buttons are created as needed.
  }
  views::LabelButton* button = it->first;
  const int old_pref_width = button->GetPreferredSize().width();
  ConfigureButton(node, button);
  if (old_pref_width != button->GetPreferredSize().width()) {
    LayoutAndPaint();
  }
}

void BookmarkBarView::ShowDropFolderForNode(
    const BookmarkParentFolder& folder) {
  if (bookmark_drop_menu_) {
    if (bookmark_drop_menu_->folder() == folder) {
      // Already showing for the specified node.
      return;
    }
    bookmark_drop_menu_->Cancel();
  }

  MenuButton* menu_button = GetMenuButtonForFolder(folder);
  if (!menu_button) {
    return;
  }

  size_t start_index = 0;
  if (folder.as_permanent_folder() == PermanentFolderType::kBookmarkBarNode) {
    start_index = first_hidden_node_idx_;
  }

  drop_info_->is_menu_showing = true;
  bookmark_drop_menu_ = new BookmarkMenuController(browser_, GetWidget(),
                                                   folder, start_index, true);
  bookmark_drop_menu_->set_observer(this);
  bookmark_drop_menu_->RunMenuAt(this);

  observers_.Notify(&BookmarkBarViewObserver::OnDropMenuShown);
}

void BookmarkBarView::StopShowFolderDropMenuTimer() {
  show_folder_method_factory_.InvalidateWeakPtrs();
}

void BookmarkBarView::StartShowFolderDropMenuTimer(
    const BookmarkParentFolder& folder) {
  if (!animations_enabled) {
    // So that tests can run as fast as possible disable the delay during
    // testing.
    ShowDropFolderForNode(folder);
    return;
  }
  show_folder_method_factory_.InvalidateWeakPtrs();
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&BookmarkBarView::ShowDropFolderForNode,
                     show_folder_method_factory_.GetWeakPtr(), folder),
      views::GetMenuShowDelay());
}

void BookmarkBarView::CalculateDropLocation(
    const ui::DropTargetEvent& event,
    const bookmarks::BookmarkNodeData& data,
    DropLocation* location) {
  DCHECK(bookmark_service_);
  DCHECK(bookmark_service_->loaded());
  DCHECK(data.is_valid());

  *location = DropLocation();

  // The drop event uses the screen coordinates while the child Views are
  // always laid out from left to right (even though they are rendered from
  // right-to-left on RTL locales). Thus, in order to make sure the drop
  // coordinates calculation works, we mirror the event's X coordinate if the
  // locale is RTL.
  int mirrored_x = GetMirroredXInView(event.x());

  bool found = false;
  const int other_delta_x = mirrored_x - all_bookmarks_button_->x();
  Profile* profile = browser_->GetProfile();
  if (all_bookmarks_button_->GetVisible() && other_delta_x >= 0 &&
      other_delta_x < all_bookmarks_button_->width()) {
    // Mouse is over 'other' folder.
    location->button_type = DropButtonType::kDropAllBookmarksFolder;
    location->on = true;
    found = true;
  } else if (bookmark_buttons_.empty()) {
    // No bookmarks, accept the drop.
    location->index = 0;
    const BookmarkNode* const node = data.GetFirstNode(
        bookmark_service_->bookmark_model(), profile->GetPath());
    int ops = node && !managed_->IsNodeManaged(node)
                  ? ui::DragDropTypes::DRAG_MOVE
                  : ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
    location->operation = chrome::GetPreferredBookmarkDropOperation(
        event.source_operations(), ops);
    return;
  }

  for (size_t i = 0; i < bookmark_buttons_.size() &&
                     bookmark_buttons_[i].first->GetVisible() && !found;
       i++) {
    views::LabelButton* button = bookmark_buttons_[i].first;
    int button_x = mirrored_x - button->x();
    int button_w = button->width();
    if (button_x < button_w) {
      found = true;
      const BookmarkNode* node = bookmark_buttons_[i].second;
      if (node->is_folder()) {
        if (button_x <= views::kDropBetweenPixels) {
          location->index = i;
        } else if (button_x < button_w - views::kDropBetweenPixels) {
          location->index = i;
          location->on = true;
        } else {
          location->index = i + 1;
        }
      } else if (button_x < button_w / 2) {
        location->index = i;
      } else {
        location->index = i + 1;
      }
      break;
    }
  }

  if (!found) {
    if (overflow_button_->GetVisible()) {
      // Are we over the overflow button?
      int overflow_delta_x = mirrored_x - overflow_button_->x();
      if (overflow_delta_x >= 0 &&
          overflow_delta_x < overflow_button_->width()) {
        // Mouse is over overflow button.
        location->index = first_hidden_node_idx_;
        location->button_type = DropButtonType::kDropOverflow;
      } else if (overflow_delta_x < 0) {
        // Mouse is after the last visible button but before overflow button;
        // use the last visible index.
        location->index = first_hidden_node_idx_;
      } else {
        return;
      }
    } else if (!all_bookmarks_button_->GetVisible() ||
               mirrored_x < all_bookmarks_button_->x()) {
      // Mouse is after the last visible button but before more recently
      // bookmarked; use the last visible index.
      location->index = first_hidden_node_idx_;
    } else {
      return;
    }
  }

  if (location->on) {
    const BookmarkParentFolder parent_folder = [&]() -> BookmarkParentFolder {
      if (location->button_type == DropButtonType::kDropAllBookmarksFolder) {
        return BookmarkParentFolder::OtherFolder();
      }
      const BookmarkNode* const node =
          bookmark_buttons_[location->index.value()].second;
      return BookmarkParentFolder::FromFolderNode(node);
    }();

    location->operation = GetBookmarkDropOperation(
        profile, event, data, parent_folder,
        bookmark_service_->GetChildrenCount(parent_folder));
  } else {
    location->operation = GetBookmarkDropOperation(
        profile, event, data, BookmarkParentFolder::BookmarkBarFolder(),
        location->index.value());
  }
}

void BookmarkBarView::InvalidateDrop() {
  if (drop_info_ && drop_info_->valid) {
    drop_info_->valid = false;
    SchedulePaint();
  }
  if (bookmark_drop_menu_) {
    bookmark_drop_menu_->Cancel();
  }
  StopShowFolderDropMenuTimer();
}

void BookmarkBarView::ShowFolderMenuForFolder(
    const BookmarkParentFolder& folder) {
  const size_t start_index =
      (folder.as_permanent_folder() == PermanentFolderType::kBookmarkBarNode)
          ? first_hidden_node_idx_
          : 0;
  bookmark_menu_ = new BookmarkMenuController(browser_, GetWidget(), folder,
                                              start_index, false);
  bookmark_menu_->set_observer(this);
  bookmark_menu_->RunMenuAt(this);
}

void BookmarkBarView::HideFolderMenuForFolder(
    const BookmarkParentFolder& folder) {
  if (bookmark_menu_ && bookmark_menu_->folder() == folder) {
    bookmark_menu_->Cancel();
  }
}

const BookmarkNode* BookmarkBarView::GetNodeForSender(View* sender) const {
  const auto i = std::ranges::find(bookmark_buttons_, sender,
                                   &BookmarkButtonAndNode::first);
  DCHECK(i != bookmark_buttons_.cend());
  return i->second;
}

void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node,
                                            ui::OSExchangeData* data) {
  DCHECK(node && data);
  bookmarks::BookmarkNodeData drag_data(node);
  drag_data.Write(browser_->GetProfile()->GetPath(), data);
}

void BookmarkBarView::UpdateAppearanceForTheme() {
  // We don't always have a color provider (ui tests, for example).
  const ui::ColorProvider* color_provider = GetColorProvider();
  if (!color_provider) {
    return;
  }
  for (auto& bookmark_button_and_node : bookmark_buttons_) {
    ConfigureButton(bookmark_button_and_node.second,
                    bookmark_button_and_node.first);
  }

  const SkColor color = color_provider->GetColor(kColorBookmarkBarForeground);
  {
    all_bookmarks_button_->SetEnabledTextColors(color);
    ui::ImageModel all_bookmarks_icon = chrome::GetBookmarkFolderIcon(
        chrome::BookmarkFolderIconType::kNormal, kColorBookmarkFolderIcon);
    all_bookmarks_button_->SetImageModel(views::Button::STATE_NORMAL,
                                         all_bookmarks_icon);
    all_bookmarks_button_->SetImageModel(
        views::Button::STATE_DISABLED,
        chrome::GetBookmarkFolderIcon(chrome::BookmarkFolderIconType::kNormal,
                                      ui::kColorIconDisabled));
  }

  {
    managed_bookmarks_button_->SetEnabledTextColors(color);
    ui::ImageModel managed_bookmarks_button_icon =
        chrome::GetBookmarkFolderIcon(chrome::BookmarkFolderIconType::kManaged,
                                      kColorBookmarkFolderIcon);
    managed_bookmarks_button_->SetImageModel(views::Button::STATE_NORMAL,
                                             managed_bookmarks_button_icon);
    managed_bookmarks_button_->SetImageModel(
        views::Button::STATE_DISABLED,
        chrome::GetBookmarkFolderIcon(chrome::BookmarkFolderIconType::kNormal,
                                      ui::kColorIconDisabled));
  }

  apps_page_shortcut_->SetEnabledTextColors(color);

  const SkColor overflow_color =
      color_provider->GetColor(kColorBookmarkButtonIcon);
  const int overflow_size = 16;
  ui::ImageModel overflow_button_icon = ui::ImageModel::FromVectorIcon(
      features::IsRoundedIconsEnabled() ? kKeyboardDoubleArrowRightIcon
                                        : kBookmarkbarOverflowRefreshOldIcon,
      overflow_color, overflow_size);

  overflow_button_->SetImageModel(views::Button::STATE_NORMAL,
                                  overflow_button_icon);
  overflow_button_->SetImageModel(views::Button::STATE_DISABLED,
                                  ui::GetDefaultDisabledIconFromImageModel(
                                      overflow_button_icon, color_provider));

  // Redraw the background.
  SchedulePaint();
}

bool BookmarkBarView::UpdateOtherAndManagedButtonsVisibility() {
  bool has_other_children =
      bookmark_service_->GetChildrenCount(BookmarkParentFolder::OtherFolder());
  bool update_other = has_other_children != all_bookmarks_button_->GetVisible();
  if (update_other) {
    all_bookmarks_button_->SetVisible(has_other_children);
    UpdateBookmarksSeparatorVisibility();
  }

  bool show_managed = bookmark_service_->GetChildrenCount(
                          BookmarkParentFolder::ManagedFolder()) &&
                      managed_bookmarks_pref_visible_;
  bool update_managed = show_managed != managed_bookmarks_button_->GetVisible();
  if (update_managed) {
    managed_bookmarks_button_->SetVisible(show_managed);
  }

  return update_other || update_managed;
}

void BookmarkBarView::UpdateBookmarksSeparatorVisibility() {
  bookmarks_separator_view_->SetVisible(all_bookmarks_button_->GetVisible());
}

void BookmarkBarView::SetAppsPageShortcutVisibility(bool visible) {
  DCHECK(apps_page_shortcut_);
  // Only perform layout if required.
  if (apps_page_shortcut_->GetVisible() == visible) {
    return;
  }
  apps_page_shortcut_->SetVisible(visible);
  UpdateBookmarksSeparatorVisibility();
  LayoutAndPaint();
}

void BookmarkBarView::SetSavedTabGroupsVisibility(bool visible) {
  if (!saved_tab_group_bar_) {
    return;
  }

  // Only perform layout if required.
  if (saved_tab_group_bar_->GetVisible() == visible) {
    return;
  }
  saved_tab_group_bar_->SetVisible(visible);
  LayoutAndPaint();
}

void BookmarkBarView::SetManagedBookmarksFolderVisibility(bool visible) {
  managed_bookmarks_pref_visible_ = visible;
  if (UpdateOtherAndManagedButtonsVisibility()) {
    LayoutAndPaint();
  }
}

void BookmarkBarView::InsertBookmarkButtonAtIndex(
    std::unique_ptr<views::View> bookmark_button,
    size_t index) {
// All of the secondary buttons are always in the view hierarchy, even if
// they're not visible. The order should be: [Apps shortcut] [Managed bookmark
// button] [saved tab group bar] [..bookmark buttons..] [Overflow chevron]
// [All bookmarks]
#if DCHECK_IS_ON()
  auto i = children().cbegin();
  DCHECK_EQ(*i++, apps_page_shortcut_);
  DCHECK_EQ(*i++, managed_bookmarks_button_);
  if (saved_tab_group_bar_) {
    DCHECK_EQ(*i++, saved_tab_group_bar_);
  }
  if (saved_tab_groups_separator_view_) {
    DCHECK_EQ(*i++, saved_tab_groups_separator_view_);
  }
  const auto is_bookmark_button = [this](const View* v) {
    return (views::IsViewClass<BookmarkButton>(v) ||
            views::IsViewClass<BookmarkFolderButton>(v)) &&
           v != overflow_button_ && v != all_bookmarks_button_;
  };
  i = std::find_if_not(i, children().cend(), is_bookmark_button);
  DCHECK_EQ(*i++, overflow_button_);
  DCHECK_EQ(*i++, all_bookmarks_button_);
#endif
  const auto view_index =
      saved_tab_groups_separator_view_
          ? GetIndexOf(saved_tab_groups_separator_view_).value()
          : GetIndexOf(managed_bookmarks_button_).value();
  AddChildViewAt(std::move(bookmark_button), view_index + 1 + index);
}

size_t BookmarkBarView::GetIndexForButton(views::View* button) {
  auto it = std::ranges::find(bookmark_buttons_, button,
                              &BookmarkButtonAndNode::first);
  if (it == bookmark_buttons_.cend()) {
    return static_cast<size_t>(-1);
  }

  return static_cast<size_t>(it - bookmark_buttons_.cbegin());
}

BookmarkParentFolder BookmarkBarView::GetParentFolderAndIndexForDrop(
    size_t& index) {
  if (drop_info_->location.index.has_value()) {
    // TODO(sky): optimize the SchedulePaint region.
    SchedulePaint();
  }

  if (drop_info_->location.button_type ==
      DropButtonType::kDropAllBookmarksFolder) {
    BookmarkParentFolder other_folder = BookmarkParentFolder::OtherFolder();
    index = bookmark_service_->GetChildrenCount(other_folder);
    return other_folder;
  }

  if (drop_info_->location.on) {
    CHECK(drop_info_->location.index.has_value());
    CHECK_LE(*drop_info_->location.index, bookmark_buttons_.size());
    const BookmarkNode* parent_node =
        bookmark_buttons_[*drop_info_->location.index].second;
    CHECK(parent_node->is_folder());
    BookmarkParentFolder folder(
        BookmarkParentFolder::FromFolderNode(parent_node));
    index = bookmark_service_->GetChildrenCount(folder);
    return folder;
  }

  index = drop_info_->location.index.value();

  return BookmarkParentFolder::BookmarkBarFolder();
}

void BookmarkBarView::PerformDrop(
    const bookmarks::BookmarkNodeData data,
    const BookmarkParentFolder& parent_folder,
    const size_t index,
    const bool copy,
    const ui::DropTargetEvent& event,
    ui::mojom::DragOperation& output_drag_op,
    std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
  DCHECK(data.is_valid());
  DCHECK_NE(index, static_cast<size_t>(-1));

  base::RecordAction(base::UserMetricsAction("BookmarkBar_DragEnd"));
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(browser_->GetProfile());
  output_drag_op =
      BookmarkUIOperationsHelperMergedSurfaces(bookmark_service_,
                                               &parent_folder)
          .DropBookmarks(browser_->GetProfile(), data, index, copy,
                         chrome::BookmarkReorderDropTarget::kBookmarkBarView,
                         browser_);
}

int BookmarkBarView::GetDropLocationModelIndexForTesting() const {
  return (drop_info_ && drop_info_->valid &&
          drop_info_->location.index.has_value())
             ? *(drop_info_->location.index)
             : -1;
}
const views::View* BookmarkBarView::GetSavedTabGroupsSeparatorViewForTesting()
    const {
  return saved_tab_groups_separator_view_;
}

void BookmarkBarView::MaybeShowSavedTabGroupsIntroPromo() const {
  // Check whether to show the synced, or unsyned version of the promo.
  tab_groups::TabGroupSyncService* tab_group_service =
      tab_groups::TabGroupSyncServiceFactory::GetForProfile(
          browser_->GetProfile());
  if (!tab_group_service) {
    return;
  }

  // In order for IPH's for V2 to show up, there must be at least 1 group.
  if (tab_group_service->GetAllGroups().empty()) {
    return;
  }

  user_education::FeaturePromoParams params(
      feature_engagement::kIPHTabGroupsSaveV2IntroFeature);

  const bool everything_button_is_visible =
      saved_tab_group_bar_ && saved_tab_group_bar_->GetVisible();

  // TODO (crbug.com/343258921) Once A11y text is supported with string
  // substitutions, add them in.

  // If tabs groups are syncing...
  if (tab_groups::SavedTabGroupUtils::AreSavedTabGroupsSyncedForProfile(
          browser()->GetProfile())) {
    // Anchor the IPH to the bookmarks bar if the everything button is visible.
    // Otherwise, anchor to the AppMenu.
    if (everything_button_is_visible) {
      params.body_params = {l10n_util::GetStringUTF16(
          IDS_SAVED_TAB_GROUPS_V2_INTRO_IPH_BOOKMARKS_BAR_SYNCED_BODY)};
    } else {
      params.body_params = {l10n_util::GetStringUTF16(
          IDS_SAVED_TAB_GROUPS_V2_INTRO_IPH_APP_MENU_SYNCED_BODY)};
    }
  } else {
    // Anchor the IPH to the bookmarks bar if the everything button is visible.
    // Otherwise, anchor to the AppMenu.
    if (everything_button_is_visible) {
      params.body_params =
          std::vector<std::u16string>{l10n_util::GetStringUTF16(
              IDS_SAVED_TAB_GROUPS_V2_INTRO_IPH_BOOKMARKS_BAR_NOT_SYNCED_BODY)};
    } else {
      params.body_params =
          std::vector<std::u16string>{l10n_util::GetStringUTF16(
              IDS_SAVED_TAB_GROUPS_V2_INTRO_IPH_APP_MENU_NOT_SYNCED_BODY)};
    }
  }

  BrowserUserEducationInterface::From(browser())->MaybeShowStartupFeaturePromo(
      std::move(params));
}

bool BookmarkBarView::HasDropInfo() const {
  return drop_info_.get();
}

BEGIN_METADATA(BookmarkBarView)
ADD_PROPERTY_METADATA(bool, InfoBarVisible)
ADD_READONLY_PROPERTY_METADATA(size_t, FirstHiddenNodeIndex)
END_METADATA
