// Copyright 2025 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/toolbar/split_tabs_button.h"

#include <memory>
#include <optional>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/i18n/rtl.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser_commands.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/layout_constants.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/split_tab_menu_model.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/tabs/split_tab_util.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/pinned_action_toolbar_button_menu_model.h"
#include "chrome/browser/ui/views/toolbar/pinned_toolbar_button_status_indicator.h"
#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_controller.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/prefs/pref_service.h"
#include "components/split_tabs/split_tab_id.h"
#include "components/split_tabs/split_tab_visual_data.h"
#include "components/tabs/public/split_tab_data.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/menu_source_utils.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/view_class_properties.h"

SplitTabsToolbarButton::SplitTabsToolbarButton(BrowserWindowInterface* browser)
    : ToolbarButton(
          views::Button::PressedCallback(),
          std::make_unique<PinnedActionToolbarButtonMenuModel>(browser,
                                                               kActionSplitTab),
          nullptr,
          false),
      browser_(browser) {
  SetProperty(views::kElementIdentifierKey,
              kToolbarSplitTabsToolbarButtonElementId);
  set_menu_identifier(kToolbarSplitTabsMenuElementId);
  SetButtonController(std::make_unique<views::MenuButtonController>(
      this,
      base::BindRepeating(&SplitTabsToolbarButton::ButtonPressed,
                          base::Unretained(this)),
      std::make_unique<views::Button::DefaultButtonControllerDelegate>(this)));
  pin_state_.Init(
      prefs::kPinSplitTabButton, browser_->GetProfile()->GetPrefs(),
      base::BindRepeating(&SplitTabsToolbarButton::UpdateButtonVisibility,
                          base::Unretained(this)));
  views::View* const image_container = image_container_view();
  status_indicator_ =
      PinnedToolbarButtonStatusIndicator::Install(image_container);
  status_indicator_->SetColorId(kColorToolbarActionItemEngaged,
                                kColorToolbarButtonIconInactive);
  UpdateButtonVisibility();
  split_tab_menu_ = std::make_unique<SplitTabMenuModel>(
      browser_->GetTabStripModel(),
      SplitTabMenuModel::MenuSource::kToolbarButton);
  browser->GetTabStripModel()->AddObserver(this);
}

SplitTabsToolbarButton::~SplitTabsToolbarButton() {
  browser_->GetTabStripModel()->RemoveObserver(this);
}

void SplitTabsToolbarButton::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (selection.active_tab_changed()) {
    UpdateButtonVisibility();
  }
}

void SplitTabsToolbarButton::OnSplitTabChanged(const SplitTabChange& change) {
  if (change.type == SplitTabChange::Type::kAdded ||
      change.type == SplitTabChange::Type::kRemoved ||
      change.type == SplitTabChange::Type::kContentsChanged ||
      change.type == SplitTabChange::Type::kVisualsChanged) {
    UpdateButtonVisibility();
  }
}

void SplitTabsToolbarButton::Layout(PassKey) {
  LayoutSuperclass<ToolbarButton>(this);
  gfx::Rect status_rect(kSplitTabsStatusIndicatorWidth,
                        kSplitTabsStatusIndicatorHeight);
  const gfx::Rect image_container_bounds =
      image_container_view()->GetLocalBounds();
  const int new_x =
      image_container_bounds.x() +
      (image_container_bounds.width() - kSplitTabsStatusIndicatorWidth) / 2;
  const int new_y =
      image_container_bounds.bottom() + kSplitTabsStatusIndicatorSpacing;
  status_rect.set_origin(gfx::Point(new_x, new_y));
  status_indicator_->SetBoundsRect(status_rect);
}

void SplitTabsToolbarButton::UpdateIcon() {
  const std::optional<VectorIcons>& icons = GetVectorIcons();
  if (!icons.has_value() || !GetColorProvider()) {
    return;
  }

  if (IsActiveTabInSplit()) {
    const gfx::VectorIcon& icon = ui::TouchUiController::Get()->touch_ui()
                                      ? icons->touch_icon
                                      : icons->icon;
    SkColor engaged_color =
        GetColorProvider()->GetColor(kColorToolbarActionItemEngaged);
    UpdateIconsWithColors(icon, engaged_color, engaged_color, engaged_color,
                          GetForegroundColor(ButtonState::STATE_DISABLED));
  } else {
    ToolbarButton::UpdateIcon();
  }
}

const std::optional<ToolbarButton::VectorIcons>&
SplitTabsToolbarButton::GetIconsForTesting() {
  return GetVectorIcons();
}

bool SplitTabsToolbarButton::IsActiveTabInSplit() {
  TabStripModel* const tab_strip_model = browser_->GetTabStripModel();
  return tab_strip_model && tab_strip_model->GetActiveTab() &&
         tab_strip_model->GetActiveTab()->IsSplit();
}

void SplitTabsToolbarButton::ButtonPressed(const ui::Event& event) {
  if (IsActiveTabInSplit()) {
    if (menu_runner_ && menu_runner_->IsRunning()) {
      menu_runner_->Cancel();
      return;
    }

    // Force the button to pop out and lay itself out before calculating bounds.
    BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
    if (browser_view && browser_view->toolbar() &&
        browser_view->toolbar()->toolbar_controller()) {
      browser_view->toolbar()->toolbar_controller()->PopOut(
          kToolbarSplitTabsToolbarButtonElementId,
          /*show_synchronously=*/true);
    }

    menu_runner_.reset();
    menu_model_adapter_.reset();

    menu_model_adapter_ =
        std::make_unique<views::MenuModelAdapter>(split_tab_menu_.get());
    std::unique_ptr<views::MenuItemView> root =
        menu_model_adapter_->CreateMenu();
    root->SetSubmenuId(menu_identifier());
    menu_runner_ = std::make_unique<views::MenuRunner>(
        std::move(root), views::MenuRunner::HAS_MNEMONICS);
    menu_runner_->RunMenuAt(
        GetWidget(),
        static_cast<views::MenuButtonController*>(button_controller()),
        GetAnchorBoundsInScreen(), views::MenuAnchorPosition::kTopLeft,
        ui::GetMenuSourceTypeForEvent(event));
    MaybeNotifyIndirectAccessIPHUsed();
  } else {
    chrome::NewSplitTab(browser_, split_tabs::SplitTabLayout::kSideBySide,
                        split_tabs::SplitTabCreatedSource::kToolbarButton);
  }
}

void SplitTabsToolbarButton::UpdateButtonVisibility() {
  // Force the menu to close if it's open because the active tab or split may
  // have changed and invalidated the menu.
  if (menu_runner_ && menu_runner_->IsRunning()) {
    menu_runner_->Cancel();
  }
  const bool is_active_tab_in_split = IsActiveTabInSplit();
  UpdateButtonIcon();
  UpdateStatusIndicator(is_active_tab_in_split);
  SetVisible(pin_state_.GetValue() || is_active_tab_in_split);
  UpdateAccessibilityRole(is_active_tab_in_split);
  UpdateAccessibilityLabel(is_active_tab_in_split);
  MaybeAbortIndirectAccessIPH();
}

void SplitTabsToolbarButton::UpdateButtonIcon() {
  TabStripModel* const tab_strip_model = browser_->GetTabStripModel();
  tabs::TabInterface* const active_tab = tab_strip_model->GetActiveTab();
  if (active_tab && active_tab->IsSplit()) {
    const split_tabs::SplitTabActiveLocation location =
        split_tabs::GetLastActiveTabLocation(tab_strip_model,
                                             active_tab->GetSplit().value());
    constexpr auto kOldIcons = base::MakeFixedFlatMap<
        split_tabs::SplitTabActiveLocation, const gfx::VectorIcon*>({
        {split_tabs::SplitTabActiveLocation::kStart, &kSplitSceneLeftOldIcon},
        {split_tabs::SplitTabActiveLocation::kEnd, &kSplitSceneRightOldIcon},
        {split_tabs::SplitTabActiveLocation::kTop, &kSplitSceneUpOldIcon},
        {split_tabs::SplitTabActiveLocation::kBottom, &kSplitSceneDownOldIcon},
    });
    constexpr auto kRoundedIcons =
        base::MakeFixedFlatMap<split_tabs::SplitTabActiveLocation,
                               const gfx::VectorIcon*>({
            {split_tabs::SplitTabActiveLocation::kStart, &kSplitSceneLeftIcon},
            {split_tabs::SplitTabActiveLocation::kEnd, &kSplitSceneRightIcon},
            {split_tabs::SplitTabActiveLocation::kTop, &kSplitSceneUpIcon},
            {split_tabs::SplitTabActiveLocation::kBottom, &kSplitSceneDownIcon},
        });
    if (features::IsRoundedIconsEnabled()) {
      SetVectorIcon(*kRoundedIcons.at(location));
    } else {
      SetVectorIcon(*kOldIcons.at(location));
    }
  } else {
    SetVectorIcon(features::IsRoundedIconsEnabled() ? kSplitSceneIcon
                                                    : kSplitSceneOldIcon);
  }
}

void SplitTabsToolbarButton::UpdateStatusIndicator(bool show_status_indicator) {
  if (show_status_indicator) {
    status_indicator_->Show();
  } else {
    status_indicator_->Hide();
  }
}

void SplitTabsToolbarButton::UpdateAccessibilityRole(bool has_menu) {
  auto role =
      has_menu ? ax::mojom::Role::kPopUpButton : ax::mojom::Role::kButton;

  if (role == GetViewAccessibility().GetCachedRole()) {
    return;
  }

  GetViewAccessibility().SetRole(role);
  GetViewAccessibility().SetHasPopup(has_menu ? ax::mojom::HasPopup::kMenu
                                              : ax::mojom::HasPopup::kFalse);
}

void SplitTabsToolbarButton::UpdateAccessibilityLabel(bool is_enabled) {
  auto string_id = IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_PINNED;
  if (is_enabled) {
    TabStripModel* const tab_strip_model = browser_->GetTabStripModel();
    tabs::TabInterface* const active_tab = tab_strip_model->GetActiveTab();
    CHECK(active_tab);
    std::optional<split_tabs::SplitTabId> split_tab_id = active_tab->GetSplit();
    CHECK(split_tab_id.has_value());
    const split_tabs::SplitTabActiveLocation location =
        split_tabs::GetLastActiveTabLocation(tab_strip_model,
                                             split_tab_id.value());
    const bool is_rtl = base::i18n::IsRTL();
    switch (location) {
      case split_tabs::SplitTabActiveLocation::kStart:
        string_id =
            is_rtl ? IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_RIGHT_ACTIVE
                   : IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_LEFT_ACTIVE;
        break;
      case split_tabs::SplitTabActiveLocation::kEnd:
        string_id =
            is_rtl ? IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_LEFT_ACTIVE
                   : IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_RIGHT_ACTIVE;
        break;
      case split_tabs::SplitTabActiveLocation::kTop:
        string_id = IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_TOP_ACTIVE;
        break;
      case split_tabs::SplitTabActiveLocation::kBottom:
        string_id = IDS_ACCNAME_SPLIT_TABS_TOOLBAR_BUTTON_ENABLED_BOTTOM_ACTIVE;
        break;
    }
  }

  GetViewAccessibility().SetName(l10n_util::GetStringUTF16(string_id));
  SetTooltipText(l10n_util::GetStringUTF16(string_id));
}

void SplitTabsToolbarButton::MaybeNotifyIndirectAccessIPHUsed() {
  if (auto* const user_ed = BrowserUserEducationInterface::From(browser_);
      tabs::IsSplitViewHorizontalIndirectAccessEnabled() && user_ed &&
      user_ed->IsFeaturePromoActive(
          feature_engagement::kIPHSplitViewHorizontalIndirectAccessFeature)) {
    user_ed->NotifyFeaturePromoFeatureUsed(
        feature_engagement::kIPHSplitViewHorizontalIndirectAccessFeature,
        FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
  }
}

void SplitTabsToolbarButton::MaybeAbortIndirectAccessIPH() {
  if (auto* const user_ed = BrowserUserEducationInterface::From(browser_);
      tabs::IsSplitViewHorizontalIndirectAccessEnabled() && user_ed) {
    TabStripModel* const tab_strip_model = browser_->GetTabStripModel();
    tabs::TabInterface* const active_tab = tab_strip_model->GetActiveTab();
    if (!active_tab || !active_tab->IsSplit() ||
        tab_strip_model->GetSplitData(active_tab->GetSplit().value())
                ->visual_data()
                ->split_layout() == split_tabs::SplitTabLayout::kStacked) {
      user_ed->AbortFeaturePromo(
          feature_engagement::kIPHSplitViewHorizontalIndirectAccessFeature);
    }
  }
}

BEGIN_METADATA(SplitTabsToolbarButton)
END_METADATA
