trimsec 2.2.1

Calculate time saved on using speed multipliers.
Documentation
use std::collections::HashSet;

use reqwest::blocking::Client;

use crate::{
    core::{
        deser::{YTPlaylistItems, YTPlaylistList, YTVideos},
        time::{parse_duration, parse_time},
    },
    errors::TYoutubeError,
    youtube_utils::YoutubeId,
};

pub struct ApiClientManager<'a> {
    client: Client,
    key: &'a str,
}

impl<'a> ApiClientManager<'a> {
    #[must_use]
    pub fn new(key: &'a str) -> Self {
        Self {
            client: Client::new(),
            key,
        }
    }

    pub fn fetch_duration_from_id(
        &self,
        id: &YoutubeId,
        given_max: usize,
    ) -> Result<(String, usize), TYoutubeError> {
        let total_ids = {
            let mut next_tok: Option<String> = None;
            let mut ids = Vec::new();
            let mut seen_tokens: HashSet<String> = HashSet::new();

            if id.is_playlist {
                let url = format!(
                    "https://www.googleapis.com/youtube/v3/playlists?part=contentDetails&id={}&key={}&maxResults=1",
                    &id.id, self.key
                );

                let response: YTPlaylistList = self
                    .client
                    .get(url)
                    .send()
                    .map_err(TYoutubeError::Reqwest)?
                    .json()
                    .map_err(TYoutubeError::Reqwest)?;

                let traversible_items = if let Some(ic) = response.items.first() {
                    let max_traversible = ic.content_details.item_count;

                    if given_max != 0 {
                        if given_max > max_traversible {
                            return Err(TYoutubeError::InvalidMaxSize((
                                given_max,
                                max_traversible,
                            )));
                        } else {
                            given_max
                        }
                    } else {
                        max_traversible
                    }
                } else {
                    return Err(TYoutubeError::InvalidPlaylist(id.id.clone()));
                };

                for start in (0..traversible_items).step_by(50) {
                    let max_results = (traversible_items - start).min(50);

                    let url = format!(
                        "https://www.googleapis.com/youtube/v3/playlistItems?playlistId={}&key={}&maxResults={}&part=contentDetails{}",
                        &id.id,
                        self.key,
                        max_results,
                        if let Some(ref tok) = next_tok {
                            format!("&pageToken={tok}")
                        } else {
                            "".to_string()
                        }
                    );

                    let response: YTPlaylistItems = self
                        .client
                        .get(url)
                        .send()
                        .map_err(TYoutubeError::Reqwest)?
                        .json()
                        .map_err(TYoutubeError::Reqwest)?;

                    if let Some(t) = &response.next_page_token
                        && seen_tokens.contains(t)
                    {
                        break;
                    }

                    let current_ids = response
                        .items
                        .into_iter()
                        .map(|f| f.content_details.video_id);

                    ids.extend(current_ids);

                    if let Some(new_tok) = response.next_page_token {
                        next_tok = Some(new_tok.clone());
                        seen_tokens.insert(new_tok);
                    } else {
                        break;
                    }
                }
            } else {
                ids.push(id.id.clone());
            }

            ids
        };

        let mut total_duration: f64 = 0.0;

        for chunk_ids in total_ids.chunks(50) {
            let url = format!(
                "https://www.googleapis.com/youtube/v3/videos?id={}&key={}&part=contentDetails",
                chunk_ids.join(","),
                self.key
            );

            let response: YTVideos = self
                .client
                .get(url)
                .send()
                .map_err(TYoutubeError::Reqwest)?
                .json()
                .map_err(TYoutubeError::Reqwest)?;

            let chunk_duration: f64 = response
                .items
                .iter()
                .map(|f| {
                    let (dur, _) = parse_duration(
                        f.content_details
                            .duration
                            .to_lowercase()
                            .trim_start_matches("pt"),
                    )
                    .unwrap_or((0.0, 0));
                    dur
                })
                .collect::<Vec<f64>>()
                .iter()
                .sum();

            total_duration += chunk_duration;
        }

        Ok((parse_time(total_duration), total_ids.len()))
    }
}