use adw::prelude::*;
use gtk::{glib, subclass::prelude::*};

use crate::{
    models::{Icon, IconsGroup},
    widgets::{ExportDialog, icons::IconWidget},
};

mod imp {
    use std::cell::OnceCell;

    use super::*;

    #[derive(Default, Debug, gtk::CompositeTemplate, glib::Properties)]
    #[properties(wrapper_type = super::IconsGroupWidget)]
    #[template(resource = "/org/gnome/design/IconLibrary/icons_group.ui")]
    pub struct IconsGroupWidget {
        #[property(get, set, construct_only)]
        pub group: OnceCell<IconsGroup>,
        #[template_child]
        pub flowbox: TemplateChild<gtk::FlowBox>,
        #[template_child]
        pub name_label: TemplateChild<gtk::Label>,
        #[template_child]
        pub system_icons_label: TemplateChild<gtk::Label>,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for IconsGroupWidget {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();
            let group = obj.group();
            self.name_label.set_label(&group.name());

            let sorter = gtk::StringSorter::new(Some(Icon::this_expression("name")));
            let model = gtk::SortListModel::new(Some(group.icons()), Some(sorter));

            self.flowbox.bind_model(Some(&model), move |obj| {
                let icon = obj.downcast_ref::<Icon>().unwrap();
                let child = IconWidget::default();
                child.set_icon(Some(icon));
                child.upcast::<gtk::Widget>()
            });

            if group.is_system() {
                self.system_icons_label.set_visible(true);
            }
        }
    }
    impl WidgetImpl for IconsGroupWidget {}
    impl ListBoxRowImpl for IconsGroupWidget {}
}

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

#[gtk::template_callbacks]
impl IconsGroupWidget {
    pub fn new(group: &IconsGroup) -> Self {
        glib::Object::builder().property("group", group).build()
    }

    pub fn n_visible(&self) -> u32 {
        let mut index = 0;
        let mut counter = 0;
        while let Some(child) = self.imp().flowbox.child_at_index(index) {
            index += 1;
            // We use is_child_visible since is_visible and get_visible
            // always return true.
            if child.is_child_visible() {
                counter += 1;
            }
        }
        counter
    }

    pub fn filter(&self, search_text: Option<String>) {
        let imp = self.imp();
        if let Some(search_text) = search_text {
            imp.flowbox.set_filter_func(move |row| {
                let icon_widget = row.downcast_ref::<IconWidget>().unwrap();
                icon_widget
                    .icon()
                    .map(|icon| icon.should_display(&search_text))
                    .unwrap_or_default()
            });
        } else {
            imp.flowbox.unset_filter_func();
        }
    }

    #[template_callback]
    fn flowbox_child_activated(&self, child: &IconWidget, _flowbox: &gtk::FlowBox) {
        let Some(icon) = child.icon() else {
            return;
        };
        let export_dialog = ExportDialog::new(icon);
        let window = self.root().and_downcast::<gtk::Window>().unwrap();
        export_dialog.present(Some(&window));
    }
}
