// Copyright 2024 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/exclusive_access/exclusive_access_bubble_views.h"

#include <string>

#include "base/strings/string_util.h"
#include "build/buildflag.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/accelerator_utils.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_bubble_type.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/fullscreen_control/subtle_notification_view.h"
#include "content/public/test/browser_test.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"

namespace {

enum UserGoal {
  kExitFullscreen = 0,
  kExitPointerLock = 1,
  kExitFullscreenAndSeeDownload = 2,
  kSeeDownload = 3,
};

enum Shortcut {
  kPressEsc = 0,
  kPressAndHoldEsc = 1,
  kAccelerator = 2,
};

struct InstructionTextTestCase {
  std::string test_name;
  ExclusiveAccessBubbleType type;
  UserGoal goal;
  Shortcut shortcut;
  bool enable_feature = false;
};

std::u16string GetUserGoalText(UserGoal goal) {
  switch (goal) {
    case kExitFullscreen:
      return u"To exit full screen";
    case kExitPointerLock:
      return u"To show your cursor";
    case kExitFullscreenAndSeeDownload:
      return u"To exit full screen and see download";
    case kSeeDownload:
      return u"Download started. To see it";
    default:
      return u"";
  }
}

std::u16string GetEscShortcutString(bool press_and_hold) {
  std::u16string esc = u"Esc";
#if BUILDFLAG(IS_MAC)
  esc = u"esc";
#endif

  return (press_and_hold ? u"press and hold |" : u"press |") + esc + u"|";
}

}  // namespace

class ExclusiveAccessBubbleViewsTest : public InProcessBrowserTest {
 public:
  ExclusiveAccessBubbleViewsTest() = default;

  ExclusiveAccessBubbleViewsContext* GetContext() {
    return BrowserView::GetBrowserViewForBrowser(browser())
        ->GetExclusiveAccessBubbleViewsContextForTesting();
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    ExclusiveAccessBubbleParams params{
        .type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION};
    bubble_view_ = std::make_unique<ExclusiveAccessBubbleViews>(
        GetContext(), params, base::NullCallback());
  }

  void TearDownOnMainThread() override {
    bubble_view_.reset();
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void UpdateExclusiveAccessBubbleType(ExclusiveAccessBubbleType type,
                                       bool has_download) {
    ExclusiveAccessBubbleParams params{
        .type = type, .has_download = has_download, .force_update = true};
    bubble_view_->Update(params, base::NullCallback());
  }

  std::u16string GetFullscreenAcceleratorString() {
#if BUILDFLAG(IS_CHROMEOS)
    return u"Fullscreen";
#else
    ui::Accelerator accelerator;
    AcceleratorProviderForBrowser(browser())->GetAcceleratorForCommandId(
        IDC_FULLSCREEN, &accelerator);
    return accelerator.GetShortcutText();
#endif
  }

  std::u16string GetShortcutText(Shortcut shortcut) {
    switch (shortcut) {
      case kPressEsc:
        return GetEscShortcutString(false);
      case kPressAndHoldEsc:
        return GetEscShortcutString(true);
      case kAccelerator:
        return u"press |" + GetFullscreenAcceleratorString() + u"|";
      default:
        return u"";
    }
  }

  std::u16string CreateInstructionText(UserGoal goal, Shortcut shortcut) {
    return GetUserGoalText(goal) + u", " + GetShortcutText(shortcut);
  }

  std::u16string GetInstructionViewText() {
    return static_cast<SubtleNotificationView*>(bubble_view_->GetView())
        ->GetInstructionTextForTest();
  }

  SubtleNotificationView* GetSubtleNotificationView() {
    return static_cast<SubtleNotificationView*>(bubble_view_->GetView());
  }

 protected:
  std::unique_ptr<ExclusiveAccessBubbleViews> bubble_view_;
};

class ExclusiveAccessBubbleViewsInstructionTextTest
    : public ExclusiveAccessBubbleViewsTest,
      public testing::WithParamInterface<InstructionTextTestCase> {
 public:
  ExclusiveAccessBubbleViewsInstructionTextTest() {
    if (GetParam().enable_feature) {
      scoped_feature_list_.InitAndEnableFeature(
          features::kPressAndHoldEscToExitBrowserFullscreen);
    } else {
      scoped_feature_list_.InitAndDisableFeature(
          features::kPressAndHoldEscToExitBrowserFullscreen);
    }
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(ExclusiveAccessBubbleViewsInstructionTextTest,
                       UpdateViewContent) {
  const InstructionTextTestCase& test_case = GetParam();
  UpdateExclusiveAccessBubbleType(test_case.type, /*has_download=*/false);
  EXPECT_EQ(GetInstructionViewText(),
            CreateInstructionText(test_case.goal, test_case.shortcut));
}

IN_PROC_BROWSER_TEST_F(ExclusiveAccessBubbleViewsTest,
                       SubtleNotificationViewAccessibleProperties) {
  ui::AXNodeData data;
  GetSubtleNotificationView()->GetViewAccessibility().GetAccessibleNodeData(
      &data);
  EXPECT_EQ(data.role, ax::mojom::Role::kAlert);
  EXPECT_EQ(GetSubtleNotificationView()->GetViewAccessibility().GetCachedRole(),
            ax::mojom::Role::kAlert);

  GetSubtleNotificationView()->UpdateContent(u"Sample |Accessible| Text");

  data = ui::AXNodeData();
  GetSubtleNotificationView()->GetViewAccessibility().GetAccessibleNodeData(
      &data);
  EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
            u"Sample Accessible Text");
}

// Tests where the bubble is updated once with the specified type.
INSTANTIATE_TEST_SUITE_P(
    /* no label */,
    ExclusiveAccessBubbleViewsInstructionTextTest,
    testing::ValuesIn<InstructionTextTestCase>({
        {"tabFullscreen",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressEsc, false},
        {"tabFullscreenAndPointerLock",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_POINTERLOCK_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressEsc, false},
        {"pointerLock",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_POINTERLOCK_EXIT_INSTRUCTION,
         UserGoal::kExitPointerLock, Shortcut::kPressEsc, false},
        {"tabFullscreenAndKeyboardLock",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_KEYBOARD_LOCK_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressAndHoldEsc, false},
        {"browserFullscreen",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kAccelerator, false},
        {"extensionInitiatedBrowserFullscreen",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kAccelerator, false},
        {"tabFullscreen_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressEsc, true},
        {"tabFullscreenAndPointerLock_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_POINTERLOCK_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressEsc, true},
        {"pointerLock_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_POINTERLOCK_EXIT_INSTRUCTION,
         UserGoal::kExitPointerLock, Shortcut::kPressEsc, true},
        {"tabFullscreenAndKeyboardLock_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_KEYBOARD_LOCK_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressAndHoldEsc, true},
        {"browserFullscreen_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressAndHoldEsc, true},
        {"extensionInitiatedBrowserFullscreen_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreen, Shortcut::kPressAndHoldEsc, true},
    }),
    [](const testing::TestParamInfo<
        ExclusiveAccessBubbleViewsInstructionTextTest::ParamType>& info) {
      return info.param.test_name;
    });

// Tests the creation of a has_download, non-overriding notification.
IN_PROC_BROWSER_TEST_F(ExclusiveAccessBubbleViewsTest, CreateForDownload) {
  ExclusiveAccessBubbleParams params{.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
                                     .has_download = true};
  bubble_view_ = std::make_unique<ExclusiveAccessBubbleViews>(
      GetContext(), params, base::NullCallback());
  EXPECT_TRUE(base::StartsWith(
      GetInstructionViewText(),
      CreateInstructionText(UserGoal::kSeeDownload, Shortcut::kPressEsc)));
}

// Tests the updating of a has_download, non-overriding notification with a
// second one of the same.
IN_PROC_BROWSER_TEST_F(ExclusiveAccessBubbleViewsTest,
                       CreateForDownloadUpdateForDownload) {
  ExclusiveAccessBubbleParams params{.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
                                     .has_download = true};
  bubble_view_ = std::make_unique<ExclusiveAccessBubbleViews>(
      GetContext(), params, base::NullCallback());
  UpdateExclusiveAccessBubbleType(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
                                  /*has_download=*/true);
  EXPECT_TRUE(base::StartsWith(
      GetInstructionViewText(),
      CreateInstructionText(UserGoal::kSeeDownload, Shortcut::kPressEsc)));
}

// Tests updating the bubble first with the type, then a second time for a
// download notification.
using ExclusiveAccessBubbleViewsTestWithDownload =
    ExclusiveAccessBubbleViewsInstructionTextTest;
IN_PROC_BROWSER_TEST_P(ExclusiveAccessBubbleViewsTestWithDownload,
                       UpdateViewContentThenDownload) {
  const InstructionTextTestCase& test_case = GetParam();
  UpdateExclusiveAccessBubbleType(test_case.type, /*has_download=*/false);
  // Download notifications pass the type NONE.
  UpdateExclusiveAccessBubbleType(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
                                  /*has_download=*/true);
  EXPECT_EQ(GetInstructionViewText(),
            CreateInstructionText(test_case.goal, test_case.shortcut));
}

// Tests the above but with the Update() calls coming in the other order.
IN_PROC_BROWSER_TEST_P(ExclusiveAccessBubbleViewsTestWithDownload,
                       UpdateViewContentAfterDownload) {
  const InstructionTextTestCase& test_case = GetParam();
  // Update it for a download.
  UpdateExclusiveAccessBubbleType(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
                                  /*has_download=*/true);
  // A subsequent Update() call with the test-specified type.
  UpdateExclusiveAccessBubbleType(test_case.type, /*has_download=*/false);
  EXPECT_EQ(GetInstructionViewText(),
            CreateInstructionText(test_case.goal, test_case.shortcut));
}

INSTANTIATE_TEST_SUITE_P(
    /* no label */,
    ExclusiveAccessBubbleViewsTestWithDownload,
    testing::ValuesIn<InstructionTextTestCase>({
        {"tabFullscreenSeeDownload",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kPressEsc, false},
        {"browserFullscreenSeeDownload",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kAccelerator,
         false},
        {"extensionInitiatedFullscreenSeeDownload",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kAccelerator,
         false},
        {"tabFullscreenSeeDownload_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kPressEsc, true},
        {"browserFullscreenSeeDownload_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kPressAndHoldEsc,
         true},
        {"extensionInitiatedFullscreenSeeDownload_EnablePressAndHoldEsc",
         EXCLUSIVE_ACCESS_BUBBLE_TYPE_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION,
         UserGoal::kExitFullscreenAndSeeDownload, Shortcut::kPressAndHoldEsc,
         true},
    }),
    [](const testing::TestParamInfo<
        ExclusiveAccessBubbleViewsTestWithDownload::ParamType>& info) {
      return info.param.test_name;
    });
