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

use crate::data::{KrCollection, KrCollectionKind};
use crate::widgets::{KrCollectionRow, KrCreateCollectionDialog};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate)]
    #[template(file = "collection_list.ui")]
    pub struct KrCollectionList {
        #[template_child]
        list: TemplateChild<gtk::ListBox>,
        #[template_child]
        session_collection_listbox: TemplateChild<gtk::ListBox>,

        pub model: gtk::SortListModel,
    }

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

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

            klass.install_action("collection.show-create-dialog", None, |obj, _, _| {
                KrCreateCollectionDialog::new().present(obj);
            });
            klass.add_binding_action(
                gtk::gdk::Key::k,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.show-create-dialog",
            );
        }

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

    impl ObjectImpl for KrCollectionList {
        fn constructed(&self) {
            self.parent_constructed();

            let secret_service = crate::app().secret_service();

            // Custom sorter to ensure that login collection is always at top
            let sorter = gtk::CustomSorter::new(|a, b| {
                let a = a.downcast_ref::<KrCollection>().unwrap();
                let b = b.downcast_ref::<KrCollection>().unwrap();

                if a.kind() == KrCollectionKind::Login {
                    return gtk::Ordering::Smaller;
                }

                if b.kind() == KrCollectionKind::Login {
                    return gtk::Ordering::Larger;
                }

                a.title().cmp(&b.title()).into()
            });
            self.model.set_sorter(Some(&sorter));

            // Hide session collection by default
            let filter = gtk::CustomFilter::new(|o| {
                let collection: &KrCollection = o.downcast_ref().unwrap();
                collection.kind() != KrCollectionKind::Session
            });
            let filtered =
                gtk::FilterListModel::new(Some(self.model.clone()), Some(filter.clone()));

            // And only display it when it actually include items
            secret_service.connect_session_collection_notify(clone!(@weak self as this => move |ss| {
                let session_collection = ss.session_collection().unwrap();
                let row = KrCollectionRow::new(&session_collection);
                this.session_collection_listbox.append(&row);

                this.session_collection_listbox.set_visible(session_collection.n_items() != 0);
                session_collection.connect_items_changed(clone!(@weak this => move |session, _, _, _| {
                    this.session_collection_listbox.set_visible(session.n_items() != 0);
                }));
            }));

            // Hide listbox if it doesn't contain any item
            filtered.connect_items_changed(clone!(@weak self as this => move |m, _, _, _|{
                this.list.set_visible(m.n_items() != 0);
            }));

            self.list.bind_model(Some(&filtered), move |o| {
                let collection: &KrCollection = o.downcast_ref().unwrap();
                KrCollectionRow::new(collection).upcast()
            });
        }
    }

    impl WidgetImpl for KrCollectionList {}

    impl PreferencesGroupImpl for KrCollectionList {}
}

glib::wrapper! {
    pub struct KrCollectionList(ObjectSubclass<imp::KrCollectionList>)
        @extends adw::PreferencesGroup, gtk::Widget, gtk::Box;
}

impl KrCollectionList {
    pub fn set_model(&self, model: Option<&impl IsA<gio::ListModel>>) {
        self.imp().model.set_model(model);
    }
}
