// 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_GLIC_HOST_HOST_H_
#define CHROME_BROWSER_GLIC_HOST_HOST_H_

#include <deque>
#include <vector>

#include "base/callback_list.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "chrome/browser/glic/host/context/glic_sharing_manager_provider.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/browser/glic/host/glic_web_client_access.h"
#include "chrome/browser/glic/host/glic_webui.mojom.h"
#include "chrome/browser/glic/public/glic_instance.h"
#include "chrome/browser/glic/public/glic_passkeys.h"
#include "components/autofill/core/browser/integrators/actor/actor_form_filling_types.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/visibility.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"

class Profile;
namespace content {
class WebContents;
class RenderProcessHost;
}  // namespace content
namespace glic {
class GlicKeyedService;
class GlicPageHandler;
class GlicWebClientManager;
class WebUIContentsContainer;
class GlicInstanceMetrics;
class GlicInstanceMetricsBackwardsCompatibility;

class GlicPinCandidateProvider;
class GlicSkillsManager;
class GlicExperimentalTriggeringManager;
class GlicWebClientAccess;

// The host owns the WebUI that contains the main glic UI and the web client.
// TODO(crbug.com/409332639): Better encapsulate details here.
class Host : public GlicSharingManagerProvider {
 public:
  class EmbedderDelegate {
   public:
    virtual ~EmbedderDelegate() = default;

    // Optional, does nothing by default.
    // If supported, ensure `callback` is invoked after the animation finishes
    // or is destroyed.
    virtual void Resize(const gfx::Size& size,
                        base::TimeDelta duration,
                        base::OnceClosure callback);

    // Allows the user to manually resize the widget by dragging. If the widget
    // hasn't been created yet, apply this setting when it is created. No effect
    // if the widget doesn't exist or the feature flag is disabled.
    virtual void EnableDragResize(bool enabled);

    // Attaches glic to the last focused Chrome window.
    virtual void Attach() = 0;
    virtual void Detach() = 0;
    virtual void ClosePanel() = 0;
    virtual void OnReload() = 0;
    // Sets the minimum widget size that the widget will allow the user to
    // resize to.
    virtual void SetMinimumWidgetSize(const gfx::Size& size);
    virtual void CaptureScreenshot(
        glic::mojom::WebClientHandler::CaptureScreenshotCallback callback) = 0;

    // Returns true if the glic widget is visible.
    virtual bool IsShowing() const = 0;

    virtual void SwitchConversation(
        glic::mojom::ConversationInfoPtr info,
        mojom::WebClientHandler::SwitchConversationCallback callback) = 0;

    // Called when the microphone status changes in the web client.
    virtual void OnMicrophoneStatusChanged(mojom::MicrophoneStatus status) = 0;
  };

  // Functions that are on either GlicInstance or GlidKeyedService.
  // Interface for methods that the host can call on an instance.
  // TODO(refactor): This interface should eventually be combined with
  // InstanceInterfaceForMigration.
  class InstanceDelegate {
   public:
    virtual ~InstanceDelegate() = default;
    virtual void CreateTab(
        const ::GURL& url,
        bool open_in_background,
        const std::optional<int32_t>& window_id,
        glic::mojom::WebClientHandler::CreateTabCallback callback,
        bool show_side_panel = true) = 0;

    virtual void FetchZeroStateSuggestions(
        bool is_first_run,
        std::optional<std::vector<std::string>> supported_tools,
        glic::mojom::WebClientHandler::
            GetZeroStateSuggestionsForFocusedTabCallback callback) = 0;

    virtual void CreateZeroStateSuggestionsHandler(
        mojo::PendingReceiver<mojom::ZeroStateSuggestionsHandler> receiver) = 0;

    virtual void RegisterConversation(
        glic::mojom::ConversationInfoPtr info,
        mojom::WebClientHandler::RegisterConversationCallback callback) = 0;

    virtual void OnWebClientCleared() = 0;
    virtual void PrepareForOpen() = 0;
    virtual void OnUserInputSubmitted(mojom::WebClientMode mode,
                                      mojom::PromptType prompt_type) = 0;

    virtual void OnInteractionModeChange(mojom::WebClientMode new_mode) = 0;
    virtual GlicInstanceMetrics& instance_metrics() = 0;
    virtual GlicInstanceMetricsBackwardsCompatibility&
    instance_metrics_backwards_compatibility() = 0;

    virtual GlicSkillsManager& skills_manager() = 0;

    virtual std::unique_ptr<WebUIContentsContainer>
    CreateWebUIContentsContainer() = 0;
    virtual GlicExperimentalTriggeringManager*
    GetExperimentalTriggeringManager() = 0;
  };

  class Observer : public base::CheckedObserver {
   public:
    // Called when Glic is connected to the WebClient.
    virtual void WebClientConnected() {}
    // Called when Glic is disconnected from the WebClient.
    virtual void WebClientDisconnected() {}

    // Called when the web client state changes.
    virtual void WebClientStateChanged(mojom::WebClientState state) {}

    // Called when the client is ready to show, invoked sometime after
    // `Host::PanelWillOpen()` is called.
    virtual void ClientReadyToShow(const mojom::OpenPanelInfo&) {}

    // TODO(b/409332639): These signals are dubious, is this what window
    // controller really wants to know?

    // Called when the web client initialize has failed.
    virtual void WebClientInitializeFailed() {}
    // The webview reached a login page.
    virtual void LoginPageCommitted() {}
    // Called when the WebUI state changes in the glic WebUI.
    // If the glic WebUI is destroyed, the webUI state is returned to
    // kUninitialized.
    virtual void WebUiStateChanged(mojom::WebUiState state) {}
    virtual void ContextAccessIndicatorChanged(bool enabled) {}
  };

  // When no sharing manager provider is supplied, GlicKeyedService is used.
  explicit Host(Profile* profile,
                GlicSharingManagerProvider* sharing_manager_provider,
                GlicInstance* glic_instance,
                InstanceDelegate* instance_delegate);
  Host(const Host&) = delete;
  ~Host() override;
  Host& operator=(const Host&) = delete;

  Profile* profile() const { return profile_; }
  GlicInstance* glic_instance() const { return glic_instance_; }

  void SetDelegate(EmbedderDelegate* delegate);

  struct PanelWillOpenOptions {
    PanelWillOpenOptions();
    ~PanelWillOpenOptions();
    PanelWillOpenOptions(PanelWillOpenOptions&&);
    PanelWillOpenOptions& operator=(PanelWillOpenOptions&&);

    // The conversation to open. If conversation_id is unset/empty, the web
    // client will open a new conversation.
    glic::mojom::ConversationInfoPtr conversation_info =
        glic::mojom::ConversationInfo::New();
    // If set, the textbox for user input will be populated with the given
    // string before the panel opens.
    std::optional<std::string> prompt_suggestion;
    // If set, the suggested query will be auto-sent after the panel opens.
    bool auto_send = false;
    // Up to 3 most recently active conversations, ordered by most recently
    // active first.
    std::optional<std::vector<glic::mojom::ConversationInfoPtr>>
        recently_active_conversations;
    // An override for the First Run Experience.
    mojom::FreOverride fre_override = mojom::FreOverride::kUnspecified;
  };

  // Sets whether the host is visible in an embedder. This signal is debounced
  // on hide, so it will receive a slightly delayed off signal.
  void SetDebouncedVisibility(bool is_visible);

  void PanelWillOpen(mojom::InvocationSource invocation_source,
                     PanelWillOpenOptions options);

  void PanelWasClosed();

  // Requests the primary web client to stop microphone recording.
  void StopMicrophone(base::OnceClosure done);

  void SwitchConversation(
      glic::mojom::ConversationInfoPtr info,
      mojom::WebClientHandler::SwitchConversationCallback callback);

  // Wakes up the host. When awake, the host will maintain a web client.
  void Awaken();
  // Frees resources, no longer maintaining the web client.
  void Hibernate();
  // Returns true if the host should maintain the web client.
  bool IsAwake() const;

  // Request panel closing.
  void Close();
  // Reload the web contents.
  void Reload();

  // Called when the WebUI web contents has navigated.
  void OnWebContentsNavigated();

  // Signals the glic WebUI that the glic window will be shown soon.
  void NotifyWindowIntentToShow();

  // Signals the glic WebUI to adjust the zoom level of its hosted webview.
  void Zoom(mojom::ZoomAction zoom_action, ZoomSource source);

  // GlicSharingManagerProvider Implementation.
  GlicSharingManagerInternal& GetSharingManagerInternal() override;

  GlicPinCandidateProvider& pin_candidate_provider() override;

  Host::InstanceDelegate& instance_delegate();

  GlicInstance& instance() { return *glic_instance_; }

  GlicInstanceMetrics& instance_metrics() {
    return instance_delegate().instance_metrics();
  }

  GlicInstanceMetricsBackwardsCompatibility&
  instance_metrics_backwards_compatibility() {
    return instance_delegate().instance_metrics_backwards_compatibility();
  }

  InstanceId GetInstanceId() const;

  void OnGuestWebClientCleared(bool had_web_client);

  WebUIContentsContainer* contents_container() { return contents_.get(); }
  std::unique_ptr<content::WebContents> ReleaseWebContents();
  void ReclaimWebContents(std::unique_ptr<content::WebContents> web_contents);
  // Returns the WebUI web contents. May be null.
  content::WebContents* webui_contents() const;

  // Sets the visibility override of the WebUI web contents.
  void SetWebContentsVisibilityOverride(
      std::optional<content::Visibility> visibility_override);

  // Returns the WebClient web contents. May be null.
  content::WebContents* web_client_contents() const;

  // Returns whether `contents` is the glic WebUI web contents.
  bool IsGlicWebUi(content::WebContents* contents) const;

  // Returns the guest main frame. May be null and may change over time.
  content::RenderFrameHost* GetGuestMainFrame() const;

  // Returns the list of page handlers for glic WebUI pages.
  std::vector<GlicPageHandler*> GetPageHandlersForTesting();
  GlicPageHandler* GetPrimaryPageHandlerForTesting();

  // TODO(b/409332639): Hide direct access to the web client.
  // TODO(harringtond): Rename to GetWebClient() if we can't remove this.
  GlicWebClientAccess* GetPrimaryWebClient() const;

  void CreateWebClient(
      mojo::PendingReceiver<glic::mojom::WebClientHandler> web_client_receiver);

  void ManualResizeChanged(bool resizing);

  // Whether the primary client is alive and has returned from PanelWillOpen().
  // This transitions to false after PanelWasClosed() is called.
  bool IsPrimaryClientOpen();

  mojom::WebClientState web_client_state() const;
  bool is_web_client_ready() const {
    return web_client_state() == mojom::WebClientState::kResponsive;
  }

  // Whether the primary web client is connected. Guaranteed not to be true
  // until the initialize() handshake has completed.
  virtual bool IsWebClientConnected() const;
  bool IsContextAccessIndicatorEnabled() const;

  std::optional<mojom::InvocationSource> invocation_source() const {
    return invocation_source_;
  }

  void SetInvocationSource(mojom::InvocationSource invocation_source) {
    invocation_source_ = invocation_source;
  }

  mojom::MicrophoneStatus microphone_status() const {
    return microphone_status_;
  }

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Returns the current WebUI state, or kUninitialized if there is no active
  // glic WebUI.
  const mojom::WebUiState& GetPrimaryWebUiState() const {
    return primary_webui_state_;
  }

  void NotifyInstanceActivationChanged(bool is_active);
  void OnActuatingChanged(bool actuating);
  void OnTaskTabsVisibilityChanged(bool has_visible_tab);

  // Informs the web client that additional context is available.
  void NotifyAdditionalContext(mojom::AdditionalContextPtr context);

  // Returns the RenderProcessHost for the WebClient, or nullptr if none.
  content::RenderProcessHost* GetWebClientRenderProcessHost() const;

  // Returns the page handler that owns the WebUI web contents.
  GlicPageHandler* FindPageHandlerForWebUiContents(
      const content::WebContents* webui_contents);

  //////////////////////////////////////////////////////////////////////////
  // Methods intended to be used by page handler or web client handler
  //////////////////////////////////////////////////////////////////////////

  // Called when a login page was committed in a glic webview.
  void LoginPageCommitted(GlicPageHandler* page_handler);

  void WebClientInitialized();
  void WebClientInitializeFailed();

  void SetContextAccessIndicator(bool enabled);

  // Informs the host that the WebUi state has changed.
  void WebUiStateChanged(GlicPageHandler* page_handler,
                         mojom::WebUiState new_state);

  // Called when the web client changes its mode.
  void OnInteractionModeChange(mojom::WebClientMode new_mode);

  // Called when the microphone status changes in the web client.
  void OnMicrophoneStatusChanged(mojom::MicrophoneStatus status);

  // Sets the size of the glic window to the specified dimensions. Callback
  // runs when the animation finishes or is destroyed, or soon if the window
  // doesn't exist yet. In this last case `size` will be used for the
  // initial size when creating the widget later.
  void ResizePanel(const gfx::Size& size,
                   base::TimeDelta duration,
                   base::OnceClosure callback);

  // Allows the user to manually resize the widget by dragging. If the widget
  // hasn't been created yet, apply this setting when it is created. No effect
  // if the widget doesn't exist or the feature flag is disabled.
  void EnableDragResize(bool enabled);
  void HibernateImpl(bool is_destroying);
  void AttachPanel();
  void DetachPanel();
  void ClosePanel();

  // Sets the minimum widget size that the widget will allow the user to resize
  // to.
  void SetMinimumWidgetSize(const gfx::Size& size);

  void CaptureScreenshot(
      glic::mojom::WebClientHandler::CaptureScreenshotCallback callback);

  // Returns true if the widget is visible.
  bool IsWidgetShowing(GlicWebClientAccess* client) const;

  base::WeakPtr<Host> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }

  void FloatingPanelCanAttachChanged(bool can_attach);

  // Returns if the outer frame matches either the WebUI frame or the guest
  // frame.
  bool IsWebContentPresentAndMatches(content::RenderFrameHost* rfh);

  void NotifyActorTaskListRowClicked(int32_t task_id);


  virtual void Invoke(mojom::InvokeOptionsPtr options,
                      base::OnceClosure callback);
  void InvokeWithAutoSubmit(InvokeWithAutoSubmitPasskey auto_submit_passkey,
                            mojom::InvokeOptionsPtr options,
                            base::OnceClosure callback);

  void WebUIPageHandlerAdded(GlicPageHandler* page_handler);
  void WebUIPageHandlerRemoved(GlicPageHandler* page_handler);

 private:
  friend class GlicWebClientManager;

  void UnsetWebClient();
  void InvokeInternal(mojom::InvokeOptionsPtr options,
                      base::OnceClosure callback);
  void OnWebClientStateChanged(mojom::WebClientState state);

  GlicKeyedService& glic_service();
  GlicPageHandler* page_handler() const;
  bool IsGlicWebUiHost(content::RenderProcessHost* host) const;

  // Information about the page handler which is cleared when the page handler
  // goes away.
  struct PageHandlerInfo {
    PageHandlerInfo();
    ~PageHandlerInfo();
    PageHandlerInfo(PageHandlerInfo&&);
    PageHandlerInfo& operator=(PageHandlerInfo&&);

    raw_ptr<GlicPageHandler> page_handler = nullptr;
    // True if the response to PanelWillOpen was received. Cleared when
    // PanelWasClosed() is called.
    bool open_complete = false;
    bool context_access_indicator_enabled = false;
  };

  void PanelWillOpenComplete(GlicWebClientAccess* client,
                             mojom::OpenPanelInfoPtr open_info);
  PageHandlerInfo* FindInfo(GlicPageHandler* handler);
  const PageHandlerInfo* FindInfo(GlicPageHandler* handler) const {
    return const_cast<Host*>(this)->FindInfo(handler);
  }
  PageHandlerInfo* FindInfoForWebUiContents(content::WebContents* web_contents);
  const PageHandlerInfo* FindInfoForWebUiContents(
      content::WebContents* web_contents) const {
    return const_cast<Host*>(this)->FindInfoForWebUiContents(web_contents);
  }
  GlicWebClientManager* web_client_manager();
  const GlicWebClientManager* web_client_manager() const;
  content::Visibility GetExpectedVisibility() const;
  void UpdateVisibility();

  raw_ptr<Profile> profile_;

  // The instance that owns this host.
  raw_ptr<InstanceDelegate> instance_delegate_;
  // Never null, GlicInstance owns this.
  raw_ptr<GlicInstance> glic_instance_;

  // Null before `Initialize()` and after `Shutdown()`.
  raw_ptr<EmbedderDelegate> delegate_;
  base::ReentrantObserverList<Observer> observers_;

  // The invocation source if the panel was opened. This remains present even
  // after the panel is closed.
  std::optional<mojom::InvocationSource> invocation_source_;
  bool panel_open_ = false;
  // Whether the host is (or was recently) showing on an embedder.
  bool debounced_visibility_ = false;
  bool is_manually_resizing_ = false;
  std::optional<PanelWillOpenOptions> pending_panel_open_options_;
  base::flat_map<mojom::AdditionalContextSource, mojom::AdditionalContextPtr>
      pending_additional_contexts_;
  mojom::WebUiState primary_webui_state_ = mojom::WebUiState::kUninitialized;
  std::optional<mojom::PanelState> pending_panel_state_;

  std::unique_ptr<WebUIContentsContainer> contents_;
  std::optional<PageHandlerInfo> handler_info_;

  raw_ptr<GlicSharingManagerProvider> sharing_manager_provider_;

  mojom::MicrophoneStatus microphone_status_ =
      mojom::MicrophoneStatus::kUnknown;
  std::optional<content::Visibility> visibility_override_;
  content::Visibility web_contents_visibility_ = content::Visibility::HIDDEN;
  base::WeakPtrFactory<Host> weak_ptr_factory_{this};
};

// A Host::Delegate which does nothing. For chrome://glic tabs or inactive
// embedders.
class EmptyEmbedderDelegate : public Host::EmbedderDelegate {
 public:
  ~EmptyEmbedderDelegate() override = default;
  void Resize(const gfx::Size& size,
              base::TimeDelta duration,
              base::OnceClosure callback) override;
  void EnableDragResize(bool enabled) override {}
  void Attach() override {}
  void Detach() override {}
  void ClosePanel() override {}
  void OnReload() override {}
  void SetMinimumWidgetSize(const gfx::Size& size) override {}
  void CaptureScreenshot(
      glic::mojom::WebClientHandler::CaptureScreenshotCallback callback)
      override;
  bool IsShowing() const override;
  void SwitchConversation(
      glic::mojom::ConversationInfoPtr info,
      mojom::WebClientHandler::SwitchConversationCallback callback) override;
  void OnMicrophoneStatusChanged(mojom::MicrophoneStatus status) override {}

 private:
  mojom::PanelState panel_state_ =
      mojom::PanelState(mojom::PanelStateKind::kDetached, std::nullopt);
};

}  // namespace glic

#endif  // CHROME_BROWSER_GLIC_HOST_HOST_H_
