use crate::content_page::ContentPage;
use crate::gobject_models::GArticle;
use glib::{Properties, clone, subclass};
use gtk4::{Accessible, Buildable, CompositeTemplate, ConstraintTarget, Widget, prelude::*, subclass::prelude::*};
use html2gtk::{Audio, AudioWidget, Image, ImageDownloaderExt, ImageType, ImageWidget, Video, VideoWidget};
use libadwaita::{Bin, Carousel, subclass::prelude::*};
use std::cell::RefCell;
use std::collections::HashSet;
use std::sync::Arc;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::EnclosureContainer)]
    #[template(file = "data/resources/ui_templates/article_view/enclosure_container.blp")]
    pub struct EnclosureContainer {
        #[template_child]
        pub carousel: TemplateChild<Carousel>,

        #[property(get, set, nullable)]
        pub article: RefCell<Option<GArticle>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for EnclosureContainer {
        const NAME: &'static str = "EnclosureContainer";
        type ParentType = Bin;
        type Type = super::EnclosureContainer;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_callbacks();
        }

        fn instance_init(obj: &subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for EnclosureContainer {}

    impl WidgetImpl for EnclosureContainer {}

    impl BinImpl for EnclosureContainer {}

    #[gtk4::template_callbacks]
    impl EnclosureContainer {
        #[template_callback]
        fn has_children(&self, n_pages: u32) -> bool {
            n_pages > 0
        }

        #[template_callback]
        fn has_multiple_children(&self, n_pages: u32) -> bool {
            n_pages > 1
        }
    }
}

glib::wrapper! {
    pub struct EnclosureContainer(ObjectSubclass<imp::EnclosureContainer>)
        @extends Widget, Bin,
        @implements Accessible, Buildable, ConstraintTarget;
}

impl Default for EnclosureContainer {
    fn default() -> Self {
        glib::Object::new::<Self>()
    }
}

impl EnclosureContainer {
    pub fn fill_audio(&self, content_audio: &HashSet<String>) {
        self.clear();

        let imp = self.imp();
        let borrow = imp.article.borrow();
        let Some(article) = borrow.as_ref() else {
            return;
        };
        let enclosures = article.enclosures();
        let audio_enclosures = enclosures
            .as_ref()
            .iter()
            .filter(|enc| enc.is_audio())
            .collect::<Vec<_>>();
        let enclosure_count = audio_enclosures.len();

        if enclosure_count == 0 {
            return;
        }

        tracing::trace!(enclosure_count, "article has audio enclosures");

        for audio in audio_enclosures.iter() {
            if audio.alternative_to().is_some() {
                continue;
            }

            let url = audio.url();

            if content_audio.contains(&url) {
                continue;
            }

            tracing::trace!(url, "add audio widget");

            let audio_element = Audio {
                ids: HashSet::new(),
                url,
                author: audio.author(),
                title: audio.title(),
            };
            let audio_widget = AudioWidget::from(&audio_element);
            audio_widget.set_hexpand(true);
            imp.carousel.append(&audio_widget);
        }
    }

    pub fn fill_video(
        &self,
        content_video: &HashSet<String>,
        image_downloader: Option<Arc<Box<dyn ImageDownloaderExt>>>,
    ) {
        self.clear();

        let imp = self.imp();
        let borrow = imp.article.borrow();
        let Some(article) = borrow.as_ref() else {
            return;
        };
        let enclosures = article.enclosures();
        let video_enclosures = enclosures
            .as_ref()
            .iter()
            .filter(|enc| enc.is_video())
            .collect::<Vec<_>>();
        let enclosure_count = video_enclosures.len();

        if enclosure_count == 0 {
            return;
        }

        tracing::trace!(enclosure_count, "article has video enclosures");

        for video in video_enclosures.iter() {
            if video.alternative_to().is_some() {
                continue;
            }

            let url = video.url();

            if content_video.contains(&url) {
                continue;
            }

            let video_element = Video {
                ids: HashSet::new(),
                url,
                thumbnail_url: video.thumbnail_url(),
                author: None,
                title: None,
            };
            let video_widget = VideoWidget::create(video_element, image_downloader.clone());
            video_widget.set_hexpand(true);
            video_widget.connect_play_video_url(|widget, url| {
                let url_str = url.as_str();
                tracing::debug!(url_str, "show video");
                ContentPage::instance().show_video(url_str, widget.upcast_ref());
            });
            imp.carousel.append(&video_widget);
        }
    }

    pub fn fill_image(
        &self,
        content_image: &HashSet<String>,
        image_downloader: Option<Arc<Box<dyn ImageDownloaderExt>>>,
    ) {
        self.clear();

        let Some(image_downloader) = image_downloader else {
            return;
        };

        let imp = self.imp();
        let borrow = imp.article.borrow();
        let Some(article) = borrow.as_ref() else {
            return;
        };
        let enclosures = article.enclosures();
        let image_enclosures = enclosures
            .as_ref()
            .iter()
            .filter(|enc| enc.is_image())
            .collect::<Vec<_>>();
        let enclosure_count = image_enclosures.len();

        if enclosure_count == 0 {
            return;
        }

        self.set_visible(true);

        tracing::trace!(enclosure_count, "article has image enclosures");

        for image in image_enclosures.iter() {
            if image.alternative_to().is_some() {
                continue;
            }

            let url = image.url();

            if content_image.contains(&url) || content_image.iter().any(|src| src.starts_with(&url)) {
                continue;
            }

            let image_element = Image {
                ids: HashSet::new(),
                url: Some(url),
                data: None,
                alt: image.summary(),
                width: if image.width() == -1 { None } else { Some(image.width()) },
                height: if image.height() == -1 {
                    None
                } else {
                    Some(image.height())
                },
                parent: None,
            };
            let image_widget = ImageWidget::new(ImageType::Single, 11);
            image_widget.set_hexpand(true);
            image_widget.connect_display_image(|widget, texture, parent_url| {
                ContentPage::instance().display_image(&texture, widget.upcast_ref(), parent_url)
            });
            image_widget.connect_image_width_notify(clone!(
                #[weak(rename_to = obj)]
                self,
                move |widget| {
                    let width = widget.image_width();
                    let height = widget.image_height();

                    if (width > -1 && width < 200) || height < 120 {
                        tracing::trace!(width, height, "removin small image enclosure");

                        if enclosure_count == 1 {
                            obj.set_visible(false);
                        } else {
                            obj.imp().carousel.remove(widget);
                        }
                    }
                }
            ));
            let content_id = image.article_id().to_string();

            glib::spawn_future_local(clone!(
                #[weak]
                image_widget,
                #[strong]
                image_downloader,
                async move { image_widget.load(&content_id, image_element, image_downloader).await }
            ));

            imp.carousel.append(&image_widget);
        }
    }

    pub fn clear(&self) {
        let imp = self.imp();
        while let Some(child) = imp.carousel.first_child() {
            imp.carousel.remove(&child);
        }
    }
}
