use glib::{Object, Properties, prelude::*, subclass};
use gtk4::{
    Accessible, Buildable, CompositeTemplate, ConstraintTarget, Picture, Stack, StackTransitionType, Widget,
    WidgetPaintable, prelude::*, subclass::prelude::*,
};
use libadwaita::{Bin, subclass::prelude::*};
use std::cell::{Cell, RefCell};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::SelfStack)]
    #[template(file = "data/resources/ui_templates/self_stack.blp")]
    pub struct SelfStack {
        #[template_child]
        pub stack: TemplateChild<Stack>,
        #[template_child]
        pub widget_paintable: TemplateChild<WidgetPaintable>,
        #[template_child]
        pub fake_widget: TemplateChild<Picture>,

        #[property(get, set, name = "transition-duration")]
        pub transition_duration: Cell<u32>,

        #[property(get, set = Self::set_child, nullable)]
        pub child: RefCell<Option<Widget>>,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for SelfStack {
        fn constructed(&self) {
            self.transition_duration.set(150);
            self.parent_constructed();
        }
    }

    impl WidgetImpl for SelfStack {}

    impl BinImpl for SelfStack {}

    impl SelfStack {
        fn set_child(&self, widget: Widget) {
            if let Some(widget) = self.stack.child_by_name("widget") {
                self.stack.remove(&widget);
            }
            self.stack.add_named(&widget, Some("widget"));
            self.widget_paintable.set_widget(Some(&widget));
            self.stack.set_visible_child_name("widget");
        }
    }
}

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

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

impl SelfStack {
    pub fn freeze(&self) {
        let imp = self.imp();
        let paintable = imp.widget_paintable.current_image();
        imp.fake_widget.set_paintable(Some(&paintable));

        imp.stack.set_transition_duration(0);
        imp.stack.set_visible_child_full("picture", StackTransitionType::None);
    }

    pub fn update(&self, transition: StackTransitionType) {
        let imp = self.imp();
        imp.stack.set_transition_duration(imp.transition_duration.get());
        imp.stack.set_visible_child_full("widget", transition);
    }

    pub fn is_frozen(&self) -> bool {
        self.imp().stack.visible_child_name().as_deref() == Some("picture")
    }
}
