// 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_BROWSER_ACTOR_ACTOR_TASK_H_
#define CHROME_BROWSER_ACTOR_ACTOR_TASK_H_

#include <iosfwd>
#include <memory>
#include <optional>
#include <vector>

#include "base/callback_list.h"
#include "base/cancelable_callback.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/supports_user_data.h"
#include "base/timer/elapsed_timer.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "chrome/browser/actor/actor_navigation_throttle.h"
#include "chrome/browser/actor/actor_task_delegate.h"
#include "chrome/browser/actor/tools/tool_request.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/common/actor_webui.mojom-forward.h"
#include "chrome/common/glic_enums.mojom.h"
#include "components/actor/core/actor_ui_mode.h"
#include "components/actor/core/aggregated_journal.h"
#include "components/actor/core/task_id.h"
#include "components/actor/core/task_source_info.h"
#include "components/actor/public/mojom/actor_types.mojom-forward.h"
#include "components/optimization_guide/proto/features/actions_data.pb.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/visibility.h"
#include "content/public/common/buildflags.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"

class Profile;
namespace actor {

class ActionTrackerForMetrics;
class ActorKeyedService;
class EnterprisePolicyChecker;
class ExecutionEngine;
class TabObservationStrategy;

namespace ui {
class UiEventDispatcher;
}
struct ActionResultWithLatencyInfo;

// Represents a task that Chrome is executing on behalf of the user.
//
// ActorTask tracks the state of a single interaction session and takes place
// over multiple "turns" (calls to Act()). Browser tabs that are involved in the
// task are added to the set of "controlled" tabs.  ActorTask can be in one of
// three high level states:
//
// * ActorControl: Only the actor is able to interact with controlled tabs
// * UserControl: Only the user is able to interact with controlled tabs
// * Completed: The task is no longer running.
//
// The task is created under actor control. It may be paused or resumed to move
// between actor and user control.
class ActorTask : public base::SupportsUserData {
 public:
  using ActCallback =
      base::OnceCallback<void(std::vector<ActionResultWithLatencyInfo>,
                              TabObservationStrategy)>;

  // Created only via ActorKeyedService::CreateTask or the CreateForTesting
  // method in this class.
  ActorTask(base::PassKey<ActorKeyedService, ActorTask>,
            ActorKeyedService& service,
            TaskId id,
            std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher,
            webui::mojom::TaskOptionsPtr options,
            const TaskSourceInfo& source_info,
            const EnterprisePolicyChecker* policy_checker,
            base::WeakPtr<ActorTaskDelegate> delegate = nullptr,
            std::optional<glic::mojom::InvocationSource>
                initial_invocation_source = std::nullopt);
  ~ActorTask() override;

  ActorTask() = delete;
  ActorTask(const ActorTask&) = delete;
  ActorTask& operator=(const ActorTask&) = delete;

  static std::unique_ptr<ActorTask> CreateForTesting(
      ActorKeyedService& service,
      TaskId id,
      std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher,
      webui::mojom::TaskOptionsPtr options,
      const TaskSourceInfo& source_info,
      const EnterprisePolicyChecker* policy_checker,
      base::WeakPtr<ActorTaskDelegate> delegate,
      std::optional<glic::mojom::InvocationSource> initial_invocation_source =
          std::nullopt);

  TaskId id() const { return id_; }

  const TaskSourceInfo& source_info() const { return source_info_; }

  glic::mojom::FeatureMode feature_mode() const { return feature_mode_; }

  std::optional<glic::mojom::InvocationSource> initial_invocation_source()
      const {
    return initial_invocation_source_;
  }

  const std::string& title() const { return title_; }
  base::WeakPtr<ActorTaskDelegate> delegate() const { return delegate_; }

  void SetNavigationDelegate(
      base::WeakPtr<ActorNavigationThrottle::Delegate> delegate) {
    navigation_delegate_ = std::move(delegate);
  }
  base::WeakPtr<ActorNavigationThrottle::Delegate> navigation_delegate() const {
    return navigation_delegate_;
  }

  const EnterprisePolicyChecker& policy_checker() const {
    return policy_checker_.get();
  }

  // Once `state_` leaves kCreated it should never go back. Once `state_` enters
  // kFinished, kCancelled, or kFailed it should never change. These states are
  // granular, prefer using the Is[Actor|User]Controlled and IsCompleted methods
  // rather than querying `state_` directly.
  //
  // LINT.IfChange(State)
  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.actor
  // GENERATED_JAVA_CLASS_NAME_OVERRIDE: ActorTaskState
  // These enum values are persisted to logs. Do not renumber or reuse numeric
  // values.
  enum class State {
    kCreated = 0,
    kActing = 1,
    kReflecting = 2,
    kPausedByActor = 3,
    kPausedByUser = 4,
    kCancelled = 5,
    kFinished = 6,
    kWaitingOnUser = 7,
    kFailed = 8,
    kMaxValue = kFailed,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/actor/histograms.xml:ActorTaskState, //tools/metrics/histograms/metadata/actor/enums.xml:ActorTaskState)

  // LINT.IfChange(StoppedReason)
  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.actor
  // The reason a task was stopped.
  enum class StoppedReason {
    kStoppedByUser = 0,
    kTaskComplete = 1,
    kModelError = 2,
    kChromeFailure = 3,
    kTabDetached = 4,
    kShutdown = 5,
    kUserStartedNewChat = 6,
    kUserLoadedPreviousChat = 7,
    kUserNavigatedAway = 8,
    kTimeout = 9,
    kMaxValue = kTimeout,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/actor/histograms.xml:StoppedReason,
  // //tools/metrics/histograms/metadata/actor/enums.xml:StoppedReasonEnum)

  enum class TaskDuration {
    kDefault = 0,
    kTransient = 1,
  };

  enum class InterruptReason {
    kUnknownReason = 0,
    kTaskComplete = 1,
    kWaitingUserInput = 2,
    kWaitingUserClarification = 3,
    kWaitingUserConfirmation = 4,
    kWaitingUserTakeOver = 5,
    kWaitingIrrelevantUserInput = 6,
    kWaitingUnsafeCounterAbuseVerdict = 7,
    kWaitingForExperimentalTriggeringConsent = 8,
    kMaxValue = kWaitingForExperimentalTriggeringConsent,
  };

  State GetState() const;
  // TODO(bokan): This should be private (this class must be in control of its
  // state) but is used by tests. Make the tests friends (or update the tests)
  // and remove it from the public interface.
  void SetState(State new_state);

  std::optional<InterruptReason> GetInterruptReason() const {
    return interrupt_reason_;
  }

  TaskDuration get_task_duration() const { return duration_; }

  base::Time GetEndTime() const;

  void Act(std::vector<std::unique_ptr<ToolRequest>>&& actions,
           ActCallback callback);

  // Converts stopped_reason to the final state of the task.
  static State GetTaskStateFromStoppedReason(StoppedReason stopped_reason);

  // Sets State to `stop_reason` and cancels any pending actions.
  void Stop(StoppedReason stop_reason);

  // Pause() is called to indicate that either the actor or user is pausing
  // actor actions, determined by the `from_actor` flag. If the
  // `cancel_existing_action` flag is true, any in-progress action will be
  // cancelled. If there is an existing action and it's not canceled, its
  // completion will resume the task.
  void Pause(bool from_actor, bool cancel_existing_action = true);

  // Resume() puts the task back into an actor-controlled state. The caller is
  // responsible for updating the actor with the latest state of the browser.
  void Resume();

  // Indicate the task is blocked waiting for user input. The task remains in an
  // actor-controlled state. User interaction is prevented unless
  // retain_user_control is set to `true`.
  // TODO(crbug.com/484367299): Implement a proper actor task state for
  // interrupt-with-user-control.
  void Interrupt(
      bool retain_user_control = false,
      InterruptReason interrupt_reason = InterruptReason::kUnknownReason);

  // Uninterrupt from waiting on user input.
  void Uninterrupt(State resumed_state);

  // Cancels any pending actions. Returns true if the task is still running, and
  // false otherwise.
  bool CancelOngoingActions(mojom::ActionResultCode reason);

  // Returns true if the task hasn't completed and is under control of the user.
  // That is, the actor cannot send actions and the user is able to interact
  // with the task's tabs. i.e. the task is "paused".
  bool IsUnderUserControl() const;

  // Returns true if the task hasn't completed and is under control of the
  // actor. That is, the user is unable to interact with the task's tabs.
  bool IsUnderActorControl() const;

  // Returns true if the task has completed, either successfully or cancelled.
  bool IsCompleted() const;
  static bool IsCompletedState(State state);

  ExecutionEngine& GetExecutionEngine() const;

  // Add/remove the given TabHandle to the set of tabs this task is operating
  // over and notify the UI if this is a new tab for the task. Added tabs will
  // enter actuation mode and be kept as visible. If `stop_task_on_detach` is
  // true, then the task will be stopped when the given tab is detached.
  using AddTabCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
  void AddTab(tabs::TabHandle tab,
              bool stop_task_on_detach,
              AddTabCallback callback);
  void RemoveTab(tabs::TabHandle tab);

  // Transient version of the above. The tab will enter the same
  // simulated-visible state but only until the next call to Act. Until then it
  // will always be be included in the LastActedTabs set.
  void ObserveTabOnce(tabs::TabHandle tab_handle);

  // Returns true if the given tab is part of this task's tab set.
  bool HasTab(tabs::TabHandle tab) const;

  // Returns true if the given tab is part of this task's controlled tab set and
  // the task is under actor control.
  bool IsActingOnTab(tabs::TabHandle tab) const;

  using TabHandleSet = absl::flat_hash_set<tabs::TabHandle>;

  // The set of tabs that have been acted on at any point during this task.
  TabHandleSet GetTabs() const;

  // The set of tabs that were acted on by the last call to Act.
  TabHandleSet GetLastActedTabs() const;

  base::WeakPtr<ActorTask> GetWeakPtr();

  Profile* GetProfile() const;

  ActionTrackerForMetrics& action_tracker_for_metrics() const {
    return *action_tracker_for_metrics_;
  }

  ActorKeyedService& actor_keyed_service() const { return service_.get(); }

  bool has_visible_tab() const { return has_visible_tab_; }
  bool is_in_pip() const { return is_in_pip_; }
#if BUILDFLAG(IS_ANDROID)
  void SetIsInPip(bool is_in_pip);
#endif
  ActorUiMode GetUiMode() const;

  // These observations will be added to the final ActionsResult returned by the
  // task. This is currently only used by the load and extract content tool. A
  // check ensures that feature is enabled.
  void AddAdditionalTabObservations(
      std::vector<optimization_guide::proto::TabObservation> tab_observations);

  const std::vector<optimization_guide::proto::TabObservation>&
  GetAdditionalTabObservations() const {
    return additional_tab_observations_;
  }

  void OnTabWillDetach(tabs::TabHandle handle);

  const std::string& step_progress() const { return step_progress_; }
  void SetStepProgress(std::string step_progress);

 private:
  class ActorControlledTabState : public content::WebContentsObserver {
   public:
    ActorControlledTabState(ActorTask* task, bool stop_task_on_detach);
    ~ActorControlledTabState() override;

    void SetContents(content::WebContents* web_contents);

    // content::WebContentsObserver overrides
    void PrimaryPageChanged(content::Page& page) override;
    void OnVisibilityChanged(content::Visibility visibility) override;

    // Parent task
    raw_ptr<ActorTask> task;
    // Keeps the tab in "actuation mode". The runner is present when the tab is
    // actively being kept awake and is reset during pause.
    base::ScopedClosureRunner actuation_runner;
    // When a tab is active, external popup menus are disabled. This runner
    // allows external popups to be created again.
#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
    base::ScopedClosureRunner reenable_external_popups;
#endif  // BUILDFLAG(IS_MAC) && BUILDFLAG(USE_EXTERNAL_POPUP_MENU)

    // Subscription for TabInterface::WillDiscardContents.
    base::CallbackListSubscription content_discarded_subscription;

    // Whether to stop the task when the tab is detached.
    bool stop_task_on_detach = true;
  };

  // Transitions a tab/contents into a state where only the actor is responsible
  // for interacting with the tab.
  void DidTabEnterActorControl(tabs::TabHandle handle);
  void DidContentsEnterActorControl(ActorControlledTabState* state,
                                    content::WebContents* contents);

  // Transitions the tab from being actor controlled back to user being able to
  // interact with in.
  void DidTabExitActorControl(tabs::TabHandle handle);
  void DidContentsExitActorControl(ActorControlledTabState* state,
                                   content::WebContents* contents);

  // Returns true if the tab belongs to a different profile than the task,
  // and logs an error to the journal.
  bool CheckCrossProfileAndLog(tabs::TabInterface* tab,
                               tabs::TabHandle tab_handle,
                               std::string_view method_name);

  // Callback from TabInterface for when the WebContents change.
  void HandleDiscardContents(tabs::TabInterface* tab,
                             content::WebContents* old_contents,
                             content::WebContents* new_contents);

  void OnFinishedAct(std::vector<ActionResultWithLatencyInfo> action_results,
                     TabObservationStrategy observation_strategy);

  void ResetToObserveTabsSet();
  void ResetAdditionalTabObservations();

  // Recomputes the visible tab. This is necessary to capture the previous
  // visibility state for UpdateVisibilityTimes() when called after
  // ActorControlledTabState::OnVisibilityChanged() is fired.
  void RecomputeHasVisibleTab();
  void UpdateVisibilityTimes();

  void DidEarlyAddTabs(std::vector<std::unique_ptr<ToolRequest>>&& actions,
                       std::vector<mojom::ActionResultPtr> add_tab_results);

  State state_ = State::kCreated;

  const raw_ref<ActorKeyedService> service_;

  TaskId id_;

  TaskSourceInfo source_info_;

  // The time at which the task was created.
  base::TimeTicks create_time_;

  // The time at which the task was completed or cancelled.
  base::Time end_time_;

  std::unique_ptr<ActionTrackerForMetrics> action_tracker_for_metrics_;

  // The engine responsible for actually processing and invoking a list of
  // ToolRequests. Always non-null.
  std::unique_ptr<ExecutionEngine> execution_engine_;

  std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher_;

  base::SafeRef<AggregatedJournal> journal_;

  // The title does not change for the lifetime of a task.
  const std::string title_;

  // The task duration type does not change for the lifetime of a task.
  const TaskDuration duration_;

  // The feature mode for the task.
  const glic::mojom::FeatureMode feature_mode_;

  // Invocation source that first opened the Glic instance this task was
  // created from. nullopt for tasks not created via Glic.
  const std::optional<glic::mojom::InvocationSource> initial_invocation_source_;

  // The callback to notify the client of the result of calling Act().
  ActCallback callback_for_act_;

  using DidAddTabsCallback =
      base::CancelableOnceCallback<void(std::vector<mojom::ActionResultPtr>)>;
  DidAddTabsCallback did_add_tabs_callback_;

  // A timer for the current state.
  base::ElapsedTimer current_state_timer_;
  // An accumulation of elapsed times for previous "active" states. i.e. the
  // actor is controlling the task and not waiting on a user action.
  base::TimeDelta total_actor_controlled_active_time_;

  // A timer for the current actuation period.
  base::ElapsedTimer visibility_timer_;
  // Whether any of the controlled tabs is visible.
  bool has_visible_tab_ = false;
  bool is_in_pip_ = false;
  // Total time this task has been actuating while a tab was visible.
  base::TimeDelta total_time_visible_;
  // Total time this task has been actuating with no tabs visible.
  base::TimeDelta total_time_not_visible_;

  // A map from a tab's handle to state associated with that tab. The presence
  // of a tab in this map signifies that it is part of this task.
  absl::flat_hash_map<tabs::TabHandle, std::unique_ptr<ActorControlledTabState>>
      controlled_tabs_;

  // An additional set of tabs to capture for observations at the end of an Act
  // turn. Reset at the beginning of each call to Act.
  absl::flat_hash_map<tabs::TabHandle, std::unique_ptr<ActorControlledTabState>>
      to_observe_tabs_;

  // A set of additional tab observations performed directly by the tools.
  std::vector<optimization_guide::proto::TabObservation>
      additional_tab_observations_;

  // Running number of actions taken in the current state.
  size_t actions_in_current_state_ = 0;
  // Running number of actions this task has taken.
  size_t total_number_of_actions_ = 0;
  // Number of interruptions
  size_t total_number_of_interruptions_ = 0;

  // Whether the user should retain control of tabs while a task is interrupted.
  bool interrupted_task_needs_user_control_ = false;

  // Progress text for the current step.
  std::string step_progress_;

  // Once a task is stopped what the reason was.
  std::optional<StoppedReason> stopped_reason_;

  std::optional<InterruptReason> interrupt_reason_;

  // This is owned by actor keyed service which owns this class.
  const raw_ref<const EnterprisePolicyChecker> policy_checker_;

  // Delegate for task-related events.
  base::WeakPtr<ActorTaskDelegate> delegate_;

  base::WeakPtr<ActorNavigationThrottle::Delegate> navigation_delegate_;

  base::WeakPtrFactory<ui::UiEventDispatcher> ui_weak_ptr_factory_;
  base::WeakPtrFactory<ActorTask> weak_ptr_factory_{this};
};

std::ostream& operator<<(std::ostream& os, const ActorTask::State& state);
std::string ToString(const ActorTask::State& state);

}  // namespace actor

#endif  // CHROME_BROWSER_ACTOR_ACTOR_TASK_H_
