mod bus_name_row;
mod message_tag_row;

use gtk::{
    glib::{self, clone, translate::FromGlib},
    prelude::*,
    subclass::prelude::*,
};

use crate::{
    bus_name_item::BusNameItem,
    bus_name_list::BusNameList,
    filter_pane::{bus_name_row::BusNameRow, message_tag_row::MessageTagRow},
    filtered_message_model::FilteredMessageModel,
    message_tag::MessageTag,
};

mod imp {
    use std::cell::{OnceCell, RefCell};

    use super::*;

    #[derive(Default, glib::Properties, gtk::CompositeTemplate)]
    #[properties(wrapper_type = super::FilterPane)]
    #[template(resource = "/org/freedesktop/Bustle/ui/filter_pane.ui")]
    pub struct FilterPane {
        #[property(get, set = Self::set_model)]
        pub(super) model: OnceCell<FilteredMessageModel>,

        #[template_child]
        pub(super) toolbar_view: TemplateChild<adw::ToolbarView>, /* Unused, but needed for
                                                                   * disposal */
        #[template_child]
        pub(super) message_tag_list_box: TemplateChild<gtk::ListBox>,
        #[template_child]
        pub(super) bus_name_list_box: TemplateChild<gtk::ListBox>,

        pub(super) message_tag_model: RefCell<Option<adw::EnumListModel>>,
        pub(super) bus_name_model: RefCell<Option<BusNameList>>,
    }

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

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_instance_callbacks();
            klass.set_layout_manager_type::<gtk::BinLayout>();

            klass.install_action(
                "filter-pane.unselect-all-message-tags",
                None,
                |obj, _, _| {
                    obj.message_tag_rows_set_selected_no_notify(false);

                    if let Some(model) = obj.imp().message_tag_model.borrow().as_ref() {
                        for item in model.iter::<adw::EnumListItem>() {
                            let enum_list_item = item.unwrap();
                            let message_tag =
                                unsafe { MessageTag::from_glib(enum_list_item.value()) };
                            obj.model().add_message_tag_filter(message_tag);
                        }
                    }
                },
            );

            klass.install_action("filter-pane.unselect-all-bus-names", None, |obj, _, _| {
                obj.bus_name_rows_set_selected_no_notify(false);

                if let Some(model) = obj.imp().bus_name_model.borrow().as_ref() {
                    for item in model.iter::<BusNameItem>() {
                        let bus_name_item = item.unwrap();
                        obj.model()
                            .add_bus_name_filter(bus_name_item.name())
                            .unwrap();
                    }
                }
            });

            klass.install_action("filter-pane.select-all-message-tags", None, |obj, _, _| {
                obj.message_tag_rows_set_selected_no_notify(true);

                obj.model().remove_all_message_tag_filters();
            });

            klass.install_action("filter-pane.select-all-bus-names", None, |obj, _, _| {
                obj.bus_name_rows_set_selected_no_notify(true);

                obj.model().remove_all_bus_name_filters();
            });
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for FilterPane {
        fn constructed(&self) {
            self.parent_constructed();

            let obj = self.obj();

            let message_tag_model = adw::EnumListModel::new(MessageTag::static_type());
            self.message_tag_list_box.bind_model(
                Some(&message_tag_model),
                clone!(
                    #[weak]
                    obj,
                    #[upgrade_or_panic]
                    move |item| {
                        let enum_list_item = item.downcast_ref::<adw::EnumListItem>().unwrap();
                        let message_tag = unsafe { MessageTag::from_glib(enum_list_item.value()) };
                        obj.create_message_tag_row(message_tag).upcast()
                    }
                ),
            );
            self.message_tag_model.replace(Some(message_tag_model));
        }

        fn dispose(&self) {
            self.dispose_template();
        }
    }

    impl WidgetImpl for FilterPane {}

    impl FilterPane {
        fn set_model(&self, model: FilteredMessageModel) {
            let obj = self.obj();

            self.bus_name_model.replace(None);

            model
                .filtered_bus_names()
                .connect_bus_name_list_notify(clone!(
                    #[weak]
                    obj,
                    move |filtered_bus_names| {
                        let imp = obj.imp();

                        obj.message_tag_rows_set_selected_no_notify(true);
                        obj.bus_name_rows_set_selected_no_notify(true);

                        let bus_name_model = filtered_bus_names.bus_name_list();
                        imp.bus_name_list_box.bind_model(
                            bus_name_model.as_ref(),
                            clone!(
                                #[weak]
                                obj,
                                #[upgrade_or_panic]
                                move |item| {
                                    let bus_name_item = item.downcast_ref().unwrap();
                                    obj.create_bus_name_row(bus_name_item).upcast()
                                }
                            ),
                        );
                        imp.bus_name_model.replace(bus_name_model);
                    }
                ));

            self.model.set(model).unwrap();
        }
    }
}

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

impl FilterPane {
    fn create_message_tag_row(&self, message_tag: MessageTag) -> MessageTagRow {
        let row = MessageTagRow::new(&message_tag);
        row.connect_is_active_notify(clone!(
            #[weak(rename_to = obj)]
            self,
            move |row| {
                let model = obj.model();
                let message_tag = row.message_tag();
                if row.is_active() {
                    model.remove_message_tag_filter(message_tag);
                } else {
                    model.add_message_tag_filter(message_tag);
                }
            }
        ));
        row
    }

    fn create_bus_name_row(&self, bus_name_item: &BusNameItem) -> BusNameRow {
        let row = BusNameRow::new(bus_name_item);
        row.connect_is_active_notify(clone!(
            #[weak(rename_to = obj)]
            self,
            move |row| {
                let model = obj.model();
                let bus_name_item = row.bus_name_item();
                let name = bus_name_item.name();
                if row.is_active() {
                    model.remove_bus_name_filter(name);
                } else {
                    model.add_bus_name_filter(name).unwrap();
                }
            }
        ));
        row
    }

    fn message_tag_rows_set_selected_no_notify(&self, is_selected: bool) {
        let imp = self.imp();

        let mut curr = imp.message_tag_list_box.first_child();
        while let Some(child) = &curr {
            let message_tag_row = child.downcast_ref::<MessageTagRow>().unwrap();
            message_tag_row.set_selected_no_notify(is_selected);
            curr = child.next_sibling();
        }
    }

    fn bus_name_rows_set_selected_no_notify(&self, is_selected: bool) {
        let imp = self.imp();

        let mut curr = imp.bus_name_list_box.first_child();
        while let Some(child) = &curr {
            let bus_name_row = child.downcast_ref::<BusNameRow>().unwrap();
            bus_name_row.set_selected_no_notify(is_selected);
            curr = child.next_sibling();
        }
    }
}

#[gtk::template_callbacks]
impl FilterPane {
    #[template_callback]
    fn message_tag_list_box_row_activated(&self, row: &gtk::ListBoxRow) {
        let message_tag_row = row.downcast_ref::<MessageTagRow>().unwrap();
        message_tag_row.handle_activation();
    }

    #[template_callback]
    fn bus_name_list_box_row_activated(&self, row: &gtk::ListBoxRow) {
        let bus_name_row = row.downcast_ref::<BusNameRow>().unwrap();
        bus_name_row.handle_activation();
    }
}
