// Copyright 2026 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/page_action/chip_selector.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/callback.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/page_action/page_action_controller.h"
#include "chrome/browser/ui/ui_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/actions/action_id.h"

const char kActiveChipsHistogram[] =
    "PageActionController.ActiveSuggestionChips";

namespace page_actions {
namespace {

using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Pair;

class DefaultChipSelectorTest : public testing::Test {
 public:
  void SetUp() override {
    selector = std::make_unique<internal::DefaultChipSelector>(
        base::BindRepeating(&DefaultChipSelectorTest::ShowChipCallback,
                            base::Unretained(this)),
        base::BindRepeating(&DefaultChipSelectorTest::HideChipCallback,
                            base::Unretained(this)),
        base::BindRepeating(
            &DefaultChipSelectorTest::ShowAnchoredMessageCallback,
            base::Unretained(this)),
        base::BindRepeating(
            &DefaultChipSelectorTest::HideAnchoredMessageCallback,
            base::Unretained(this)));
  }

 private:
  void ShowChipCallback(actions::ActionId page_action_id,
                        const SuggestionChipConfig& config) {
    calls.emplace_back("show_chip", page_action_id);
  }

  void HideChipCallback(actions::ActionId page_action_id) {
    calls.emplace_back("hide_chip", page_action_id);
  }

  void ShowAnchoredMessageCallback(actions::ActionId page_action_id,
                                   const AnchoredMessageConfig& config) {
    calls.emplace_back("show_anchored_message", page_action_id);
  }

  void HideAnchoredMessageCallback(actions::ActionId page_action_id) {
    calls.emplace_back("hide_anchored_message", page_action_id);
  }

 public:
  std::unique_ptr<internal::DefaultChipSelector> selector;
  std::vector<std::pair<std::string, actions::ActionId>> calls;
};

TEST_F(DefaultChipSelectorTest, ShowSingleChip) {
  base::HistogramTester histogram_tester;
  selector->RequestChipShow(0, SuggestionChipConfig{});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 1), 1);
}

TEST_F(DefaultChipSelectorTest, ShowChipTwice) {
  base::HistogramTester histogram_tester;
  selector->RequestChipShow(0, SuggestionChipConfig{});
  selector->RequestChipShow(0, SuggestionChipConfig{});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("show_chip", 0)));
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 1), 1);
}

TEST_F(DefaultChipSelectorTest, ShowTwoChips) {
  base::HistogramTester histogram_tester;
  selector->RequestChipShow(0, SuggestionChipConfig{});
  selector->RequestChipShow(1, SuggestionChipConfig{});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("show_chip", 1)));
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 1), 1);
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 2), 1);
}

TEST_F(DefaultChipSelectorTest, HideUnshownChip) {
  selector->RequestChipHide(0);
  EXPECT_THAT(calls, ElementsAre(Pair("hide_chip", 0)));
}

TEST_F(DefaultChipSelectorTest, HideShownChip) {
  selector->RequestChipShow(0, SuggestionChipConfig{});
  selector->RequestChipHide(0);
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0)));
}

TEST_F(DefaultChipSelectorTest, HistogramShowHideShow) {
  base::HistogramTester histogram_tester;
  selector->RequestChipShow(0, SuggestionChipConfig{});
  selector->RequestChipHide(0);
  selector->RequestChipShow(0, SuggestionChipConfig{});
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 1), 2);
  EXPECT_EQ(histogram_tester.GetBucketCount(kActiveChipsHistogram, 2), 0);
}

TEST_F(DefaultChipSelectorTest, AnchoredMessageHidesChip) {
  selector->RequestChipShow(0, SuggestionChipConfig{});
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  EXPECT_THAT(
      calls, ElementsAre(Pair("show_chip", 0), Pair("show_anchored_message", 0),
                         Pair("hide_chip", 0)));
}

TEST_F(DefaultChipSelectorTest, ChipHidesAnchoredMessage) {
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  selector->RequestChipShow(0, SuggestionChipConfig{});
  EXPECT_THAT(
      calls, ElementsAre(Pair("show_anchored_message", 0), Pair("show_chip", 0),
                         Pair("hide_anchored_message", 0)));
}

TEST_F(DefaultChipSelectorTest, OnlyFirstAnchoredMessageShows) {
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  selector->RequestAnchoredMessageShow(1, AnchoredMessageConfig{});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

TEST_F(DefaultChipSelectorTest, AnchoredMessageQueue) {
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  selector->RequestAnchoredMessageShow(1, AnchoredMessageConfig{});
  selector->RequestAnchoredMessageShow(2, AnchoredMessageConfig{});
  selector->RequestAnchoredMessageHide(1);
  selector->RequestAnchoredMessageHide(0);
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("hide_anchored_message", 0),
                                 Pair("show_anchored_message", 2)));
}

TEST_F(DefaultChipSelectorTest, HideUnshownAnchoredMessage) {
  selector->RequestAnchoredMessageHide(0);
  EXPECT_THAT(calls, IsEmpty());
}

TEST_F(DefaultChipSelectorTest,
       UserInteractionAnchoredMessageDowngradesCurrent) {
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});

  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  EXPECT_THAT(
      calls, ElementsAre(Pair("show_anchored_message", 0),
                         Pair("hide_anchored_message", 0), Pair("show_chip", 0),
                         Pair("show_anchored_message", 1)));
}

TEST_F(DefaultChipSelectorTest, UserInteractionAnchoredMessageReplacesChip) {
  selector->RequestChipShow(0, {});
  selector->RequestAnchoredMessageShow(
      0, {.priority = PageActionPriorityCategory::kUserInteraction});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0),
                                 Pair("show_anchored_message", 0)));
}

TEST_F(DefaultChipSelectorTest,
       UserInteractionAnchoredMessageReplacesFrontOfQueue) {
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  selector->RequestAnchoredMessageShow(1, AnchoredMessageConfig{});
  calls.clear();

  selector->RequestAnchoredMessageShow(
      2, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  EXPECT_THAT(
      calls, ElementsAre(Pair("hide_anchored_message", 0), Pair("show_chip", 0),
                         Pair("show_anchored_message", 2)));

  // Verify queue state by hiding the current message and checking if the next
  // one shows.
  calls.clear();
  selector->RequestAnchoredMessageHide(2);
  EXPECT_THAT(calls, ElementsAre(Pair("hide_anchored_message", 2),
                                 Pair("show_anchored_message", 1)));
}

TEST_F(DefaultChipSelectorTest, OnTabActiveChangedNoOp) {
  selector->OnTabActiveChanged(false);
  selector->RequestAnchoredMessageShow(0, AnchoredMessageConfig{});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

class PriorityChipSelectorTest : public testing::Test {
 public:
  void SetUp() override {
    selector = std::make_unique<internal::PriorityChipSelector>(
        base::BindRepeating(&PriorityChipSelectorTest::ShowChipCallback,
                            base::Unretained(this)),
        base::BindRepeating(&PriorityChipSelectorTest::HideChipCallback,
                            base::Unretained(this)),
        base::BindRepeating(
            &PriorityChipSelectorTest::ShowAnchoredMessageCallback,
            base::Unretained(this)),
        base::BindRepeating(
            &PriorityChipSelectorTest::HideAnchoredMessageCallback,
            base::Unretained(this)));
  }

 protected:
  void ShowChipCallback(actions::ActionId page_action_id,
                        const SuggestionChipConfig& config) {
    calls.emplace_back("show_chip", page_action_id);
  }

  void HideChipCallback(actions::ActionId page_action_id) {
    calls.emplace_back("hide_chip", page_action_id);
  }

  void ShowAnchoredMessageCallback(actions::ActionId page_action_id,
                                   const AnchoredMessageConfig& config) {
    calls.emplace_back("show_anchored_message", page_action_id);
  }

  void HideAnchoredMessageCallback(actions::ActionId page_action_id) {
    calls.emplace_back("hide_anchored_message", page_action_id);
  }

  std::unique_ptr<internal::PriorityChipSelector> selector;
  std::vector<std::pair<std::string, actions::ActionId>> calls;
};

TEST_F(PriorityChipSelectorTest, ShowSingleChip) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest, HigherPriorityPreemptsLowerPriority) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  selector->RequestChipShow(
      1, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest, LowerPriorityIgnored) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipShow(
      1, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest, SamePriorityIgnored) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  selector->RequestChipShow(
      1, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest, PrivacySecurityAllowsMultipleChips) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipShow(
      1, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest, AnchoredMessagePreemptsLowerPriorityChip) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0),
                                 Pair("show_anchored_message", 1)));
}

TEST_F(PriorityChipSelectorTest, PrivacySecurityAllowsChipAndMessage) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipShow(
      1, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest,
       ChipDoesntPreemptHigherPriorityAnchoredMessage) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipShow(
      1, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});

  // ContextualCue is lower than PrivacySecurity, so it should be ignored.
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

TEST_F(PriorityChipSelectorTest, HideClearsPriority) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipHide(0);

  // Now lower priority should be allowed.
  selector->RequestChipShow(
      1, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest, PrivacySecurityChipThenMessage) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0),
                                 Pair("show_anchored_message", 1)));
}

TEST_F(PriorityChipSelectorTest, PrivacySecurityMultipleChipsHideOne) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestChipShow(
      1, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  selector->RequestChipHide(0);

  // Priority should still be PrivacySecurity because of chip 1.
  selector->RequestChipShow(
      2, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});

  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("show_chip", 1),
                                 Pair("hide_chip", 0)));
}

TEST_F(PriorityChipSelectorTest, SamePriorityChipThenMessage) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{.priority =
                                   PageActionPriorityCategory::kContextualCue});

  // Message should be ignored as a chip of the same priority is active.
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest,
       PrivacySecuritySecondAnchoredMessageDowngraded) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest, UserInteractionDowngradesAnchoredMessage) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});

  EXPECT_THAT(
      calls, ElementsAre(Pair("show_anchored_message", 0),
                         Pair("hide_anchored_message", 0), Pair("show_chip", 0),
                         Pair("show_anchored_message", 1)));
}

TEST_F(PriorityChipSelectorTest, UserInteractionPreemptsLowerPriority) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{.priority =
                                  PageActionPriorityCategory::kContextualCue});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});

  // Lower priority (ContextualCue) should be hidden, not downgraded.
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0), Pair("hide_chip", 0),
                                 Pair("show_anchored_message", 1)));
}

TEST_F(PriorityChipSelectorTest, UserInteractionAlongsidePrivacySecurityChips) {
  selector->RequestChipShow(
      0, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});

  // PrivacySecurity chip should remain showing alongside UserInteraction
  // message.
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0),
                                 Pair("show_anchored_message", 1)));
}

TEST_F(PriorityChipSelectorTest,
       PrivacySecurityChipShownWhileUserInteractionActive) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  selector->RequestChipShow(
      1, SuggestionChipConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest,
       PrivacySecurityAnchoredMessageDowngradedWhileUserInteractionActive) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});

  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("show_chip", 1)));
}

TEST_F(PriorityChipSelectorTest,
       PrivacySecurityShowsAfterUserInteractionDismissed) {
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  selector->RequestAnchoredMessageShow(
      1, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  selector->RequestAnchoredMessageHide(0);
  selector->RequestAnchoredMessageShow(
      2, {.priority = PageActionPriorityCategory::kPrivacySecurity});
  EXPECT_THAT(
      calls, ElementsAre(Pair("show_anchored_message", 0), Pair("show_chip", 1),
                         Pair("hide_anchored_message", 0),
                         Pair("show_anchored_message", 2)));
}

TEST_F(PriorityChipSelectorTest, AnchoredMessageDowngradedOnTabDeactivation) {
  base::test::ScopedFeatureList feature_list(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(true);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));

  selector->OnTabActiveChanged(false);
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0),
                                 Pair("hide_anchored_message", 0),
                                 Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest,
       AnchoredMessageNotDowngradedOnTabDeactivationWhenFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(true);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));

  selector->OnTabActiveChanged(false);
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

TEST_F(PriorityChipSelectorTest,
       AnchoredMessageUserInteractionNotDowngradedOnTabDeactivation) {
  base::test::ScopedFeatureList feature_list(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(true);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));

  selector->OnTabActiveChanged(false);
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

TEST_F(PriorityChipSelectorTest,
       AnchoredMessageDowngradedWhenRequestedOnInactiveTab) {
  base::test::ScopedFeatureList feature_list(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(false);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  EXPECT_THAT(calls, ElementsAre(Pair("show_chip", 0)));
}

TEST_F(PriorityChipSelectorTest,
       AnchoredMessageUserInteractionNotDowngradedWhenRequestedOnInactiveTab) {
  base::test::ScopedFeatureList feature_list(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(false);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kUserInteraction});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

TEST_F(
    PriorityChipSelectorTest,
    AnchoredMessageNotDowngradedWhenRequestedOnInactiveTabWhenFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kPageActionAnchoredMessageActiveTabOnly);

  selector->OnTabActiveChanged(false);
  selector->RequestAnchoredMessageShow(
      0, AnchoredMessageConfig{
             .priority = PageActionPriorityCategory::kPrivacySecurity});
  EXPECT_THAT(calls, ElementsAre(Pair("show_anchored_message", 0)));
}

}  // namespace
}  // namespace page_actions
