// 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/tabs/tab_menu_model.h"

#include "base/callback_list.h"
#include "base/feature_list.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/commerce/shopping_service_factory.h"
#include "chrome/browser/glic/host/glic.mojom-shared.h"
#include "chrome/browser/glic/host/glic_features.mojom.h"
#include "chrome/browser/glic/public/glic_invoke_options.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/glic/public/service/glic_instance_coordinator.h"
#include "chrome/browser/glic/test_support/glic_test_environment.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/tabs/existing_base_sub_menu_model.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.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_swap_menu_model.h"
#include "chrome/browser/ui/tabs/split_view_layout_menu_model.h"
#include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/menu_model_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/feature_utils.h"
#include "components/commerce/core/mock_account_checker.h"
#include "components/commerce/core/mock_shopping_service.h"
#include "components/commerce/core/shopping_service.h"
#include "components/commerce/core/test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/optimization_guide/core/model_execution/model_execution_features.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/prefs/testing_pref_service.h"
#include "components/send_tab_to_self/fake_send_tab_to_self_model.h"
#include "components/send_tab_to_self/features.h"
#include "components/send_tab_to_self/send_tab_to_self_model.h"
#include "components/send_tab_to_self/stub_send_tab_to_self_sync_service.h"
#include "components/send_tab_to_self/target_device_info.h"
#include "components/tabs/public/split_tab_data.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/menus/simple_menu_model.h"

class TabMenuModelBrowserTest : public MenuModelTest,
                                public InProcessBrowserTest {
 public:
  Profile* profile() { return browser()->GetProfile(); }

  void ActivateSwapWithSplitSubmenuCommand(
      int tab_index,
      SplitTabSwapMenuModel::CommandId command_id) {
    TabMenuModel menu(&delegate_,
                      browser()->GetFeatures().tab_menu_model_delegate(),
                      browser()->tab_strip_model(), tab_index);
    size_t submenu_index =
        menu.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .value();
    ui::SimpleMenuModel* submenu = static_cast<ui::SimpleMenuModel*>(
        menu.GetSubmenuModelAt(submenu_index));
    submenu->ActivatedAt(static_cast<size_t>(
        submenu->GetIndexOfCommandId(static_cast<int>(command_id)).value()));
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, Basics) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  TabMenuModel model(&delegate_,
                     browser()->GetFeatures().tab_menu_model_delegate(),
                     browser()->tab_strip_model(), 0);

  // Verify it has items. The number varies by platform, so we don't check
  // the exact number.
  EXPECT_GT(model.GetItemCount(), 5u);

  int item_count = 0;
  CountEnabledExecutable(&model, &item_count);
  EXPECT_GT(item_count, 0);
  EXPECT_EQ(item_count, delegate_.execute_count_);
  EXPECT_EQ(item_count, delegate_.enable_count_);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, MoveToNewWindow) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  TabMenuModel model(&delegate_,
                     browser()->GetFeatures().tab_menu_model_delegate(),
                     browser()->tab_strip_model(), 0);

  // Verify that CommandMoveTabsToNewWindow is in the menu.
  EXPECT_TRUE(
      model.GetIndexOfCommandId(TabStripModel::CommandMoveTabsToNewWindow)
          .has_value());
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, AddToExistingGroupSubmenu) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();

  tab_strip_model->AddToNewGroup({0});
  tab_strip_model->AddToNewGroup({1});
  tab_strip_model->AddToNewGroup({2});

  TabMenuModel menu(&delegate_,
                    browser()->GetFeatures().tab_menu_model_delegate(),
                    tab_strip_model, 3);

  size_t submenu_index =
      menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
          .value();
  ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);

  EXPECT_EQ(submenu->GetItemCount(), 5u);
  EXPECT_EQ(submenu->GetCommandIdAt(0),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId);
  EXPECT_EQ(submenu->GetTypeAt(1), ui::MenuModel::TYPE_SEPARATOR);
  EXPECT_EQ(submenu->GetCommandIdAt(2),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 1);
  EXPECT_FALSE(submenu->GetIconAt(2).IsEmpty());
  EXPECT_EQ(submenu->GetCommandIdAt(3),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 2);
  EXPECT_EQ(submenu->GetCommandIdAt(4),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 3);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest,
                       AddToExistingGroupSubmenu_DoesNotIncludeCurrentGroup) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();

  tab_strip_model->AddToNewGroup({0});
  tab_strip_model->AddToNewGroup({1});
  tab_strip_model->AddToNewGroup({2});

  TabMenuModel menu(&delegate_,
                    browser()->GetFeatures().tab_menu_model_delegate(),
                    tab_strip_model, 1);

  size_t submenu_index =
      menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
          .value();
  ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);

  EXPECT_EQ(submenu->GetItemCount(), 4u);
  EXPECT_EQ(submenu->GetCommandIdAt(0),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId);
  EXPECT_EQ(submenu->GetTypeAt(1), ui::MenuModel::TYPE_SEPARATOR);
  EXPECT_EQ(submenu->GetCommandIdAt(2),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 1);
  EXPECT_FALSE(submenu->GetIconAt(2).IsEmpty());
  EXPECT_EQ(submenu->GetCommandIdAt(3),
            ExistingBaseSubMenuModel::kMinExistingTabGroupCommandId + 2);
}

// In some cases, groups may change after the menu is created. For example an
// extension may modify groups while the menu is open. If a group referenced in
// the menu goes away, ensure we handle this gracefully.
//
// Regression test for crbug.com/40055511
IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest,
                       AddToExistingGroupAfterGroupDestroyed) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  tab_strip_model->AddToNewGroup({0});

  TabMenuModel menu(&delegate_,
                    browser()->GetFeatures().tab_menu_model_delegate(),
                    tab_strip_model, 1);

  size_t submenu_index =
      menu.GetIndexOfCommandId(TabStripModel::CommandAddToExistingGroup)
          .value();
  ui::MenuModel* submenu = menu.GetSubmenuModelAt(submenu_index);

  EXPECT_EQ(submenu->GetItemCount(), 3u);

  // Ungroup the tab at 0 to make the group in the menu dangle.
  tab_strip_model->RemoveFromGroup({0});

  // Try adding to the group from the menu.
  submenu->ActivatedAt(2);

  EXPECT_FALSE(tab_strip_model->GetTabGroupForTab(0).has_value());
  EXPECT_FALSE(tab_strip_model->GetTabGroupForTab(1).has_value());
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, ActiveTabNotSplit) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 4);
  EXPECT_EQ(tab_strip_model->active_index(), 3);

  tab_strip_model->ExecuteContextMenuCommand(2,
                                             TabStripModel::CommandAddToSplit);
  tab_strip_model->ActivateTabAt(0);

  // Active tab is not split, context menu index is active tab
  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 0);

    EXPECT_TRUE(menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
                    .has_value());
    EXPECT_FALSE(
        menu_model
            .GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .has_value());
    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .has_value());
  }

  // Active tab is not split, context menu index is on inactive tab
  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 1);

    EXPECT_TRUE(menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
                    .has_value());
    EXPECT_FALSE(
        menu_model
            .GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .has_value());
    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .has_value());
  }

  // Active tab is not split, context menu index is on inactive split tab
  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 2);

    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
            .has_value());
    EXPECT_FALSE(
        menu_model
            .GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .has_value());
    EXPECT_TRUE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .has_value());
  }
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, SplitActiveTab) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 4);
  EXPECT_EQ(tab_strip_model->active_index(), 3);

  tab_strip_model->ExecuteContextMenuCommand(2,
                                             TabStripModel::CommandAddToSplit);

  // Active tab is split, context menu index is active tab
  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 3);

    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
            .has_value());
    EXPECT_FALSE(
        menu_model
            .GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .has_value());
    EXPECT_TRUE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .has_value());
  }

  // Active tab is split, context menu index is on inactive tab
  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 1);

    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
            .has_value());
    EXPECT_TRUE(
        menu_model
            .GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
            .has_value());
    EXPECT_FALSE(
        menu_model.GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .has_value());
  }
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, MultiSelectTabs) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 4);
  EXPECT_EQ(tab_strip_model->active_index(), 3);

  tab_strip_model->ActivateTabAt(1);
  tab_strip_model->AddSelectionFromAnchorTo(2);

  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 2);

    auto index =
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit);

    EXPECT_TRUE(index.has_value());
    EXPECT_TRUE(menu_model.IsEnabledAt(index.value()));
  }

  tab_strip_model->ActivateTabAt(0);
  tab_strip_model->AddSelectionFromAnchorTo(2);

  {
    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 2);

    auto index =
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit);

    EXPECT_TRUE(index.has_value());
    EXPECT_FALSE(menu_model.IsEnabledAt(index.value()));
  }
}

class TabMenuModelSplitViewHorizontalBrowserTest
    : public TabMenuModelBrowserTest {
 public:
  TabMenuModelSplitViewHorizontalBrowserTest() {
    scoped_feature_list_.InitAndEnableFeature(tabs::kSplitViewHorizontal);
  }

  ui::SimpleMenuModel* GetArrangeSplitSubmenu(
      int tab_index,
      std::unique_ptr<TabMenuModel>& out_menu_model) {
    out_menu_model = std::make_unique<TabMenuModel>(
        &delegate_, browser()->GetFeatures().tab_menu_model_delegate(),
        browser()->tab_strip_model(), tab_index);
    size_t arrange_submenu_index =
        out_menu_model->GetIndexOfCommandId(TabStripModel::CommandArrangeSplit)
            .value();
    return static_cast<ui::SimpleMenuModel*>(
        out_menu_model->GetSubmenuModelAt(arrange_submenu_index));
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(TabMenuModelSplitViewHorizontalBrowserTest,
                       ToggleOrientationMenuAction) {
  chrome::NewTab(browser(), NewTabTypes::kNewTabCommand);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  ASSERT_EQ(tab_strip_model->count(), 2);

  // Creates a side-by-side split.
  tab_strip_model->AddToNewSplit(
      {0},
      split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kSideBySide),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  auto split_id = tab_strip_model->GetSplitForTab(1);
  ASSERT_TRUE(split_id.has_value());
  EXPECT_EQ(tab_strip_model->GetSplitData(split_id.value())
                ->visual_data()
                ->split_layout(),
            split_tabs::SplitTabLayout::kSideBySide);

  // Verifies the context menu when the split view is side by side.
  std::unique_ptr<TabMenuModel> menu_model;
  ui::SimpleMenuModel* arrange_submenu = GetArrangeSplitSubmenu(1, menu_model);

  int toggle_cmd_id =
      ExistingBaseSubMenuModel::kMinSplitTabMenuModelCommandId +
      static_cast<int>(SplitTabMenuModel::CommandId::kToggleOrientation);

  size_t item_idx = arrange_submenu->GetIndexOfCommandId(toggle_cmd_id).value();
  EXPECT_EQ(arrange_submenu->GetLabelAt(item_idx),
            l10n_util::GetStringUTF16(IDS_SPLIT_TAB_SHOW_STACKED));

  // Toggle split view orientation.
  arrange_submenu->ActivatedAt(item_idx);
  EXPECT_EQ(tab_strip_model->GetSplitData(split_id.value())
                ->visual_data()
                ->split_layout(),
            split_tabs::SplitTabLayout::kStacked);

  // Verifies the context menu when the split view is stacked.
  std::unique_ptr<TabMenuModel> menu_model_2;
  ui::SimpleMenuModel* arrange_submenu_2 =
      GetArrangeSplitSubmenu(1, menu_model_2);

  size_t item_idx_2 =
      arrange_submenu_2->GetIndexOfCommandId(toggle_cmd_id).value();
  EXPECT_EQ(arrange_submenu_2->GetLabelAt(item_idx_2),
            l10n_util::GetStringUTF16(IDS_SPLIT_TAB_SHOW_SIDE_BY_SIDE));
}

class TabMenuModelSplitViewHorizontalDirectAccessBrowserTest
    : public TabMenuModelBrowserTest {
 public:
  TabMenuModelSplitViewHorizontalDirectAccessBrowserTest() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        /*enabled_features=*/{{tabs::kSplitViewHorizontal,
                               {{"split_view_horizontal_direct_access",
                                 "true"}}}},
        /*disabled_features=*/{});
  }

  void TestNewSplit(SplitViewLayoutMenuModel::CommandId command_id,
                    split_tabs::SplitTabLayout expected_layout) {
    chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

    TabStripModel* tab_strip_model = browser()->tab_strip_model();
    ASSERT_EQ(tab_strip_model->count(), 2);
    ASSERT_EQ(tab_strip_model->active_index(), 1);

    TabMenuModel menu_model(&delegate_,
                            browser()->GetFeatures().tab_menu_model_delegate(),
                            tab_strip_model, 0);

    size_t submenu_index =
        menu_model.GetIndexOfCommandId(TabStripModel::CommandAddToSplit)
            .value();
    ui::SimpleMenuModel* submenu = static_cast<ui::SimpleMenuModel*>(
        menu_model.GetSubmenuModelAt(submenu_index));
    submenu->ActivatedAt(static_cast<size_t>(
        submenu->GetIndexOfCommandId(static_cast<int>(command_id)).value()));

    EXPECT_TRUE(tab_strip_model->GetActiveTab()->GetSplit().has_value());
    EXPECT_EQ(
        expected_layout,
        tab_strip_model
            ->GetSplitData(tab_strip_model->GetActiveTab()->GetSplit().value())
            ->visual_data()
            ->split_layout());
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(TabMenuModelSplitViewHorizontalDirectAccessBrowserTest,
                       NewSideBySideSplit) {
  TestNewSplit(SplitViewLayoutMenuModel::CommandId::kSideBySide,
               split_tabs::SplitTabLayout::kSideBySide);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelSplitViewHorizontalDirectAccessBrowserTest,
                       NewStackedSplit) {
  TestNewSplit(SplitViewLayoutMenuModel::CommandId::kStacked,
               split_tabs::SplitTabLayout::kStacked);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, SwapWithActiveTab) {
  // Add 3 tabs to the browser.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 3);

  // Create a split. Assert that the last two tabs are split with the rightmost
  // tab active.
  tab_strip_model->ActivateTabAt(0);
  tab_strip_model->AddToNewSplit(
      {1},
      split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kSideBySide),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
  EXPECT_EQ(tab_strip_model->active_index(), 0);

  // Trigger the swap start tab command.
  ActivateSwapWithSplitSubmenuCommand(
      2, SplitTabSwapMenuModel::CommandId::kSwapStartTab);

  // Check that now the left two tabs are in a split and the left (swapped) tab
  // in the split is active.
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
  EXPECT_EQ(tab_strip_model->active_index(), 0);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, SwapWithInactiveTab) {
  // Add 3 tabs to the browser.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 3);

  // Create a split. Assert that the last two tabs are split with the rightmost
  // tab active.
  tab_strip_model->AddToNewSplit(
      {1},
      split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kSideBySide),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(2).has_value());
  EXPECT_EQ(tab_strip_model->active_index(), 2);

  // Trigger the swap start tab command.
  ActivateSwapWithSplitSubmenuCommand(
      0, SplitTabSwapMenuModel::CommandId::kSwapStartTab);

  // Check that now the right two tabs are in a split and the right (swapped)
  // tab in the split is active.
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(2).has_value());
  EXPECT_EQ(tab_strip_model->active_index(), 2);
}

IN_PROC_BROWSER_TEST_F(TabMenuModelBrowserTest, SwapWithSplitActiveTabChanged) {
  // Add 3 tabs to the browser.
  chrome::NewTab(browser(), NewTabTypes::kNewTabCommand);
  chrome::NewTab(browser(), NewTabTypes::kNewTabCommand);
  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  EXPECT_EQ(tab_strip_model->count(), 3);

  // Create a split with tabs 0 and 1. Tab 0 is active.
  tab_strip_model->ActivateTabAt(0);
  tab_strip_model->AddToNewSplit(
      {1},
      split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kSideBySide),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
  EXPECT_EQ(tab_strip_model->active_index(), 0);

  // Create the TabMenuModel for tab 2. This instantiates SplitTabSwapMenuModel.
  TabMenuModel menu(&delegate_,
                    browser()->GetFeatures().tab_menu_model_delegate(),
                    tab_strip_model, 2);
  size_t submenu_index =
      menu.GetIndexOfCommandId(TabStripModel::CommandSwapWithActiveSplit)
          .value();
  ui::SimpleMenuModel* submenu =
      static_cast<ui::SimpleMenuModel*>(menu.GetSubmenuModelAt(submenu_index));

  // Now, activate tab 2 (non-split tab) to simulate focus change.
  tab_strip_model->ActivateTabAt(2);
  EXPECT_FALSE(tab_strip_model->GetActiveTab()->IsSplit());

  // Trigger the swap start tab command from the submenu.
  // It should not crash and should be a no-op because the active tab is no
  // longer split.
  submenu->ActivatedAt(static_cast<size_t>(
      submenu
          ->GetIndexOfCommandId(
              static_cast<int>(SplitTabSwapMenuModel::CommandId::kSwapStartTab))
          .value()));

  // Verify that the split state did not change (tabs 0 and 1 are still split,
  // tab 2 is not).
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(0).has_value());
  EXPECT_TRUE(tab_strip_model->GetSplitForTab(1).has_value());
  EXPECT_FALSE(tab_strip_model->GetSplitForTab(2).has_value());
}

class TabMenuModelGlicMultiTabTest : public TabMenuModelBrowserTest {
 public:
  TabMenuModelGlicMultiTabTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{glic::mojom::features::kGlicMultiTab},
        /*disabled_features=*/{});
  }

 protected:
  TabStripModel* tab_strip() { return browser()->tab_strip_model(); }

  glic::GlicSharingManagerInternal& sharing_manager() {
    return glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile())
        ->active_instance_sharing_manager();
  }

  glic::GlicSharingManagerInternal& sharing_manager(
      glic::GlicInstance* instance) {
    CHECK(instance);
    return *static_cast<glic::GlicSharingManagerInternal*>(
        instance->GetSharingManager());
  }

  tabs::TabHandle TabHandleAtIndex(int index) {
    return tab_strip()->GetTabAtIndex(index)->GetHandle();
  }

  glic::GlicTestEnvironment glic_test_environment_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, NotShared) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  TabStripModel* tab_strip_model = browser()->tab_strip_model();
  TabMenuModel model(&delegate_,
                     browser()->GetFeatures().tab_menu_model_delegate(),
                     tab_strip_model, 1);
  EXPECT_TRUE(
      model.GetIndexOfCommandId(TabStripModel::CommandGlicShare).has_value());
}

IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, SomeShared) {
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);

  auto* service = glic::GlicKeyedService::Get(profile());
  service->ShowUI(browser(), glic::mojom::InvocationSource::kOsButtonMenu);
  auto* instance =
      service->GetInstanceForTab(browser()->GetActiveTabInterface());
  ASSERT_TRUE(instance);
  sharing_manager(instance).PinTabs(
      {tab_strip()->GetTabAtIndex(0)->GetHandle()},
      glic::GlicPinTrigger::kContextMenu);

  TabMenuModel model(&delegate_,
                     browser()->GetFeatures().tab_menu_model_delegate(),
                     tab_strip(), 1);
  EXPECT_TRUE(
      model.GetIndexOfCommandId(TabStripModel::CommandGlicShare).has_value());
  EXPECT_FALSE(
      model.GetIndexOfCommandId(TabStripModel::CommandGlicUnshare).has_value());
}

IN_PROC_BROWSER_TEST_F(TabMenuModelGlicMultiTabTest, TooManyShared) {
  auto* service = glic::GlicKeyedService::Get(profile());
  service->ShowUI(browser(), glic::mojom::InvocationSource::kOsButtonMenu);
  auto* instance =
      service->GetInstanceForTab(browser()->GetActiveTabInterface());
  ASSERT_TRUE(instance);
  const int limit = sharing_manager(instance).GetMaxPinnedTabs();
  for (int i = 0; i < limit; ++i) {
    chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
    sharing_manager(instance).PinTabs(
        {tab_strip()->GetTabAtIndex(i)->GetHandle()},
        glic::GlicPinTrigger::kContextMenu);
  }
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  tab_strip()->SelectTabAt(limit);
  sharing_manager(instance).PinTabs(
      {tab_strip()->GetTabAtIndex(limit)->GetHandle()},
      glic::GlicPinTrigger::kContextMenu);
  EXPECT_FALSE(sharing_manager(instance).IsTabPinned(TabHandleAtIndex(limit)));
}

class TabMenuModelSendTabToSelfBrowserTest : public TabMenuModelBrowserTest {
 public:
  TabMenuModelSendTabToSelfBrowserTest() {
    feature_list_.InitAndEnableFeature(
        send_tab_to_self::kSendTabToSelfEnhancedDesktopUI);
  }

  void SetUpBrowserContextKeyedServices(
      content::BrowserContext* context) override {
    TabMenuModelBrowserTest::SetUpBrowserContextKeyedServices(context);
    SendTabToSelfSyncServiceFactory::GetInstance()->SetTestingFactory(
        context, base::BindRepeating([](content::BrowserContext* context)
                                         -> std::unique_ptr<KeyedService> {
          return std::make_unique<
              send_tab_to_self::StubSendTabToSelfSyncService>();
        }));
  }

  send_tab_to_self::FakeSendTabToSelfModel* model() {
    return static_cast<send_tab_to_self::StubSendTabToSelfSyncService*>(
               SendTabToSelfSyncServiceFactory::GetForProfile(profile()))
        ->GetFakeSendTabToSelfModel();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(TabMenuModelSendTabToSelfBrowserTest,
                       SendMultipleSelectedTabs) {
  std::vector<send_tab_to_self::TargetDeviceInfo> devices;
  devices.emplace_back("Device 0", "guid0",
                       syncer::DeviceInfo::FormFactor::kDesktop,
                       syncer::DeviceInfo::OsType::kLinux, base::Time::Now());
  model()->SetTargetDeviceInfoSortedList(devices);

  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url1 = embedded_test_server()->GetURL("/title1.html");
  const GURL url2 = embedded_test_server()->GetURL("/title2.html");
  const GURL url3 = embedded_test_server()->GetURL("/title3.html");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
  ASSERT_TRUE(AddTabAtIndex(1, url2, ui::PAGE_TRANSITION_TYPED));
  ASSERT_TRUE(AddTabAtIndex(2, url3, ui::PAGE_TRANSITION_TYPED));

  TabStripModel* tab_strip = browser()->tab_strip_model();
  ASSERT_EQ(tab_strip->count(), 3);

  // Multi-select tabs 1 and 2
  tab_strip->ActivateTabAt(1);
  tab_strip->AddSelectionFromAnchorTo(2);

  TabMenuModel menu(&delegate_,
                    browser()->GetFeatures().tab_menu_model_delegate(),
                    tab_strip, 2);

  size_t submenu_index =
      menu.GetIndexOfCommandId(TabStripModel::CommandSendTabToSelf).value();
  ui::SimpleMenuModel* submenu =
      static_cast<ui::SimpleMenuModel*>(menu.GetSubmenuModelAt(submenu_index));

  // Trigger send to device 0.
  submenu->ActivatedAt(0);

  // Wait for async SendTabToDevice requests (scroll position generation) to
  // complete.
  ASSERT_TRUE(base::test::RunUntil(
      [&]() { return model()->GetAllGuids().size() == 2u; }));

  std::vector<GURL> sent_urls;
  for (const std::string& guid : model()->GetAllGuids()) {
    sent_urls.push_back(model()->GetEntryByGUID(guid)->GetURL());
  }
  EXPECT_THAT(sent_urls, testing::UnorderedElementsAre(url2, url3));
}
