use glib::{Object, Properties, prelude::*, subclass};
use gtk4::{
    Accessible, Adjustment, Box, Buildable, CompositeTemplate, ConstraintTarget, PositionType, Scale, Widget,
    prelude::*, subclass::prelude::*,
};
use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::Slider)]
    #[template(file = "data/resources/ui_templates/article_view/slider.blp")]
    pub struct Slider {
        #[template_child]
        pub scale: TemplateChild<Scale>,

        #[template_child]
        pub adj: TemplateChild<Adjustment>,

        #[property(get, set)]
        pub title: RefCell<String>,

        #[property(get, set)]
        pub icon_name: RefCell<String>,

        #[property(get, set)]
        pub value: Cell<f64>,

        pub marks: Arc<Mutex<Vec<f64>>>,
    }

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

        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 Slider {
        fn constructed(&self) {
            let marks1 = self.marks.clone();
            let marks2 = self.marks.clone();

            self.scale
                .adjustment()
                .bind_property("value", &*self.obj(), "value")
                .bidirectional()
                .transform_to(move |_binding, scale_value: f64| {
                    let index = scale_value as usize;
                    let value = marks1.lock().unwrap().get(index).copied().unwrap_or(-1.0);
                    Some(value)
                })
                .transform_from(move |_binding, slide_value: f64| {
                    let index = marks2
                        .lock()
                        .unwrap()
                        .iter()
                        .enumerate()
                        .find(|(_i, val)| f64::abs(**val - slide_value) <= 0.1)
                        .map(|(i, _val)| i)
                        .unwrap_or(0);
                    Some(index as f64)
                })
                .build();

            self.parent_constructed();
        }
    }

    impl WidgetImpl for Slider {}

    impl BoxImpl for Slider {}
}

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

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

impl Slider {
    pub fn add_marks(&self, marks: &[f64]) {
        let imp = self.imp();
        let len = marks.len();

        imp.adj.set_upper((len - 1) as f64);

        for i in 0..len {
            imp.scale.add_mark(i as f64, PositionType::Bottom, None);
        }

        *imp.marks.lock().unwrap() = marks.to_vec();

        imp.scale.adjustment().notify("value");
    }
}
