use super::enclosure_container::EnclosureContainer;
use super::enclosure_list::EnclosureList;
use super::header::ArticleHeader;
use super::url_overlay::UrlOverlay;
use crate::App;
use crate::content_page::ContentPage;
use crate::gobject_models::GArticle;
use crate::infrastructure::TokioRuntime;
use crate::media::ImageLoader;
use crate::self_stack::{SelfStack, SelfStackTransition};
use glib::{Properties, clone, subclass::*};
use gtk4::{
    Accessible, Buildable, CompositeTemplate, ConstraintTarget, ScrolledWindow, Viewport, Widget, prelude::*,
    subclass::prelude::*,
};
use html2gtk::{HtmlWidget, ImageDownloaderExt, MathRendererExt, ParseResult, RatexRenderer};
use libadwaita::{Bin, Clamp, subclass::prelude::*};
use std::cell::{Cell, RefCell};
use std::str;
use std::sync::Arc;

mod imp {
    use super::*;

    #[derive(Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::ArticleWidget)]
    #[template(file = "data/resources/ui_templates/article_view/article.blp")]
    pub struct ArticleWidget {
        #[template_child]
        pub scroll: TemplateChild<ScrolledWindow>,
        #[template_child]
        pub clamp: TemplateChild<Clamp>,
        #[template_child]
        pub self_stack: TemplateChild<SelfStack>,
        #[template_child]
        pub html_widget: TemplateChild<HtmlWidget>,
        #[template_child]
        pub header: TemplateChild<ArticleHeader>,
        #[template_child]
        pub video_enclosure: TemplateChild<EnclosureContainer>,
        #[template_child]
        pub audio_enclosure: TemplateChild<EnclosureContainer>,
        #[template_child]
        pub image_enclosure: TemplateChild<EnclosureContainer>,
        #[template_child]
        pub all_enclosure: TemplateChild<EnclosureList>,

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

        #[property(get, set)]
        pub inner_margin: Cell<i32>,

        #[property(get, set)]
        pub load_completed: Cell<bool>,

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

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

        fn class_init(klass: &mut Self::Class) {
            UrlOverlay::ensure_type();

            klass.bind_template();
            klass.bind_template_callbacks();

            EnclosureList::ensure_type();
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for ArticleWidget {
        fn constructed(&self) {
            let obj = self.obj();

            let image_downloader: Arc<Box<dyn ImageDownloaderExt>> = Arc::new(Box::new(ImageLoader::new()));
            let math_renderer: Arc<Box<dyn MathRendererExt>> =
                Arc::new(Box::new(RatexRenderer::new(&TokioRuntime::instance())));

            self.html_widget.set_image_downloader(Some(&image_downloader));
            self.html_widget.set_math_renderer(Some(&math_renderer));
            self.html_widget.connect_activate_link(clone!(
                #[weak(rename_to = scroll)]
                self.scroll,
                move |widget, link| {
                    if let Some(id) = link.strip_prefix('#') {
                        let Some(widget_for_id) = widget.get_widget_for_id(id) else {
                            tracing::warn!(id, "no widget for id");
                            return;
                        };
                        let Some(viewport) = scroll.first_child().and_downcast::<Viewport>() else {
                            tracing::warn!("no viewport");
                            return;
                        };

                        tracing::trace!(link, "scroll into view");
                        viewport.scroll_to(&widget_for_id, None);
                        return;
                    }

                    App::open_url_in_default_browser(&link);
                }
            ));
            self.html_widget.connect_play_video_url(|_widget, source, url| {
                let url_str = url.as_str();
                tracing::debug!(url_str, "show video");
                ContentPage::instance().show_video(url_str, &source);
            });
            self.html_widget
                .connect_display_image(|_widget, source, image, parent_url| {
                    ContentPage::instance().display_image(&image, &source, parent_url);
                });
            self.html_widget
                .bind_property("hovered-url", &*obj, "hovered-url")
                .build();

            let settings = App::default().settings().article_view();
            settings
                .bind_property("font-size", &*self.html_widget, "font-size")
                .sync_create()
                .build();
            settings
                .bind_property("font-style", &*self.html_widget, "font-style")
                .sync_create()
                .build();
            settings
                .bind_property("justification", &*self.html_widget, "justification")
                .sync_create()
                .build();
            settings
                .bind_property("line-height", &*self.html_widget, "line-height")
                .sync_create()
                .build();
            settings
                .bind_property("line-height", &*self.html_widget, "paragraph-spacing")
                .transform_to(move |_binding, line_height: f32| {
                    let value = (line_height - 1.0) * 5.0;
                    Some(value as f64)
                })
                .sync_create()
                .build();
            settings
                .bind_property("selectable", &*self.html_widget, "select-text")
                .sync_create()
                .build();
            settings
                .bind_property("content-width", &*self.clamp, "tightening-threshold")
                .sync_create()
                .build();
            settings
                .bind_property("content-width", &*self.clamp, "maximum-size")
                .sync_create()
                .build();

            self.parent_constructed();
        }
    }

    impl WidgetImpl for ArticleWidget {}

    impl BinImpl for ArticleWidget {}

    #[gtk4::template_callbacks]
    impl ArticleWidget {}
}

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

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

impl ArticleWidget {
    pub fn set_parse_result(&self, result: ParseResult) {
        let imp = self.imp();
        imp.audio_enclosure.fill_audio(&result.audio);
        imp.video_enclosure
            .fill_video(&result.video, imp.html_widget.image_downloader());
        imp.image_enclosure
            .fill_image(&result.image, imp.html_widget.image_downloader());

        imp.html_widget.load_element(result.root);
    }

    pub fn set_parse_result_partial(&self, result: ParseResult, height: i32) {
        tracing::info!(height, "set_parse_result_partial");

        let imp = self.imp();
        imp.audio_enclosure.fill_audio(&result.audio);
        imp.video_enclosure
            .fill_video(&result.video, imp.html_widget.image_downloader());
        imp.image_enclosure
            .fill_image(&result.image, imp.html_widget.image_downloader());

        imp.load_completed
            .set(imp.html_widget.load_element_partial(result.root, height));
    }

    pub fn reload(&self) {
        let imp = self.imp();
        if imp.load_completed.get() {
            return;
        }

        self.cancel_all_image_downloads();

        imp.self_stack.freeze();
        imp.html_widget.rebuild();

        glib::timeout_add_local_once(
            std::time::Duration::from_millis(500),
            clone!(
                #[weak]
                imp,
                move || {
                    imp.self_stack.show_content_full(SelfStackTransition::Crossfade);
                }
            ),
        );
    }

    pub fn cancel_all_image_downloads(&self) {
        if let Some(image_downloader) = self.imp().html_widget.image_downloader() {
            glib::spawn_future_local(async move {
                image_downloader.cancel_all().await;
            });
        }
    }

    pub fn clear(&self) {
        let imp = self.imp();
        imp.audio_enclosure.clear();
        imp.video_enclosure.clear();
        imp.image_enclosure.clear();
        imp.all_enclosure.set_is_expanded(false);

        self.set_article(GArticle::NONE);
        self.set_hovered_url(None::<String>);
        self.cancel_all_image_downloads();
    }

    pub fn collapse_enclosures(&self) {
        self.imp().all_enclosure.set_is_expanded(false);
    }
}
