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

#ifndef CHROME_RENDERER_ACTOR_TOOL_BASE_H_
#define CHROME_RENDERER_ACTOR_TOOL_BASE_H_

#include <cstdint>
#include <optional>
#include <string>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ref.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chrome/common/actor.mojom-forward.h"
#include "chrome/renderer/actor/journal.h"
#include "components/actor/core/task_id.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_node.h"
#include "third_party/blink/public/web/web_page_popup.h"
#include "third_party/blink/public/web/web_widget.h"

namespace content {
class RenderFrame;
}

namespace actor {

class ToolBase;

// Struct to return the resolved information about a tool target.
struct ResolvedTarget {
  // The node identified by the target. May be null if the node has been
  // removed from DOM.
  blink::WebNode node;

  // The interaction point of node in Blink (physical) pixels, relative to the
  // resolved widget's origin.
  gfx::PointF widget_point;

  // Set if the target hits a popup. If a popup is hit this handle identifies
  // the popup and is used to confirm the popup widget is the one that was
  // originally hit.
  std::optional<blink::WebPagePopup::Handle> popup_handle;

  // The widget this target resolved to. This can be either the frame's widget
  // or the popup widget. Since a widget can be destroyed by script or during
  // async yields, callers should always access via this getter, rather than
  // holding onto the pointer.
  blink::WebWidget* GetWidget(const ToolBase& tool) const;
};

// Struct to return the validation result and the target point if set.
struct ValidationResult {
  explicit ValidationResult(
      mojom::ActionResultPtr result,
      std::optional<gfx::PointF> target_point = std::nullopt);
  ~ValidationResult();
  ValidationResult(ValidationResult&&);
  ValidationResult& operator=(ValidationResult&&);

  mojom::ActionResultPtr result;
  std::optional<gfx::PointF> target_point;
};

class ToolBase {
 public:
  using ToolFinishedCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
  ToolBase(content::RenderFrame& frame,
           TaskId task_id,
           Journal& journal,
           mojom::ToolTargetPtr target,
           mojom::ObservedToolTargetPtr observed_target);
  virtual ~ToolBase();

  // Validates that the tool can be executed and returns the result. The
  // implementation stores any necessary state to allow Execute() to run
  // afterwards.
  virtual ValidationResult Validate() = 0;

  // Executes the tool. `callback` is invoked with the tool result. Requires
  // validation to be called first.
  virtual void Execute(ToolFinishedCallback callback) = 0;

  // Cancels execution of the tool.
  virtual void Cancel();

  // Returns a human readable string representing this tool and its parameters.
  // Used primarily for logging and debugging.
  virtual std::string DebugString() const = 0;

  // The amount of time to wait when observing tool execution before starting to
  // wait for page stability. 0 by default, meaning no delay, but tools can
  // override this on a case-by-case basis when the expected effects of tool use
  // may happen asynchronously outside of the injected events.
  virtual base::TimeDelta ExecutionObservationDelay() const;

  // Scrolls the target element into view if it's not already. If the target is
  // a coordinate, the coordinate is updated to reflect the new location after
  // scrolling. Returns true if a scroll into view was requested.
  virtual bool EnsureTargetInView();

  // Whether or not the tool supports page stability monitoring via paint
  // stability tracking, which is currently only supported on a subset of
  // interactions.
  virtual bool SupportsPaintStability() const;

  void MarkAsRevalidation() { is_revalidation_ = true; }

  content::RenderFrame* frame() const { return &frame_.get(); }
  const TaskId& task_id() const { return task_id_; }

 protected:
  using ResolveResult = base::expected<ResolvedTarget, mojom::ActionResultPtr>;

  enum class TargetOcclusionMode {
    // The normal path: the live hit test at the interaction point must resolve
    // to the same target tree that APC observed.
    kRequireUnoccluded,

    // The server-approved direct activation path: the live hit test may miss
    // the target, but the DOM target still needs normal renderer-local checks.
    kAllowOccludedForDirectActivation,
  };

  // Resolves the given target into the ResolvedTarget struct which includes
  // both a point to inject input events to and a DOM node to validate against.
  ResolveResult ResolveTarget(const mojom::ToolTarget& target) const;

  // Validate that target_ passes tool-agnostic validation (e.g. within
  // viewport, no change between observation and time of use) and resolve the
  // mojom target to Node and Point, ready for tool use.
  ResolveResult ValidateAndResolveTarget(
      TargetOcclusionMode occlusion_mode =
          TargetOcclusionMode::kRequireUnoccluded) const;

  // Returns the Blink-reported reason actor should reject the target, if any.
  // Disabled controls are always rejected; other reasons are caller-controlled.
  std::optional<blink::WebElementInteractionDisallowedReason>
  GetInteractionDisallowedReason(const ResolvedTarget& resolved_target,
                                 bool check_aria,
                                 bool reject_non_disabled_reasons) const;

  // Returns the element whose interaction-disallowed state should be checked.
  // Coordinate hit tests can resolve to text or an internal child, such as a
  // select's UA-shadow button. Validate the nearest element ancestor or host,
  // if present, so internal hit-test details do not block or bypass the action.
  // If TOCTOU validation matched an observed APC target, validate that observed
  // target for the same reason.
  blink::WebElement GetTargetElementForValidation(
      const ResolvedTarget& resolved_target) const;

  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
  // RenderFrame.
  base::raw_ref<content::RenderFrame> frame_;
  TaskId task_id_;
  base::raw_ref<Journal> journal_;
  mojom::ToolTargetPtr target_;
  mojom::ObservedToolTargetPtr observed_target_;

 private:
  // Validate that resolved target matches the observed target from last
  // observation.
  mojom::ActionResultPtr ValidateTimeOfUse(
      const ResolvedTarget& resolved_target,
      TargetOcclusionMode occlusion_mode) const;

  // Returns whether the validation element is a disabled native form control.
  // This stays separate from Blink's general disallowed-state helper so actor
  // does not depend on disabled being reported before other reasons.
  bool IsDisabledFormControlTarget(
      const blink::WebElement& validation_element) const;

  // Returns Blink's interaction-disallowed reason for the validation element.
  // Callers keep disabled rejection separate before consulting this helper.
  std::optional<blink::WebElementInteractionDisallowedReason>
  GetInteractionDisallowedReason(const blink::WebElement& validation_element,
                                 bool check_aria) const;

  bool is_revalidation_ = false;
};
}  // namespace actor

#endif  // CHROME_RENDERER_ACTOR_TOOL_BASE_H_
