use std::cell::OnceCell;

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

use crate::data;
use crate::data::{KrFlatpak, KrItem};
use crate::utils::error::DisplayError;
use crate::widgets::KrItemRow;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::KrFlatpakItemsPage)]
    #[template(file = "flatpak_items_page.ui")]
    pub struct KrFlatpakItemsPage {
        #[property(get, set, construct_only)]
        flatpak: OnceCell<KrFlatpak>,
        #[template_child]
        search_bar: TemplateChild<gtk::SearchBar>,
        #[template_child]
        search_entry: TemplateChild<gtk::SearchEntry>,
        #[template_child]
        app_icon: TemplateChild<gtk::Image>,
        #[template_child]
        app_name: TemplateChild<gtk::Label>,
        #[template_child]
        app_description: TemplateChild<gtk::Label>,
        #[template_child]
        item_list: TemplateChild<gtk::ListBox>,
        #[template_child]
        pub content_stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub loading_stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub spinner: TemplateChild<gtk::Spinner>,
        #[template_child]
        status_page: TemplateChild<adw::StatusPage>,
        #[template_child]
        no_results_status_page: TemplateChild<adw::StatusPage>,
        #[template_child]
        preferences_page: TemplateChild<adw::PreferencesPage>,
        model: gtk::SortListModel,
    }

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

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

            klass.install_action("flatpak.search", None, |obj, _, _| {
                obj.imp().search_bar.set_search_mode(true);
            });
            klass.add_binding_action(
                gtk::gdk::Key::f,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "flatpak.search",
            );
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for KrFlatpakItemsPage {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();
            let flatpak = obj.flatpak();

            self.search_bar.connect_entry(&*self.search_entry);

            flatpak
                .bind_property("icon-name", &*self.app_icon, "icon-name")
                .sync_create()
                .build();

            flatpak
                .bind_property("title", &*self.app_name, "label")
                .sync_create()
                .build();

            flatpak
                .bind_property("description", &*self.app_description, "label")
                .sync_create()
                .build();

            // Search
            let search_filter = gtk::StringFilter::new(Some(&gtk::PropertyExpression::new(
                KrItem::static_type(),
                None::<&gtk::Expression>,
                "title",
            )));
            self.search_entry
                .bind_property("text", &search_filter, "search")
                .build();
            let filter_model =
                gtk::FilterListModel::new(Some(self.model.clone()), Some(search_filter));

            filter_model.connect_items_changed(clone!(@weak self as this => move |m, _, _, _| {
                if m.n_items() == 0 {
                    this.loading_stack.set_visible_child(&*this.no_results_status_page);
                } else {
                    this.loading_stack.set_visible_child(&*this.content_stack);
                }
            }));

            // Sort
            let sorter = gtk::StringSorter::new(Some(&gtk::PropertyExpression::new(
                data::KrItem::static_type(),
                gtk::Expression::NONE,
                "label",
            )));
            self.model.set_sorter(Some(&sorter));

            filter_model.connect_items_changed(clone!(@weak self as this => move |m, _, _, _|{
                if m.n_items() == 0 {
                    this.content_stack.set_visible_child(&*this.status_page);
                } else {
                    this.content_stack.set_visible_child(&*this.preferences_page);
                }
            }));

            self.item_list.bind_model(Some(&filter_model), |x| {
                let collection_item: data::KrItem = x.clone().downcast().unwrap();
                KrItemRow::new(collection_item).upcast()
            });

            glib::MainContext::default().spawn_local(clone!(@weak self as this => async move {
                this.loading_stack.set_visible_child(&*this.spinner);

                let flatpak = this.obj().flatpak();
                this.model.set_model(Some(&flatpak));

                flatpak
                    .load_items()
                    .await
                    .handle_error("Unable to load keys");

                this.loading_stack.set_visible_child(&*this.content_stack);
            }));
        }
    }

    impl WidgetImpl for KrFlatpakItemsPage {}

    impl NavigationPageImpl for KrFlatpakItemsPage {}

    #[gtk::template_callbacks]
    impl KrFlatpakItemsPage {
        #[template_callback]
        fn string_is_not_empty(&self, label: &str) -> bool {
            !label.is_empty()
        }
    }
}

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

impl KrFlatpakItemsPage {
    pub fn new(flatpak: &KrFlatpak) -> Self {
        glib::Object::builder().property("flatpak", flatpak).build()
    }
}
