use super::image_viewer::ImageViewer;
use super::scale_revealer::ScaleRevealer;
use super::video_player::VideoPlayer;
use gdk4::Texture;
use glib::{Properties, clone, subclass::*};
use gtk4::{
    Accessible, Allocation, Buildable, CompositeTemplate, ConstraintTarget, Overlay, Revealer, Snapshot, Widget, Window,
};
use libadwaita::{prelude::*, subclass::prelude::*};
use std::cell::Cell;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[template(file = "data/resources/ui_templates/media/media_viewer.blp")]
    #[properties(wrapper_type = super::MediaViewer)]
    pub struct MediaViewer {
        #[template_child]
        pub(super) overlay: TemplateChild<Overlay>,
        #[template_child]
        pub(super) revealer: TemplateChild<ScaleRevealer>,
        #[template_child]
        pub(super) back_revealer: TemplateChild<Revealer>,
        #[template_child]
        pub(super) fullscreen_revealer: TemplateChild<Revealer>,

        #[property(get, set = Self::set_fullscreened, explicit_notify)]
        fullscreened: Cell<bool>,
    }

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

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

            klass.install_action("media-viewer.close", None, |obj, _, _| {
                obj.imp().close();
            });
        }

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

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

            // Bind `fullscreened` to the window property of the same name.
            obj.connect_root_notify(|obj| {
                if let Some(window) = obj.root().and_downcast::<Window>() {
                    window
                        .bind_property("fullscreened", obj, "fullscreened")
                        .sync_create()
                        .build();
                }
            });

            self.revealer.connect_transition_done(clone!(
                #[weak]
                obj,
                move |revealer| {
                    if !revealer.reveal_child() {
                        // Hide the viewer when the hiding transition is done.
                        obj.set_visible(false);
                        revealer.set_child(Widget::NONE);
                    }
                }
            ));

            self.parent_constructed();
        }

        fn dispose(&self) {
            self.overlay.unparent();
        }
    }

    impl WidgetImpl for MediaViewer {
        fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
            let allocation = Allocation::new(0, 0, width, height);
            self.overlay.size_allocate(&allocation, baseline);
        }

        fn snapshot(&self, snapshot: &Snapshot) {
            let obj = self.obj();
            obj.snapshot_child(&*self.overlay, snapshot);
        }
    }

    #[gtk4::template_callbacks]
    impl MediaViewer {
        fn set_fullscreened(&self, fullscreened: bool) {
            if fullscreened == self.fullscreened.get() {
                return;
            }

            self.fullscreened.set(fullscreened);
            self.obj().notify_fullscreened();
        }

        fn close(&self) {
            if self.fullscreened.get() {
                let _ = self.obj().activate_action("win.toggle-fullscreen", None);
                return;
            }

            self.revealer.set_reveal_child(false);
            self.fullscreen_revealer.set_reveal_child(false);
            self.back_revealer.set_reveal_child(false);
        }

        pub(super) fn reveal(&self, source_widget: &Widget) {
            self.obj().set_visible(true);

            self.revealer.set_source_widget(Some(source_widget));
            self.revealer.set_reveal_child(true);
            self.fullscreen_revealer.set_reveal_child(true);
            self.back_revealer.set_reveal_child(true);
        }

        #[template_callback]
        fn on_back_button(&self) {
            self.close();
        }
    }
}

glib::wrapper! {
    /// A widget allowing to view a media file.
    ///
    /// Swiping to the top or bottom closes this viewer.
    pub struct MediaViewer(ObjectSubclass<imp::MediaViewer>)
        @extends Widget,
        @implements Accessible, Buildable, ConstraintTarget;
}

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

impl MediaViewer {
    pub fn show_video(&self, url: &str, source_widget: &impl IsA<Widget>) {
        let imp = self.imp();

        let player = VideoPlayer::new(url);
        imp.revealer.set_child(Some(&player));
        imp.reveal(source_widget.upcast_ref());
    }

    pub fn show_image(&self, texture: &Texture, source_widget: &Widget, parent_url: Option<String>) {
        let imp = self.imp();

        let image_viewer = ImageViewer::new(texture, parent_url);
        imp.revealer.set_child(Some(&image_viewer));
        imp.reveal(source_widget.upcast_ref());
    }
}
