// 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 REMOTING_HOST_LINUX_PORTAL_CAPTURE_STREAM_MANAGER_H_
#define REMOTING_HOST_LINUX_PORTAL_CAPTURE_STREAM_MANAGER_H_

#include <cstdint>
#include <memory>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/files/scoped_file.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "remoting/host/linux/capture_stream_manager.h"
#include "remoting/host/linux/gdbus_connection_ref.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"

namespace remoting {

class PipewireCaptureStream;
class ScopedPortalRequest;

// This class is used to create and manage a list of Pipewire capture streams
// that are created through the XDG desktop portal.
// The current implementation does not support adding or removing virtual
// streams after the stream manager is initialized.
class PortalCaptureStreamManager final : public CaptureStreamManager {
 public:
  using InitCallbackSignature = void(base::expected<void, std::string>);
  using InitCallback = base::OnceCallback<InitCallbackSignature>;

  PortalCaptureStreamManager();
  ~PortalCaptureStreamManager() override;

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

  // Initializes the stream manager. Must be called once before calling other
  // methods of this class.
  //
  // If `create_virtual_monitor` is true, a new virtual monitor will be created.
  // Otherwise, the stream manager will capture existing monitors instead.
  // `remote_desktop_session_handle` is a session handle that has been created
  // with the `org.freedesktop.portal.RemoteDesktop.CreateSession` method. This
  // method will select the VIRTUAL source and call Start on the specified
  // session handle.
  void Init(bool create_virtual_monitor,
            GDBusConnectionRef connection,
            gvariant::ObjectPath remote_desktop_session_handle,
            InitCallback callback);

  // CaptureStreamManager interface.
  Observer::Subscription AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer);
  base::WeakPtr<CaptureStream> GetStream(webrtc::ScreenId screen_id) override;
  void AddVirtualStream(const ScreenResolution& initial_resolution,
                        AddStreamCallback callback) override;
  void RemoveVirtualStream(webrtc::ScreenId screen_id) override;
  base::flat_map<webrtc::ScreenId, base::WeakPtr<CaptureStream>>
  GetActiveStreams() override;

  // Returns all active stream IDs and their initial desktop rects. They are
  // reported by the Portal API when the stream just started, and are not
  // updated afterwards.
  base::flat_map<webrtc::ScreenId, const webrtc::DesktopRect*>
  GetActiveStreamInitialRects();

  base::WeakPtr<PortalCaptureStreamManager> GetWeakPtr();

 private:
  struct PendingStream {
    uint32_t pipewire_node_id;
    std::string mapping_id;
    webrtc::DesktopRect initial_rect;
  };

  struct StreamInfo {
    StreamInfo();
    StreamInfo(StreamInfo&&);
    ~StreamInfo();

    StreamInfo& operator=(StreamInfo&&);

    std::unique_ptr<PipewireCaptureStream> stream;
    webrtc::DesktopRect initial_rect;
  };

  // Calls `success_method` if the returned callback is called with a result,
  // otherwise calls OnInitError().
  // Resets `request` iff it is not a nullptr (i.e. pointing to a unique_ptr).
  template <typename SuccessType, typename String>
  GDBusConnectionRef::CallCallback<SuccessType> CheckInitResultAndContinue(
      void (PortalCaptureStreamManager::*success_method)(SuccessType),
      std::unique_ptr<ScopedPortalRequest>* request,
      String&& error_context);

  // No-op if the returned callback is called with a result, otherwise resets
  // `request` and calls OnInitError().
  template <typename String>
  GDBusConnectionRef::CallCallback<GVariantRef<"(o)">> ResetRequestOnFailure(
      std::unique_ptr<ScopedPortalRequest>& request,
      String&& error_context);

  void OnInitError(std::string_view what, Loggable why);

  // DBus callback handlers:
  void OnSelectSourcesResponse(gvariant::GVariantRef<"a{sv}"> result);
  void OnStartResponse(gvariant::GVariantRef<"a{sv}"> result);
  void OnPipeWireStreamOpened(
      std::pair<std::tuple<GDBusFdList::Handle>, GDBusFdList> result);

  std::vector<PendingStream> pending_streams_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // We are using the PipeWire node ID as the screen ID.
  base::flat_map<webrtc::ScreenId, StreamInfo> streams_
      GUARDED_BY_CONTEXT(sequence_checker_);

  InitCallback init_callback_;
  gvariant::ObjectPath remote_desktop_session_handle_;
  GDBusConnectionRef connection_;
  base::ObserverList<Observer> observers_ GUARDED_BY_CONTEXT(sequence_checker_);
  base::ScopedFD pipewire_fd_;

  SEQUENCE_CHECKER(sequence_checker_);

  std::unique_ptr<ScopedPortalRequest> select_sources_request_;
  std::unique_ptr<ScopedPortalRequest> start_request_;

  base::WeakPtrFactory<PortalCaptureStreamManager> weak_ptr_factory_{this};
};

}  // namespace remoting

#endif  // REMOTING_HOST_LINUX_PORTAL_CAPTURE_STREAM_MANAGER_H_
