// Copyright 2022 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/side_panel/side_panel.h"

#include <memory>

#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile.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_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/read_anything/read_anything_prefs.h"
#include "chrome/browser/ui/side_panel/side_panel_action_callback.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_key.h"
#include "chrome/browser/ui/side_panel/side_panel_enums.h"
#include "chrome/browser/ui/side_panel/side_panel_registry.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/tabs/tab_strip_prefs.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/toolbar/bookmark_sub_menu_model.h"
#include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_resize_area.h"
#include "chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/read_anything/read_anything.mojom.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/interaction/interaction_test_util_browser.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chrome/test/interaction/tracked_element_webcontents.h"
#include "chrome/test/interaction/webcontents_interaction_test_util.h"
#include "chrome/test/user_education/interactive_feature_promo_test.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/prefs/pref_service.h"
#include "components/reading_list/core/reading_list_entry.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/actions/actions.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/polling_state_observer.h"
#include "ui/base/interaction/state_observer.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/interaction/interaction_test_util_views.h"
#include "ui/views/interaction/interactive_views_test.h"
#include "ui/views/layout/animating_layout_manager_test_util.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "url/gurl.h"

class SidePanelInteractiveTest : public InteractiveBrowserTest {
 public:
  SidePanelInteractiveTest() = default;
  ~SidePanelInteractiveTest() override = default;

  void SetUp() override {
    set_open_about_blank_on_browser_launch(true);
    InteractiveBrowserTest::SetUp();
  }

  // Helper to register and show a lightweight side panel
  // that includes a ready indicator for synchronization.
  auto RegisterAndShowBasicSidePanel(
      ui::ElementIdentifier ready_indicator_id,
      SidePanelType panel_type = SidePanelType::kContent) {
    return Steps(
        ActivateSurface(kBrowserViewElementId),
        Do([&, ready_indicator_id, panel_type]() {
          auto* registry =
              SidePanelRegistry::From(browser()->GetActiveTabInterface());
          registry->Deregister(
              SidePanelEntry::Key(SidePanelEntry::Id::kCustomizeChrome));
          registry->Register(std::make_unique<SidePanelEntry>(
              panel_type,
              SidePanelEntry::Key(SidePanelEntry::Id::kCustomizeChrome),
              base::BindRepeating(
                  [](ui::ElementIdentifier indicator_id, SidePanelEntryScope&) {
                    auto content_view = std::make_unique<views::View>();
                    content_view->SetLayoutManager(
                        std::make_unique<views::FillLayout>());
                    auto label =
                        std::make_unique<views::Label>(u"Side Panel Ready");
                    label->SetProperty(views::kElementIdentifierKey,
                                       indicator_id);
                    content_view->AddChildView(std::move(label));
                    return content_view;
                  },
                  ready_indicator_id),
              /*default_content_width_callback=*/base::NullCallback()));
          browser()->GetFeatures().side_panel_ui()->Show(
              SidePanelEntry::Id::kCustomizeChrome);
        }),
        WaitForShow(kSidePanelElementId),
        // Wait for the indicator to show.
        WaitForShow(ready_indicator_id));
  }

  auto CheckResizeHandleFocusRingVisible(bool visible) {
    return CheckView(kSidePanelResizeAreaElementId,
                     [visible](views::SidePanelResizeArea* resize_area) {
                       auto* handle = resize_area->resize_handle_for_testing();
                       auto* ring = views::FocusRing::Get(handle);
                       return (ring && ring->ShouldPaintForTesting()) ==
                              visible;
                     });
  }

  auto SimulateMouseHoverOnResizeArea(bool hover) {
    return WithView(kSidePanelResizeAreaElementId,
                    [hover](views::SidePanelResizeArea* view) {
                      ui::EventType type = hover ? ui::EventType::kMouseEntered
                                                 : ui::EventType::kMouseExited;
                      ui::MouseEvent event(type, gfx::Point(), gfx::Point(),
                                           base::TimeTicks(), 0, 0);
                      if (hover) {
                        view->OnMouseEntered(event);
                      } else {
                        view->OnMouseExited(event);
                      }
                    });
  }
};

// This test is specifically to guard against this regression
// (crbug.com/40900604).
IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest, SidePanelNotShownOnPwa) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);
  GURL second_tab_url("https://test.com");
  auto* const side_panel_ui = browser()->GetFeatures().side_panel_ui();

  RunTestSequence(
      // Add a second tab to the tab strip
      AddInstrumentedTab(kSecondTabElementId, second_tab_url),
      CheckResult(
          ([&]() { return browser()->GetTabStripModel()->active_index(); }), 1),
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      CheckResult(([&]() {
                    return browser()
                        ->GetTabStripModel()
                        ->GetActiveWebContents()
                        ->GetLastCommittedURL();
                  }),
                  second_tab_url),
      Do(([&]() {
        auto* registry =
            SidePanelRegistry::From(browser()->GetActiveTabInterface());
        registry->Register(std::make_unique<SidePanelEntry>(
            SidePanelEntry::Key(SidePanelEntry::Id::kCustomizeChrome),
            base::BindRepeating([](SidePanelEntryScope&) {
              return std::make_unique<views::View>();
            }),
            /*default_content_width_callback=*/base::NullCallback()));
        side_panel_ui->Show(SidePanelEntry::Id::kCustomizeChrome);
      })),
      WaitForShow(kSidePanelElementId),
      CheckResult(([&]() {
                    return side_panel_ui->IsSidePanelEntryShowing(
                        SidePanelEntryKey(SidePanelEntryId::kCustomizeChrome));
                  }),
                  true));

  // Install an app using second_tab_url.
  auto app_id = web_app::test::InstallDummyWebApp(browser()->GetProfile(),
                                                  "App Name", second_tab_url);

  // Move second_tab contents to app, simulating open pwa from omnibox intent
  // picker.
  BrowserWindowInterface* app_browser =
      web_app::ReparentWebContentsIntoAppBrowser(
          browser()->GetTabStripModel()->GetActiveWebContents(), app_id);
  EXPECT_TRUE(app_browser->GetType() == BrowserWindowInterface::TYPE_APP);

  // App does not show side panel.
  EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(app_browser)
                   ->side_panel()
                   ->GetVisible());
}

IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest, VisibilityToggleOnHover) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSidePanelReadyIndicatorId);

  auto disable_rich_animation =
      gfx::AnimationTestApi::SetRichAnimationRenderMode(
          gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED);
  RunTestSequence(
      // This test runs through the the base case of a user mousing
      // over the resize area, handle appearing, and then mousing away.
      RegisterAndShowBasicSidePanel(kSidePanelReadyIndicatorId),
      WaitForShow(kSidePanelResizeAreaElementId),

      // Reset state.
      SimulateMouseHoverOnResizeArea(false),
      WaitForHide(kSidePanelResizeHandleElementId),
      CheckResizeHandleFocusRingVisible(false),

      SimulateMouseHoverOnResizeArea(true),
      WaitForShow(kSidePanelResizeHandleElementId),
      CheckResizeHandleFocusRingVisible(false),

      SimulateMouseHoverOnResizeArea(false),
      WaitForHide(kSidePanelResizeHandleElementId),
      CheckResizeHandleFocusRingVisible(false));
}

IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest,
                       VisibilityToggleOnHoverWithFocus) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSidePanelReadyIndicatorId);

  auto disable_rich_animation =
      gfx::AnimationTestApi::SetRichAnimationRenderMode(
          gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED);
  RunTestSequence(
      // This test covers the visual sequencing for the
      // resize handle and its focus ring when interacted with
      // via. a combination of mouse hover and focus/tab events.
      RegisterAndShowBasicSidePanel(kSidePanelReadyIndicatorId),
      WaitForShow(kSidePanelResizeAreaElementId),

      // Reset state.
      SimulateMouseHoverOnResizeArea(false),
      WaitForHide(kSidePanelResizeHandleElementId),
      CheckResizeHandleFocusRingVisible(false),

      WithView(kSidePanelResizeAreaElementId,
               [](views::View* view) { view->RequestFocus(); }),
      WaitForShow(kSidePanelResizeHandleElementId),
      CheckResizeHandleFocusRingVisible(true),

      SimulateMouseHoverOnResizeArea(true),
      WaitForShow(kSidePanelResizeHandleElementId),

      SimulateMouseHoverOnResizeArea(false),
      CheckResizeHandleFocusRingVisible(true),
      WaitForShow(kSidePanelResizeHandleElementId),

      Do(([&]() {
        BrowserView::GetBrowserViewForBrowser(browser())
            ->GetFocusManager()
            ->ClearFocus();
      })),
      CheckResizeHandleFocusRingVisible(false),
      WaitForHide(kSidePanelResizeHandleElementId));
}

// Test case for menus that only appear with the kSidePanelPinning feature
// enabled.
class PinnedSidePanelInteractiveTest : public InteractiveFeaturePromoTest {
 public:
  PinnedSidePanelInteractiveTest()
      : InteractiveFeaturePromoTest(UseDefaultTrackerAllowingPromos(
            {feature_engagement::kIPHSidePanelGenericPinnableFeature})) {
  }
  ~PinnedSidePanelInteractiveTest() override = default;

  void SetUp() override {
    set_open_about_blank_on_browser_launch(true);
    InteractiveFeaturePromoTest::SetUp();
  }

  void SetUpOnMainThread() override {
    InteractiveFeaturePromoTest::SetUpOnMainThread();
    PinnedToolbarActionsModel* const actions_model =
        PinnedToolbarActionsModel::Get(browser()->GetProfile());
    actions_model->UpdatePinnedState(kActionShowChromeLabs, false);
    actions_model->UpdatePinnedState(kActionTabSearch, false);
    auto* const layout_manager = views::test::GetAnimatingLayoutManager(
        GetPinnedToolbarActionsContainer());
    if (layout_manager && layout_manager->is_animating()) {
      views::test::WaitForAnimatingLayoutManager(layout_manager);
    }
  }

  auto OpenBookmarksSidePanel() {
    // TODO(crbug.com/40286543): When initially writing this step, opening the
    // bookmarks submenu is flaky and sometimes causes a crash but the crash
    // doesn't seem reproducible anymore. Unsure if the crash was fixed so will
    // need to track down cause of crash if this step becomes flaky again.
    return Steps(
        PressButton(kToolbarAppMenuButtonElementId),
        SelectMenuItem(AppMenuModel::kBookmarksMenuItem),
        SelectMenuItem(BookmarkSubMenuModel::kShowBookmarkSidePanelItem),
        WaitForShow(kSidePanelElementId));
  }

  auto CheckActionPinnedToToolbar(const actions::ActionId& id,
                                  bool should_pin) {
    return CheckResult(
        [&]() {
          PinnedToolbarActionsContainer* const
              pinned_toolbar_actions_container =
                  GetPinnedToolbarActionsContainer();
          return pinned_toolbar_actions_container->GetPinnedButtonFor(id) !=
                 nullptr;
        },
        should_pin);
  }

  auto CheckPinButtonToggleState(bool should_toggle) {
    return CheckViewProperty(kSidePanelPinButtonElementId,
                             &views::ToggleImageButton::GetToggled,
                             should_toggle);
  }

  PinnedToolbarActionsContainer* GetPinnedToolbarActionsContainer() {
    CHECK(!features::IsWebUIPinnedToolbarActionsEnabled())
        << "Test needs modification to support WebUIPinnedToolbarActions";
    return static_cast<PinnedToolbarActionsContainer*>(
        BrowserView::GetBrowserViewForBrowser(browser())
            ->toolbar_button_provider()
            ->GetPinnedToolbarActions());
  }

  auto OpenReadingModeSidePanel() {
    return Steps(
        Do(([&]() {
          // Reading mode opens in "Immersive" / full-screen view by default.
          // In order to test that the side panel view of reading mode in side
          // panel tests, set its presentation state to side panel.
          browser()->GetProfile()->GetPrefs()->SetInteger(
              prefs::kAccessibilityReadAnythingLastOpenedPresentationState,
              static_cast<int>(
                  read_anything::mojom::ReadAnythingPresentationState::
                      kInSidePanel));
          chrome::ExecuteCommandWithContext(
              browser(), IDC_SHOW_READING_MODE_SIDE_PANEL,
              actions::ActionInvocationContext::Builder()
                  .SetProperty(
                      kSidePanelOpenTriggerKey,
                      static_cast<std::underlying_type_t<SidePanelOpenTrigger>>(
                          SidePanelOpenTrigger::kToolbarButton))
                  .Build());
        })),
        WaitForShow(kSidePanelElementId));
  }

  auto OpenCustomizeChromeSidePanel() {
    return Steps(
        Do(([&]() {
          chrome::ExecuteCommandWithContext(
              browser(), IDC_SHOW_CUSTOMIZE_CHROME_SIDE_PANEL,
              actions::ActionInvocationContext::Builder()
                  .SetProperty(
                      kSidePanelOpenTriggerKey,
                      static_cast<std::underlying_type_t<SidePanelOpenTrigger>>(
                          SidePanelOpenTrigger::kToolbarButton))
                  .Build());
        })),
        WaitForShow(kSidePanelElementId));
  }

  auto CheckPinnedToolbarActionsContainerChildInkDropState(int child_index,
                                                           bool is_active) {
    return Steps(CheckResult(([this, child_index]() {
                               return views::InkDrop::Get(
                                          GetPinnedToolbarActionsContainer()
                                              ->children()[child_index])
                                          ->GetInkDrop()
                                          ->GetTargetInkDropState() ==
                                      views::InkDropState::ACTIVATED;
                             }),
                             is_active));
  }

  auto ShowSidePanelForKey(SidePanelEntryKey key) {
    return Do(([&]() { browser()->GetFeatures().side_panel_ui()->Show(key); }));
  }
};

// Verify that we can open the ReadingMode side panel from the 3dot -> More
// tools context menu.
IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       OpenReadingModeSidePanel) {
  // Replace the contents of the ReadingMode side panel with an empty view so it
  // loads faster.
  auto* const registry =
      SidePanelRegistry::From(browser()->GetActiveTabInterface());
  registry->Deregister(SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
  registry->Register(std::make_unique<SidePanelEntry>(
      SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything),
      base::BindRepeating(
          [](SidePanelEntryScope&) { return std::make_unique<views::View>(); }),
      /*default_content_width_callback=*/base::NullCallback()));

  SidePanelUI* const side_panel_ui = browser()->GetFeatures().side_panel_ui();
  side_panel_ui->SetNoDelaysForTesting(true);

  RunTestSequence(OpenReadingModeSidePanel(),
                  CheckResult(
                      [side_panel_ui]() {
                        return side_panel_ui->IsSidePanelEntryShowing(
                            SidePanelEntryKey(SidePanelEntryId::kReadAnything));
                      },
                      true));
}

// Verify that we can open the CustomizeChrome side panel from the 3dot -> More
// tools context menu.
IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       OpenCustomizeChromeSidePanel) {
  // Replace the contents of the CustomizeChrome side panel with an empty view
  // so it loads faster.
  auto* registry = SidePanelRegistry::From(browser()->GetActiveTabInterface());
  registry->Deregister(
      SidePanelEntry::Key(SidePanelEntry::Id::kCustomizeChrome));
  registry->Register(std::make_unique<SidePanelEntry>(
      SidePanelEntry::Key(SidePanelEntry::Id::kCustomizeChrome),
      base::BindRepeating(
          [](SidePanelEntryScope&) { return std::make_unique<views::View>(); }),
      /*default_content_width_callback=*/base::NullCallback()));

  SidePanelUI* const side_panel_ui = browser()->GetFeatures().side_panel_ui();
  side_panel_ui->SetNoDelaysForTesting(true);

  RunTestSequence(
      EnsureNotPresent(kSidePanelElementId), OpenCustomizeChromeSidePanel(),
      CheckResult(([&]() {
                    return side_panel_ui->IsSidePanelEntryShowing(
                        SidePanelEntryKey(SidePanelEntryId::kCustomizeChrome));
                  }),
                  true));
}

// Verify that we can open the history cluster side panel from the app menu.
IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       OpenHistoryClusterSidePanel) {
  auto* const registry = SidePanelRegistry::From(browser());
  registry->Deregister(
      SidePanelEntry::Key(SidePanelEntry::Id::kHistoryClusters));
  registry->Register(std::make_unique<SidePanelEntry>(
      SidePanelEntry::Key(SidePanelEntry::Id::kHistoryClusters),
      base::BindRepeating(
          [](SidePanelEntryScope&) { return std::make_unique<views::View>(); }),
      /*default_content_width_callback=*/base::NullCallback()));

  chrome::ExecuteCommandWithContext(
      browser(), IDC_SHOW_HISTORY_CLUSTERS_SIDE_PANEL,
      actions::ActionInvocationContext::Builder()
          .SetProperty(
              kSidePanelOpenTriggerKey,
              static_cast<std::underlying_type_t<SidePanelOpenTrigger>>(
                  SidePanelOpenTrigger::kAppMenu))
          .Build());

  EXPECT_TRUE(browser()->GetFeatures().side_panel_ui()->IsSidePanelEntryShowing(
      SidePanelEntryKey(SidePanelEntryId::kHistoryClusters)));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       PanelPinnedStateUpdatesOnPinButtonPress) {
  RunTestSequence(
      EnsureNotPresent(kSidePanelElementId), OpenBookmarksSidePanel(),
      CheckPinButtonToggleState(false),
      // Pin the bookmarks side panel
      PressButton(kSidePanelPinButtonElementId),
      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, true),
      WaitForShow(kPinnedToolbarActionsContainerDividerElementId),
      CheckPinButtonToggleState(true),
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId),
      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, true),
      OpenBookmarksSidePanel(),
      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, true),
      CheckPinButtonToggleState(true),
      // Unpin the bookmarks side panel
      PressButton(kSidePanelPinButtonElementId),
      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, false),
      WaitForHide(kPinnedToolbarActionsContainerDividerElementId),
      CheckPinButtonToggleState(false));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       SidePanelPinButtonsHideInIncognitoMode) {
  Browser* const incognito = CreateIncognitoBrowser();
  RunTestSequence(
      InContext(BrowserElements::From(incognito)->GetContext(),
                WaitForShow(kBrowserViewElementId)),
      InSameContext(ActivateSurface(kBrowserViewElementId),
                    EnsureNotPresent(kSidePanelElementId),
                    OpenBookmarksSidePanel(),
                    EnsureNotPresent(kSidePanelPinButtonElementId)));
}

// TODO(crbug.com/417601707): Re-enable this test
#if BUILDFLAG(IS_MAC)
#define MAYBE_PinnedToolbarButtonsHighlightWhileSidePanelVisible \
  DISABLED_PinnedToolbarButtonsHighlightWhileSidePanelVisible
#else
#define MAYBE_PinnedToolbarButtonsHighlightWhileSidePanelVisible \
  PinnedToolbarButtonsHighlightWhileSidePanelVisible
#endif
IN_PROC_BROWSER_TEST_F(
    PinnedSidePanelInteractiveTest,
    MAYBE_PinnedToolbarButtonsHighlightWhileSidePanelVisible) {
  // Replace the contents of the ReadingMode side panel with an empty view so it
  // loads faster.
  auto* const registry =
      SidePanelRegistry::From(browser()->GetActiveTabInterface());
  registry->Deregister(SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
  registry->Register(std::make_unique<SidePanelEntry>(
      SidePanelEntryKey(SidePanelEntry::Id::kReadAnything),
      base::BindRepeating(
          [](SidePanelEntryScope&) { return std::make_unique<views::View>(); }),
      /*default_content_width_callback=*/base::NullCallback()));

  SidePanelUI* const side_panel_ui = browser()->GetFeatures().side_panel_ui();
  side_panel_ui->SetNoDelaysForTesting(true);

  PinnedToolbarActionsModel* const actions_model =
      PinnedToolbarActionsModel::Get(browser()->GetProfile());

  actions_model->UpdatePinnedState(kActionSidePanelShowBookmarks, true);

  RunTestSequence(
      // Verify side panel is closed.
      EnsureNotPresent(kSidePanelElementId),
      // Verify the bookmarks pinned toolbar button is not highlighted.
      CheckPinnedToolbarActionsContainerChildInkDropState(0, false),
      // Open the bookmarks side panel.
      OpenBookmarksSidePanel(),
      // Verify bookmarks is pinned to the toolbar.
      Check(([&]() {
        return actions_model->Contains(kActionSidePanelShowBookmarks);
      })),
      WaitForShow(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      CheckView(
          kPinnedToolbarActionsContainerElementId,
          [](views::View* view) { return view->children().size() == 2u; }),
      // Verify the bookmarks pinned toolbar button is highlighted.
      CheckPinnedToolbarActionsContainerChildInkDropState(0, true),
      CheckViewProperty(kPinnedToolbarActionsContainerDividerElementId,
                        &views::View::GetVisible, true),
      // Close the side panel.
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId),
      // Verify the bookmarks pinned toolbar button is not highlighted.
      CheckPinnedToolbarActionsContainerChildInkDropState(0, false),
      // Open non-bookmarks side panel.
      OpenReadingModeSidePanel(),
      // Verify the bookmarks pinned toolbar button is not highlighted.
      CheckPinnedToolbarActionsContainerChildInkDropState(0, false));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       ToggleSidePanelVisibility) {
  RunTestSequence(
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      EnsureNotPresent(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      // Open bookmarks sidepanel
      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId),
      WaitForShow(kPinnedToolbarActionsContainerElementId),
      // Pin the button
      CheckPinButtonToggleState(false),
      PressButton(kSidePanelPinButtonElementId),
      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, true),
      EnsurePresent(kPinnedToolbarActionsContainerElementId),
      WaitForShow(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      // Toggle side panel
      PressButton(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      WaitForHide(kSidePanelElementId));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       SwitchBetweenDifferentEntries) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kBookmarksWebContentsId);
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kReadLaterWebContentsId);
  auto* const side_panel_ui = browser()->GetFeatures().side_panel_ui();

  RunTestSequence(
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      // Click the toolbar button to open the side panel
      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId),
      InstrumentNonTabWebView(kBookmarksWebContentsId,
                              kBookmarkSidePanelWebViewElementId),

      CheckResult(([&]() {
                    return side_panel_ui->IsSidePanelEntryShowing(
                        SidePanelEntryKey(SidePanelEntryId::kBookmarks));
                  }),
                  true),
      // Switch to the reading list entry.
      ShowSidePanelForKey(SidePanelEntryKey(SidePanelEntry::Id::kReadingList)),
      InstrumentNonTabWebView(kReadLaterWebContentsId,
                              kReadLaterSidePanelWebViewElementId),

      CheckResult(([&]() {
                    return side_panel_ui->IsSidePanelEntryShowing(
                        SidePanelEntryKey(SidePanelEntryId::kReadingList));
                  }),
                  true),
      // Click on the close button to dismiss the side panel
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       StaysOpenOnTabSwitchWithActiveGlobalEntry) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);

  RunTestSequence(
      // Add a second tab to the tab strip
      AddInstrumentedTab(kSecondTabElementId, GURL(url::kAboutBlankURL)),
      CheckResult(
          ([&]() { return browser()->GetTabStripModel()->active_index(); }),
          testing::Eq(1)),
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      // Click the toolbar button to open the side panel
      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId),
      // Switch to the first tab again with the side panel open
      SelectTab(kTabStripElementId, 0),
      // Ensure the side panel is still visible
      CheckViewProperty(kSidePanelElementId, &views::View::GetVisible, true),
      // Click on the close button to dismiss the side panel
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       ToolbarButtonDisappearsOnEntryDeregister) {
  constexpr char kBookmarksButton[] = "bookmarks_button";
  RunTestSequence(
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      EnsureNotPresent(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      // Open bookmarks sidepanel
      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId),
      WaitForShow(kPinnedToolbarActionsContainerElementId),
      WaitForShow(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      NameChildViewByType<PinnedActionToolbarButton>(
          kPinnedToolbarActionsContainerElementId, kBookmarksButton),
      WaitForShow(kBookmarksButton),
      // Deregister the entry and verify the side panel and ephemeral toolbar
      // button are hidden.
      Do([this]() {
        SidePanelRegistry::From(browser())->Deregister(
            SidePanelEntry::Key(SidePanelEntry::Id::kBookmarks));
      }),
      WaitForHide(kSidePanelElementId), WaitForHide(kBookmarksButton));
}

// Regression test for crbug.com/452911460 where the side panel header close
// button triggers the side panel to close but the controller is destroyed and
// triggers a seg fault when trying to retrieve the
// BrowserUserEducationInterface.
IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest, CloseSidePanel) {
  auto disable_rich_animation =
      gfx::AnimationTestApi::SetRichAnimationRenderMode(
          gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED);
  RunTestSequence(
      // Ensure the side panel isn't open
      EnsureNotPresent(kSidePanelElementId),
      EnsureNotPresent(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      // Open bookmarks sidepanel
      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId),
      WaitForShow(kPinnedToolbarActionsContainerElementId),
      WaitForShow(kPinnedToolbarActionShowSidePanelBookmarksElementId),
      WaitForPromo(feature_engagement::kIPHSidePanelGenericPinnableFeature),
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest, CloseRestoresFocus) {
  auto disable_rich_animation =
      gfx::AnimationTestApi::SetRichAnimationRenderMode(
          gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED);

  RunTestSequence(
      EnsureNotPresent(kSidePanelElementId), OpenBookmarksSidePanel(),
      WaitForShow(kSidePanelElementId),
      WaitForShow(kSidePanelCloseButtonElementId),
      FocusElement(kSidePanelCloseButtonElementId),
      // Ensure the close button has focus.
      CheckView(kSidePanelCloseButtonElementId,
                [](views::View* view) { return view->HasFocus(); }),
      PressButton(kSidePanelCloseButtonElementId),
      WaitForHide(kSidePanelElementId),
      // Verify the focus is no longer on the side panel after close.
      CheckResult(
          [this]() {
            views::FocusManager* focus_manager =
                BrowserView::GetBrowserViewForBrowser(browser())
                    ->GetFocusManager();
            SidePanel* side_panel =
                BrowserView::GetBrowserViewForBrowser(browser())->side_panel();
            views::View* focused_view = focus_manager->GetStoredFocusView();
            return focused_view && !side_panel->Contains(focused_view);
          },
          true));
}

class SidePanelAnimationPerfUiTest : public SidePanelInteractiveTest {
 public:
  SidePanelAnimationPerfUiTest() = default;
  ~SidePanelAnimationPerfUiTest() override = default;

  auto StartCollectingSamples() {
    return Do([this]() {
      histogram_tester_ = std::make_unique<base::HistogramTester>();
    });
  }

  auto OpenBookmarksSidePanel() {
    return Steps(
        PressButton(kToolbarAppMenuButtonElementId),
        SelectMenuItem(AppMenuModel::kBookmarksMenuItem),
        SelectMenuItem(BookmarkSubMenuModel::kShowBookmarkSidePanelItem),
        WaitForEvent(kSidePanelElementId,
                     SidePanel::kOpenAnimationCompletedEvent)
            .SetMustBeVisibleAtStart(false));
  }

  auto OpenToolbarHeightSidePanel() {
    DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestSidePanelId);
    return InParallel(
        RunSubsequence(RegisterAndShowBasicSidePanel(kTestSidePanelId,
                                                     SidePanelType::kToolbar)),
        RunSubsequence(WaitForEvent(kSidePanelElementId,
                                    SidePanel::kOpenAnimationCompletedEvent)
                           .SetMustBeVisibleAtStart(false)));
  }

  auto CloseSidePanel() {
    return Steps(PressButton(kSidePanelCloseButtonElementId),
                 WaitForEvent(kSidePanelElementId,
                              SidePanel::kCloseAnimationCompletedEvent),
                 WaitForHide(kSidePanelElementId));
  }

 protected:
  std::unique_ptr<base::HistogramTester> histogram_tester_;
};

IN_PROC_BROWSER_TEST_F(SidePanelAnimationPerfUiTest, OpenSidePanel) {
  bool has_fps = false;
  RunTestSequence(StartCollectingSamples(), OpenBookmarksSidePanel(),
                  Do([this, &has_fps]() {
                    histogram_tester_->ExpectTotalCount(
                        "SidePanel.Open.TimeOfLongestAnimationStep", 1);
                    has_fps = histogram_tester_->GetTotalCountForPrefix(
                                  "SidePanel.Open.AnimationFPS") > 0;
                  }),
                  CheckResult(
                      [this]() {
                        return histogram_tester_->GetTotalSum(
                            "SidePanel.Open.TimeOfLongestAnimationStep");
                      },
                      testing::Gt(0), "Check longest step is nonzero."),
                  If([&] { return has_fps; },
                     Then(CheckResult(
                         [this]() {
                           return histogram_tester_->GetTotalSum(
                               "SidePanel.Open.AnimationFPS");
                         },
                         testing::Gt(0), "Check fps is nonzero.")),
                     Else(Log("Compositor failed to render during test."))));
}

IN_PROC_BROWSER_TEST_F(SidePanelAnimationPerfUiTest, CloseSidePanel) {
  bool has_fps = false;
  RunTestSequence(OpenBookmarksSidePanel(), StartCollectingSamples(),
                  CloseSidePanel(), Do([this, &has_fps]() {
                    histogram_tester_->ExpectTotalCount(
                        "SidePanel.Close.TimeOfLongestAnimationStep", 1);
                    has_fps = histogram_tester_->GetTotalCountForPrefix(
                                  "SidePanel.Close.AnimationFPS") > 0;
                  }),
                  CheckResult(
                      [this]() {
                        return histogram_tester_->GetTotalSum(
                            "SidePanel.Close.TimeOfLongestAnimationStep");
                      },
                      testing::Gt(0), "Check longest step is nonzero."),
                  If([&] { return has_fps; },
                     Then(CheckResult(
                         [this]() {
                           return histogram_tester_->GetTotalSum(
                               "SidePanel.Close.AnimationFPS");
                         },
                         testing::Gt(0), "Check fps is nonzero.")),
                     Else(Log("Compositor failed to render during test."))));
}

IN_PROC_BROWSER_TEST_F(SidePanelAnimationPerfUiTest,
                       OpenToolbarHeightSidePanel) {
  bool has_fps = false;
  RunTestSequence(
      StartCollectingSamples(), OpenToolbarHeightSidePanel(),
      Do([this, &has_fps]() {
        histogram_tester_->ExpectTotalCount(
            "SidePanelToolbarHeight.Open.TimeOfLongestAnimationStep", 1);
        has_fps = histogram_tester_->GetTotalCountForPrefix(
                      "SidePanelToolbarHeight.Open.AnimationFPS") > 0;
      }),
      CheckResult(
          [this]() {
            return histogram_tester_->GetTotalSum(
                "SidePanelToolbarHeight.Open.TimeOfLongestAnimationStep");
          },
          testing::Gt(0), "Check longest step is nonzero."),
      If([&] { return has_fps; },
         Then(CheckResult(
             [this]() {
               return histogram_tester_->GetTotalSum(
                   "SidePanelToolbarHeight.Open.AnimationFPS");
             },
             testing::Gt(0), "Check fps is nonzero.")),
         Else(Log("Compositor failed to render during test."))));
}

IN_PROC_BROWSER_TEST_F(SidePanelAnimationPerfUiTest,
                       CloseToolbarHeightSidePanel) {
  bool has_fps = false;
  RunTestSequence(
      OpenToolbarHeightSidePanel(), StartCollectingSamples(), CloseSidePanel(),
      Do([this, &has_fps]() {
        histogram_tester_->ExpectTotalCount(
            "SidePanelToolbarHeight.Close.TimeOfLongestAnimationStep", 1);
        has_fps = histogram_tester_->GetTotalCountForPrefix(
                      "SidePanelToolbarHeight.Close.AnimationFPS") > 0;
      }),
      CheckResult(
          [this]() {
            return histogram_tester_->GetTotalSum(
                "SidePanelToolbarHeight.Close.TimeOfLongestAnimationStep");
          },
          testing::Gt(0), "Check longest step is nonzero."),
      If([&] { return has_fps; },
         Then(CheckResult(
             [this]() {
               return histogram_tester_->GetTotalSum(
                   "SidePanelToolbarHeight.Close.AnimationFPS");
             },
             testing::Gt(0), "Check fps is nonzero.")),
         Else(Log("Compositor failed to render during test."))));
}

IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
                       PinActiveItemHighlightsButton) {
  auto* registry = SidePanelRegistry::From(browser());
  auto* entry = registry->GetEntryForKey(SidePanelEntry::Key(SidePanelEntryId::kReadingList));
  ASSERT_TRUE(entry);
  entry->set_should_show_ephemerally_in_toolbar(false);

  RunTestSequence(
      // Ensure the side panel isn't open and action is not pinned.
      EnsureNotPresent(kSidePanelElementId),
      CheckActionPinnedToToolbar(kActionSidePanelShowReadingList, false),

      // Open reading list side panel.
      ShowSidePanelForKey(SidePanelEntryKey(SidePanelEntry::Id::kReadingList)),
      WaitForShow(kSidePanelElementId),

      // Since ephemeral is disabled, no button for reading list should be added
      // yet.
      CheckActionPinnedToToolbar(kActionSidePanelShowReadingList, false),

      // Pin reading list action.
      Do(([&]() {
        PinnedToolbarActionsModel::Get(browser()->GetProfile())
            ->UpdatePinnedState(kActionSidePanelShowReadingList, true);
      })),

      // Verify it is pinned and highlighted immediately.
      CheckActionPinnedToToolbar(kActionSidePanelShowReadingList, true),
      CheckPinnedToolbarActionsContainerChildInkDropState(0, true),

      // Unpin reading list action while open.
      Do(([&]() {
        PinnedToolbarActionsModel::Get(browser()->GetProfile())
            ->UpdatePinnedState(kActionSidePanelShowReadingList, false);
      })),

      // Verify it is no longer pinned and is removed from the toolbar.
      CheckActionPinnedToToolbar(kActionSidePanelShowReadingList, false));
}
