// 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 "ui/views/interaction/mouse/interactive_mouse_test.h"

#include <variant>

#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/views/interaction/mouse/interactive_mouse_test_internal.h"

namespace views::test {

using GestureParams = InteractionTestUtilMouse::GestureParams;

using ui::test::internal::kInteractiveTestPivotElementId;

InteractiveMouseTestApi::InteractiveMouseTestApi()
    : test_impl_(private_test_impl()
                     .MaybeRegisterFrameworkImpl<
                         internal::InteractiveMouseTestPrivate>()) {}

InteractiveMouseTestApi::~InteractiveMouseTestApi() = default;

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::MoveMouseTo(
    ElementSpecifier reference,
    RelativePositionSpecifier position) {
  RequireInteractiveTest();
  StepBuilder step;
  step.SetDescription("MoveMouseTo()");
  step.SetElement(reference);
  step.SetStartCallback(base::BindOnce(
      [](InteractiveMouseTestApi* test, RelativePositionCallback pos_callback,
         ui::InteractionSequence* seq, ui::TrackedElement* el) {
        test->mouse_util().PerformGestures(
            base::BindOnce(
                [](ui::InteractionSequence::StepTransitionHandle handle,
                   bool success) { std::move(handle).Proceed(success); },
                seq->SeizeStepTransitionControl()),
            test->test_impl_->GetGestureParamsForStep(el, seq),
            InteractionTestUtilMouse::MoveTo(std::move(pos_callback).Run(el)));
      },
      base::Unretained(this), GetPositionCallback(std::move(position))));
  return step;
}

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::MoveMouseTo(
    AbsolutePositionSpecifier position) {
  return MoveMouseTo(kInteractiveTestPivotElementId,
                     GetPositionCallback(std::move(position)));
}

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::ClickMouse(
    ui_controls::MouseButton button,
    bool release,
    int modifier_keys) {
  RequireInteractiveTest();
  StepBuilder step;
  step.SetDescription("ClickMouse()");
  step.SetElementID(kInteractiveTestPivotElementId);
  step.SetStartCallback(base::BindOnce(
      [](InteractiveMouseTestApi* test, ui_controls::MouseButton button,
         bool release, int modifier_keys, ui::InteractionSequence* seq,
         ui::TrackedElement* el) {
        test->mouse_util().PerformGestures(
            base::BindOnce(
                [](ui::InteractionSequence::StepTransitionHandle handle,
                   bool success) { std::move(handle).Proceed(success); },
                seq->SeizeStepTransitionControl()),
            test->test_impl_->GetGestureParamsForStep(el, seq),
            release ? InteractionTestUtilMouse::
                          MouseGestures{InteractionTestUtilMouse::Click(
                              button, modifier_keys)}
                    : InteractionTestUtilMouse::MouseGestures{
                          InteractionTestUtilMouse::MouseDown(button,
                                                              modifier_keys)});
      },
      base::Unretained(this), button, release, modifier_keys));
  step.SetMustRemainVisible(false);
  return step;
}

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::DragMouseTo(
    ElementSpecifier reference,
    RelativePositionSpecifier position,
    bool release) {
  RequireInteractiveTest();
  StepBuilder step;
  step.SetDescription("DragMouseTo()");
  step.SetElement(reference);
  step.SetStartCallback(base::BindOnce(
      [](InteractiveMouseTestApi* test, RelativePositionCallback pos_callback,
         bool release, ui::InteractionSequence* seq, ui::TrackedElement* el) {
        const gfx::Point target = std::move(pos_callback).Run(el);
        test->mouse_util().PerformGestures(
            base::BindOnce(
                [](ui::InteractionSequence::StepTransitionHandle handle,
                   bool success) { std::move(handle).Proceed(success); },
                seq->SeizeStepTransitionControl()),
            test->test_impl_->GetGestureParamsForStep(el, seq),
            release ? InteractionTestUtilMouse::DragAndRelease(target)
                    : InteractionTestUtilMouse::DragAndHold(target));
      },
      base::Unretained(this), GetPositionCallback(std::move(position)),
      release));
  return step;
}

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::DragMouseTo(
    AbsolutePositionSpecifier position,
    bool release) {
  return DragMouseTo(kInteractiveTestPivotElementId,
                     GetPositionCallback(std::move(position)), release);
}

InteractiveMouseTestApi::StepBuilder InteractiveMouseTestApi::ReleaseMouse(
    ui_controls::MouseButton button,
    int modifier_keys) {
  RequireInteractiveTest();
  StepBuilder step;
  step.SetDescription("ReleaseMouse()");
  step.SetElementID(kInteractiveTestPivotElementId);
  step.SetStartCallback(base::BindOnce(
      [](InteractiveMouseTestApi* test, ui_controls::MouseButton button,
         int modifier_keys, ui::InteractionSequence* seq,
         ui::TrackedElement* el) {
        test->mouse_util().PerformGestures(
            base::BindOnce(
                [](ui::InteractionSequence::StepTransitionHandle handle,
                   bool success) { std::move(handle).Proceed(success); },
                seq->SeizeStepTransitionControl()),
            test->test_impl_->GetGestureParamsForStep(el, seq),
            InteractionTestUtilMouse::MouseUp(button, modifier_keys));
      },
      base::Unretained(this), button, modifier_keys));
  step.SetMustRemainVisible(false);
  return step;
}

// static
InteractiveMouseTestApi::RelativePositionCallback
InteractiveMouseTestApi::GetPositionCallback(AbsolutePositionSpecifier spec) {
  return std::visit(
      absl::Overload{
          [](const gfx::Point& point) {
            return base::BindOnce(
                [](gfx::Point p, ui::TrackedElement*) { return p; }, point);
          },
          [](std::reference_wrapper<gfx::Point> point) {
            return base::BindOnce([](std::reference_wrapper<gfx::Point> p,
                                     ui::TrackedElement*) { return p.get(); },
                                  point);
          },
          [](AbsolutePositionCallback& callback) {
            return base::RectifyCallback<RelativePositionCallback>(
                std::move(callback));
          }},
      spec);
}

// static
InteractiveMouseTestApi::RelativePositionCallback
InteractiveMouseTestApi::GetPositionCallback(RelativePositionSpecifier spec) {
  return std::visit(
      absl::Overload{[](RelativePositionCallback& callback) {
                       return std::move(callback);
                     },
                     [](CenterPoint) {
                       return base::BindOnce([](ui::TrackedElement* el) {
                         return el->GetScreenBounds().CenterPoint();
                       });
                     }},
      spec);
}

}  // namespace views::test
