from abc import abstractmethod

import gi

gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gio  # noqa: E402


class ListViewBase(Gtk.ListView):
    """ListView base class, it setup the basic factory, selection model & data model
    handlers must be overloaded & implemented in a sub class
    """

    def __init__(self, model_cls):
        Gtk.ListView.__init__(self)
        # Use the signal Factory, so we can connect our own methods to setup
        self.factory = Gtk.SignalListItemFactory()
        # connect to Gtk.SignalListItemFactory signals
        # check https://docs.gtk.org/gtk4/class.SignalListItemFactory.html for details
        self.factory.connect("setup", self.on_factory_setup)
        self.factory.connect("bind", self.on_factory_bind)
        self.factory.connect("unbind", self.on_factory_unbind)
        self.factory.connect("teardown", self.on_factory_teardown)
        # Create data model, use our own class as elements
        self.set_factory(self.factory)
        self.store = self.setup_store(model_cls)
        # create a selection model containing our data model

        self.filtered = Gtk.FilterListModel()
        self.filtered.set_model(self.store)

        self.model = self.setup_model(self.filtered)
        self.model.connect("selection-changed", self.on_selection_changed)
        # set the selection model to the view

        self.set_model(self.model)

    def setup_model(self, store: Gio.ListModel) -> Gtk.SelectionModel:
        """Setup the selection model to use in Gtk.ListView
        Can be overloaded in subclass to use another Gtk.SelectModel model
        """
        selection = Gtk.SingleSelection.new(store)
        selection.set_autoselect(True)
        return selection

    @abstractmethod
    def setup_store(self, model_cl: Gtk.FilterListModel) -> Gio.ListModel:
        """Setup the data model
        must be overloaded in subclass to use another Gio.ListModel
        """
        raise NotImplementedError

    def add(self, elem):
        """add element to the data model"""
        self.store.append(elem)

    # Gtk.SignalListItemFactory signal callbacks
    # transfer to some some callback stubs, there can be overloaded in
    # a subclass.

    def on_factory_setup(self, widget: Gtk.Widget, item: Gtk.ListItem) -> None:
        """GtkSignalListItemFactory::setup signal callback
        Setup the widgets to go into the ListView"""

        self.factory_setup(widget, item)

    def on_factory_bind(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        """GtkSignalListItemFactory::bind signal callback
        apply data from model to widgets set in setup"""
        self.factory_bind(widget, item)

    def on_factory_unbind(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        """GtkSignalListItemFactory::unbind signal callback
        Undo the the binding done in ::bind if needed
        """
        self.factory_unbind(widget, item)

    def on_factory_teardown(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        """GtkSignalListItemFactory::setup signal callback
        Undo the creation done in ::setup if needed
        """
        self.factory_teardown(widget, item)

    def on_selection_changed(
        self, widget: Gtk.ListView, position: int, n_items: int
    ) -> None:
        # get the current selection (GtkBitset)
        selection = widget.get_selection()
        # the the first value in the GtkBitset, that contain the index of the selection in the data model
        # as we use Gtk.SingleSelection, there can only be one ;-)
        ndx = selection.get_nth(0)
        self.selection_changed(widget, ndx)

    # --------------------> abstract callback methods <--------------------------------
    # Implement these methods in your subclass

    @abstractmethod
    def factory_setup(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        """Setup the widgets to go into the ListView (Overload in subclass)"""
        pass

    @abstractmethod
    def factory_bind(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        """apply data from model to widgets set in setup (Overload in subclass)"""
        pass

    @abstractmethod
    def factory_unbind(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        pass

    @abstractmethod
    def factory_teardown(self, widget: Gtk.ListView, item: Gtk.ListItem) -> None:
        pass

    @abstractmethod
    def selection_changed(self, widget, ndx):
        """triggered when selecting in listview is changed
        ndx: is the index in the data store model that is selected
        """
        pass


class ListViewListStore(ListViewBase):
    """ListView base with an Gio.ListStore as data model
    It can contain misc objects derived from GObject
    """

    def __init__(self, model_cls: Gtk.FilterListModel):
        super(ListViewListStore, self).__init__(model_cls)

    def setup_store(self, model_cls: Gtk.FilterListModel) -> Gio.ListModel:
        """Setup the data model"""
        liststore = Gio.ListStore.new(model_cls)
        return liststore
