mod imp {
    use gtk::{gio, glib};
    use gtk::prelude::*;
    use gtk::subclass::prelude::*;
    use patchpal::ui::format::Formatted;
    use patchpal::ui::workspace::Workspace;
    use std::cell::{Cell, OnceCell};

    #[derive(Default)]
    pub(crate) struct CommitsModelItem {
        pub(crate) title: OnceCell<Formatted>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for CommitsModelItem {
        const NAME: &'static str = "BackportCommitsModelItem";
        type Type = super::CommitsModelItem;
        type ParentType = glib::Object;
    }

    impl ObjectImpl for CommitsModelItem {}

    #[derive(Default)]
    pub(crate) struct CommitsModel {
        pub(crate) workspace: OnceCell<Workspace<crate::app::PatchpalApplication>>,
        pub(crate) selectable_count: Cell<u32>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for CommitsModel {
        const NAME: &'static str = "BackportCommitsModel";
        type Type = super::CommitsModel;
        type ParentType = glib::Object;
        type Interfaces = (gio::ListModel,);
    }

    impl ObjectImpl for CommitsModel {}

    impl ListModelImpl for CommitsModel {
        fn item_type(&self) -> glib::Type {
            glib::Object::static_type()
        }

        fn n_items(&self) -> u32 {
            self.workspace.get().unwrap().commit_count() as u32
        }

        fn item(&self, index: u32) -> Option<glib::Object> {
            let workspace = self.workspace.get().unwrap();
            if index < self.n_items() {
                Some(super::CommitsModelItem::new(
                    workspace.commit_title_at(index as usize),
                ).upcast())
            } else {
                None
            }
        }
    }
}

use crate::app::PatchpalApplication;
use gtk::{gio, glib};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use patchpal::ui::format::Formatted;
use patchpal::ui::workspace::Workspace;

glib::wrapper! {
    pub(crate) struct CommitsModelItem(ObjectSubclass<imp::CommitsModelItem>);
}

impl CommitsModelItem {
    fn new(title: Formatted) -> Self {
        let result: Self = glib::Object::builder().build();
        let imp = result.imp();
        imp.title.set(title).unwrap();
        result
    }

    pub(crate) fn title(&self) -> &Formatted {
        self.imp().title.get().unwrap()
    }
}

glib::wrapper! {
    pub(crate) struct CommitsModel(ObjectSubclass<imp::CommitsModel>)
        @implements gio::ListModel;
}

impl CommitsModel {
    pub(crate) fn new(
        workspace: &Workspace<PatchpalApplication>,
        selectable_count: u32,
    ) -> Self {
        let result: Self = glib::Object::builder().build();
        let imp = result.imp();
        imp.workspace.set(workspace.clone())
            .unwrap_or_else(|_| panic!("workspace already set"));
        let total = workspace.commit_count() as u32;
        imp.selectable_count.set(selectable_count.min(total));
        result
    }

    pub(crate) fn selectable_count(&self) -> u32 {
        self.imp().selectable_count.get()
    }

    pub(crate) fn set_selectable_count(&self, count: u32) {
        let total = self.imp().workspace.get().unwrap().commit_count() as u32;
        let count = count.min(total);
        let old = self.imp().selectable_count.get();
        if count == old {
            return;
        }
        self.imp().selectable_count.set(count);
        let pos = old.min(count);
        let changed = old.min(total).abs_diff(count);
        self.items_changed(pos, changed, changed);
    }
}
