chromium/ash/system/focus_mode/sounds/focus_mode_youtube_music_delegate.h

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

#ifndef ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_YOUTUBE_MUSIC_DELEGATE_H_
#define ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_YOUTUBE_MUSIC_DELEGATE_H_

#include <array>
#include <optional>
#include <string>
#include <vector>

#include "ash/ash_export.h"
#include "ash/system/focus_mode/focus_mode_retry_util.h"
#include "ash/system/focus_mode/sounds/focus_mode_sounds_delegate.h"
#include "ash/system/focus_mode/sounds/youtube_music/youtube_music_controller.h"
#include "ash/system/focus_mode/sounds/youtube_music/youtube_music_types.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "google_apis/common/api_error_codes.h"
#include "url/gurl.h"

namespace ash {

inline constexpr size_t kYouTubeMusicPlaylistBucketCount = 3;

// This class handles requests from `FocusModeSoundsDelegate` interface. It
// talks to YouTube Music API backend asynchronously, and returns results via
// given callbacks. It handles one request of a kind at a time, which means
// consecutive requests of the same kind would overwrite the previous one. It
// also invokes callbacks strictly, i.e. when successful, failed, or
// overwritten, it would run the given callbacks with valid/empty data.
class ASH_EXPORT FocusModeYouTubeMusicDelegate
    : public FocusModeSoundsDelegate {
 public:
  FocusModeYouTubeMusicDelegate();
  ~FocusModeYouTubeMusicDelegate() override;

  youtube_music::YouTubeMusicController* youtube_music_controller() const {
    return youtube_music_controller_.get();
  }

  // FocusModeSoundsDelegate:
  void GetNextTrack(const std::string& playlist_id,
                    FocusModeSoundsDelegate::TrackCallback callback) override;
  void GetPlaylists(
      FocusModeSoundsDelegate::PlaylistsCallback callback) override;

  void SetNoPremiumCallback(base::RepeatingClosure callback);

  // Reports music playback.
  bool ReportPlayback(const youtube_music::PlaybackData& playback_data);

  // Reserves a playlist for the returned playlists.
  void ReservePlaylistForGetPlaylists(const std::string& playlist_id);

 private:
  // Struct that keeps track of ongoing `GetPlaylists` request. It contains
  // enough information about how the current request should be done.
  struct GetPlaylistsRequestState {
    GetPlaylistsRequestState();
    ~GetPlaylistsRequestState();

    void Reset();

    void ResetDoneCallback();

    std::vector<FocusModeSoundsDelegate::Playlist> GetTopPlaylists();

    // Data structure that holds data from multiple API requests. It's organized
    // in buckets so that the returned list is ordered.
    std::array<std::vector<Playlist>, kYouTubeMusicPlaylistBucketCount>
        playlist_buckets;

    // Playlist ID to bucket map. It contains all specific playlists to query
    // for the request.
    base::flat_map<std::string, size_t> playlists_to_query;

    // Reserved playlist to query if set.
    std::optional<std::string> reserved_playlist_id;

    // Target number of API requests.
    int target_count = 0;

    // Count of current done API requests.
    int count = 0;

    // Callback to run when this request is successful, failed, or overwritten.
    FocusModeSoundsDelegate::PlaylistsCallback done_callback;
  };

  // Struct that keeps track of ongoing `GetNextTrack` request. It contains
  // enough information about how the current request should be done.
  struct GetNextTrackRequestState {
    GetNextTrackRequestState();
    ~GetNextTrackRequestState();

    void Reset();

    void ResetDoneCallback();

    std::string last_playlist_id;
    std::string last_queue_id;
    FocusModeSoundsDelegate::TrackCallback done_callback;
    FocusModeRetryState retry_state;
  };

  // Struct that keeps track of ongoing `ReportPlaybackRequest` request. It
  // contains enough information about how the current request should be done.
  struct ReportPlaybackRequestState {
    ReportPlaybackRequestState();
    ~ReportPlaybackRequestState();

    // Checks if it can report the playback for `url`.
    bool CanReportPlaybackForUrl(const GURL& url);

    // URL to `PlaybackState` map. It contains all playback data for the
    // requests.
    base::flat_map<GURL, youtube_music::PlaybackState> url_to_playback_state;

    // URL to playback reporting token map. It contains all tokens for the
    // requests.
    base::flat_map<GURL, std::string> url_to_token;
  };

  // Called when get playlists request is done.
  void OnGetPlaylistDone(size_t bucket,
                         google_apis::ApiErrorCode http_error_code,
                         std::optional<youtube_music::Playlist> playlist);

  // Called when get music section request is done.
  void OnGetMusicSectionDone(
      size_t bucket,
      google_apis::ApiErrorCode http_error_code,
      std::optional<const std::vector<youtube_music::Playlist>> playlists);

  // Triggers request to get next track depending on the current request state.
  void GetNextTrackInternal(const std::string& playlist_id);

  // Called when switching to next track is done.
  void OnNextTrackDone(
      const std::string& playlist_id,
      google_apis::ApiErrorCode http_error_code,
      std::optional<const youtube_music::PlaybackContext> playback_context);

  // Called when report playback request is done.
  void OnReportPlaybackDone(
      const GURL& url,
      google_apis::ApiErrorCode http_error_code,
      std::optional<const std::string> new_playback_reporting_token);

  // Playlists request state for `GetPlaylists`.
  GetPlaylistsRequestState get_playlists_state_;

  // Next track request state for `GetPlaylists`.
  GetNextTrackRequestState next_track_state_;

  // Report playback request state for `ReportPlayback`.
  ReportPlaybackRequestState report_playback_state_;

  // Callback to run when the request fails with HTTP 403.
  base::RepeatingClosure no_premium_callback_;

  // Controller for YouTube Music API integration.
  std::unique_ptr<youtube_music::YouTubeMusicController>
      youtube_music_controller_;

  base::WeakPtrFactory<FocusModeYouTubeMusicDelegate> weak_factory_{this};
};

}  // namespace ash

#endif  // ASH_SYSTEM_FOCUS_MODE_SOUNDS_FOCUS_MODE_YOUTUBE_MUSIC_DELEGATE_H_