use crate::{HtmlWidget, Image, ImageDownloaderExt, ImageWidget, Video};
use gtk4::glib::{
    self, GString, MainContext, Object, Properties, SignalHandlerId, clone, subclass::prelude::*,
    subclass::*,
};
use gtk4::{
    Accessible, Buildable, CompositeTemplate, ConstraintTarget, Widget, prelude::*,
    subclass::prelude::*,
};
use libadwaita::{Bin, subclass::prelude::*};
use once_cell::sync::Lazy;
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::VideoWidget)]
    #[template(file = "src/widget_factory/video/widget.blp")]
    pub struct VideoWidget {
        #[template_child]
        pub thumbnail: TemplateChild<ImageWidget>,

        #[property(get, set, construct_only, name = "video-url")]
        pub video_url: RefCell<String>,

        #[property(get, set, nullable, construct_only, name = "thumbnail-url")]
        pub thumbnail_url: RefCell<Option<String>>,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for VideoWidget {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                vec![
                    Signal::builder("play-video-url")
                        .param_types([super::VideoWidget::static_type(), GString::static_type()])
                        .build(),
                ]
            });
            SIGNALS.as_ref()
        }
    }

    impl WidgetImpl for VideoWidget {}

    impl BinImpl for VideoWidget {}

    #[gtk4::template_callbacks]
    impl VideoWidget {
        #[template_callback]
        fn on_play(&self) {
            let obj = self.obj();
            let widget = obj.upcast_ref();

            let Some(html_widget) = HtmlWidget::find_parent(widget) else {
                tracing::trace!("not decendent of HtmlWidget");
                obj.emit_play_video_url(self.video_url.borrow().as_str());
                return;
            };

            html_widget.emit_play_video_url(self.video_url.borrow().as_str(), widget);
        }
    }
}

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

impl VideoWidget {
    pub fn create(value: Video, downloader: Option<Arc<Box<dyn ImageDownloaderExt>>>) -> Self {
        let widget: Self = Object::builder()
            .property("video-url", value.url.as_str())
            .property("thumbnail-url", value.thumbnail_url.as_deref())
            .build();

        if let Some(thumbnail_url) = value.thumbnail_url
            && let Some(downloader) = downloader
        {
            let content_id = thumbnail_url.as_str().to_string();
            let image = Image {
                ids: HashSet::new(),
                url: Some(thumbnail_url),
                data: None,
                alt: None,
                width: None,
                height: None,
                parent: None,
            };
            MainContext::default().spawn_local(clone!(
                #[weak(rename_to = image_widget)]
                widget.imp().thumbnail,
                async move {
                    image_widget
                        .load(content_id.as_str(), image, downloader)
                        .await
                }
            ));
        }

        widget
    }

    pub fn emit_play_video_url(&self, url: &str) {
        self.emit_by_name::<()>("play-video-url", &[&self.clone(), &GString::from(url)])
    }

    pub fn connect_play_video_url<F: Fn(&Self, GString) + 'static>(&self, f: F) -> SignalHandlerId {
        self.connect_local("play-video-url", false, move |args| {
            let this = args[1]
                .get::<Self>()
                .expect("The value needs to be of type `VideoWidget`");
            let url = args[2]
                .get::<GString>()
                .expect("The value needs to be of type `GString`");
            f(&this, url);
            None
        })
    }
}
