mod error;
mod ext;
mod peertube;
mod youtube;

use self::error::Error;
use self::peertube::PeertubeThumbnailExtractor;
use self::youtube::YoutubeThumbnailExtractor;
use crate::Element;
pub use ext::{ClientRequestorExt, VideoThumbnailExtractorExt};
use std::sync::Arc;
use tokio::runtime::Runtime;

#[derive(Clone)]
pub struct VideoThumbnailExtractor {
    extractors: Vec<Arc<Box<dyn ext::VideoThumbnailExtractorExt>>>,
    runtime: Arc<Runtime>,
    client_requestor: Arc<Box<dyn ext::ClientRequestorExt>>,
}

impl VideoThumbnailExtractor {
    pub fn new(runtime: &Arc<Runtime>, client_requestor: Box<dyn ext::ClientRequestorExt>) -> Self {
        Self {
            runtime: runtime.clone(),
            client_requestor: Arc::new(client_requestor),
            extractors: vec![
                Arc::new(Box::new(YoutubeThumbnailExtractor)),
                Arc::new(Box::new(PeertubeThumbnailExtractor)),
            ],
        }
    }

    pub fn add_extractor(&mut self, extractor_impl: Arc<Box<dyn ext::VideoThumbnailExtractorExt>>) {
        self.extractors.push(extractor_impl);
    }

    pub async fn add_missing_thumbnails(&self, element: &mut Element) {
        match element {
            Element::Video(video) if video.thumbnail_url.is_none() => {
                let thumbnail = self.extract(&video.url).await;
                if let Ok(thumbnail_url) = thumbnail {
                    video.thumbnail_url.replace(thumbnail_url);
                }
            }
            Element::List(list_items) => {
                for list_item in list_items {
                    Box::pin(self.add_missing_thumbnails(&mut list_item.element)).await;
                }
            }
            Element::Table(table) => {
                for row in &mut table.rows {
                    for cell in &mut row.cells {
                        Box::pin(self.add_missing_thumbnails(&mut cell.element)).await;
                    }
                }
            }
            Element::Container(elements) => {
                for element in elements.iter_mut() {
                    Box::pin(self.add_missing_thumbnails(element)).await;
                }
            }
            Element::Quote(element) => Box::pin(self.add_missing_thumbnails(element)).await,
            Element::Figure(figure) => {
                Box::pin(self.add_missing_thumbnails(&mut figure.element)).await
            }
            _ => {}
        }
    }

    pub async fn extract(&self, video_url: &str) -> Result<String, Error> {
        for extractor in self.extractors.iter() {
            if !extractor.matches_url(video_url) {
                continue;
            }

            tracing::trace!(video_url, "extractor matches url");

            let extractor = extractor.clone();
            let video_url = video_url.to_string();
            let client = self.client_requestor.get_client().await;

            return self
                .runtime
                .spawn(async move { extractor.get_thumbnail_url(&video_url, &client).await })
                .await
                .expect("task join");
        }

        Err(Error::NoExtractor(video_url.to_string()))
    }
}
