// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/float/float_controller.h"

#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/shell.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/float/float_test_api.h"
#include "ash/wm/window_state.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/views/frame/browser_frame_view_chromeos.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/test/base/ash/util/ash_test_util.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/ash/components/system_web_apps/system_web_app_type.h"
#include "content/public/test/browser_test.h"
#include "ui/aura/window.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"

namespace {

// Tuck a window to the bottom right corner by generating a fling.
void TuckWindow(aura::Window* window) {
  auto* frame_view = static_cast<BrowserFrameViewChromeOS*>(
      views::Widget::GetWidgetForNativeView(window)
          ->non_client_view()
          ->frame_view());
  const gfx::Point start =
      frame_view->GetBoundsInScreen().top_center() + gfx::Vector2d(0, 10);
  const gfx::Vector2d offset(10, 10);
  ui::test::EventGenerator event_generator(window->GetRootWindow());
  event_generator.GestureTapAt(start);
  event_generator.GestureScrollSequence(start, start + offset,
                                        base::Milliseconds(10), /*steps=*/1);
}

}  // namespace

using FloatControllerBrowserTest = InProcessBrowserTest;

// Tests that repeated tucking of a floated window in tablet mode does not cause
// the window to freeze. Regression test for b/278917878.
IN_PROC_BROWSER_TEST_F(FloatControllerBrowserTest,
                       TuckingBrowserDoesNotFreezeWindow) {
  ash::test::InstallSystemAppsForTesting(browser()->GetProfile());

  // Open two SWAs. The bug was a result of the window targeters installed by
  // the window tucker and immersive mode not being reinstalled in the correct
  // order. More details in b/278917878.
  ash::test::CreateSystemWebApp(browser()->GetProfile(),
                                ash::SystemWebAppType::FILE_MANAGER);
  aura::Window* browser_window1 =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile()
          ->GetWindow()
          ->GetNativeWindow();

  ash::test::CreateSystemWebApp(browser()->GetProfile(),
                                ash::SystemWebAppType::SETTINGS);
  aura::Window* browser_window2 =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile()
          ->GetWindow()
          ->GetNativeWindow();

  ASSERT_NE(browser()->GetWindow()->GetNativeWindow(), browser_window1);
  ASSERT_NE(browser()->GetWindow()->GetNativeWindow(), browser_window2);
  ASSERT_NE(browser_window1, browser_window2);

  auto* float_controller = ash::Shell::Get()->float_controller();

  ash::ShellTestApi().SetTabletModeEnabledForTest(true);

  // Float and then tuck the background window repeatedly. This emulates the
  // steps listed in the bug.
  ui::test::EventGenerator event_generator(browser_window1->GetRootWindow());
  event_generator.PressAndReleaseKeyAndModifierKeys(
      ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(ash::WindowState::Get(browser_window2)->IsFloated());
  TuckWindow(browser_window2);
  ash::ShellTestApi().WaitForWindowFinishAnimating(browser_window2);
  ASSERT_TRUE(
      float_controller->IsFloatedWindowTuckedForTablet(browser_window2));

  // Float `browser_window1` using accelerator and tuck it.
  wm::ActivateWindow(browser_window1);
  event_generator.PressAndReleaseKeyAndModifierKeys(
      ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(ash::WindowState::Get(browser_window1)->IsFloated());
  ASSERT_TRUE(ash::WindowState::Get(browser_window2)->IsMaximized());
  TuckWindow(browser_window1);
  ash::ShellTestApi().WaitForWindowFinishAnimating(browser_window2);
  ASSERT_TRUE(
      float_controller->IsFloatedWindowTuckedForTablet(browser_window1));

  // Float `browser_window2` using accelerator and tuck it. At each point,
  // `TuckWindow` should tuck the window otherwise the window has frozen and the
  // test will hang.
  wm::ActivateWindow(browser_window2);
  event_generator.PressAndReleaseKeyAndModifierKeys(
      ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(ash::WindowState::Get(browser_window2)->IsFloated());
  ASSERT_TRUE(ash::WindowState::Get(browser_window1)->IsMaximized());
  TuckWindow(browser_window2);
  ash::ShellTestApi().WaitForWindowFinishAnimating(browser_window2);
  ASSERT_TRUE(
      float_controller->IsFloatedWindowTuckedForTablet(browser_window2));
}
