use crate::MathRendererExt;
use crate::elements::Math;
use gtk4::glib::MainContext;
use gtk4::glib::{self, Object, Properties, clone, subclass::prelude::*, subclass::*};
use gtk4::{
    Accessible, Allocation, Buildable, CompositeTemplate, ConstraintTarget, Orientation,
    SizeRequestMode, Stack, Widget, prelude::*, subclass::prelude::*,
};
use std::cell::RefCell;
use std::sync::Arc;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::MathWidget)]
    #[template(file = "src/widget_factory/math/widget.blp")]
    pub struct MathWidget {
        #[template_child]
        pub stack: TemplateChild<Stack>,

        #[property(get, set, name = "math-widget", nullable)]
        pub math_widget: RefCell<Option<Widget>>,

        #[property(get, set, name = "error")]
        pub error: RefCell<Option<String>>,
    }

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

        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 MathWidget {
        fn dispose(&self) {
            self.stack.unparent();
        }
    }

    impl WidgetImpl for MathWidget {
        fn request_mode(&self) -> SizeRequestMode {
            SizeRequestMode::WidthForHeight
        }

        fn measure(&self, orientation: Orientation, for_size: i32) -> (i32, i32, i32, i32) {
            let borrow = self.math_widget.borrow();
            let Some(math_widget) = borrow.as_ref() else {
                return self.stack.measure(orientation, for_size);
            };

            tracing::trace!(?orientation, for_size);

            if orientation == Orientation::Vertical {
                let height = math_widget.height_request();
                tracing::trace!(height);
                (height, height, -1, -1)
            } else {
                let width = math_widget.margin_start()
                    + math_widget.width_request()
                    + math_widget.margin_end();
                tracing::trace!(width);
                (width, width, -1, -1)
            }
        }

        fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
            tracing::trace!(width, height, baseline, "allocate");
            self.stack
                .size_allocate(&Allocation::new(0, 0, width, height), baseline);
            self.parent_size_allocate(width, height, baseline)
        }
    }

    #[gtk4::template_callbacks]
    impl MathWidget {
        #[template_callback]
        fn visible_child_name(&self, math_widget: Option<Widget>) -> &'static str {
            if math_widget.is_some() {
                "math"
            } else {
                "error"
            }
        }
    }
}

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

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

impl MathWidget {
    pub fn load(
        &self,
        math: Math,
        font_size: i32,
        scale_factor: i32,
        math_renderer: Arc<Box<dyn MathRendererExt>>,
    ) {
        MainContext::default().spawn_local(clone!(
            #[weak(rename_to = this)]
            self,
            async move {
                match math_renderer.render(math, font_size, scale_factor).await {
                    Ok(math) => this.set_math_widget(Some(math)),
                    Err(error) => {
                        this.set_error(error.to_string());
                        this.set_math_widget(Widget::NONE);
                    }
                }
            }
        ));
    }
}
