# base.py
#
# Copyright 2025 Hari Rana <theevilskeleton@riseup.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later

from abc import abstractmethod
from typing import TYPE_CHECKING, Any, cast

from gi.repository import GLib, GObject, Gio, Gtk

from refine.logger import logger

if TYPE_CHECKING:
    from refine.main import Application


class InvalidSettingError(TypeError): ...


class BaseInterface(GObject.Object):
    __gtype_name__ = "BaseInterface"

    key: str
    schema_id: str
    schema: Gio.SettingsSchema
    settings: Gio.Settings
    is_valid_setting: bool

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)

        GLib.idle_add(self._post_init)

    @staticmethod
    def set_up_separator_revealer(revealer: Gtk.Revealer, widget: Gtk.Widget) -> None:
        """Initialize and set up the `Gtk.Revealer`."""

        def state_flags_changed(
            widget: Gtk.Widget, _flags: Gtk.StateFlags, revealer: Gtk.Revealer
        ) -> None:
            flags = widget.get_state_flags()

            is_prelight = flags & (Gtk.StateFlags.PRELIGHT)
            is_activated = flags & (Gtk.StateFlags.SELECTED | Gtk.StateFlags.CHECKED)
            is_focused = flags & Gtk.StateFlags.FOCUSED & Gtk.StateFlags.FOCUS_VISIBLE

            revealer.set_reveal_child(not (is_prelight or is_activated or is_focused))

        revealer.set_reveal_child(True)
        widget.connect("state-flags-changed", state_flags_changed, revealer)
        widget.bind_property(
            "visible", revealer, "visible", GObject.BindingFlags.SYNC_CREATE
        )
        widget.bind_property(
            "opacity", revealer, "opacity", GObject.BindingFlags.SYNC_CREATE
        )

    @abstractmethod
    def do_check_value(self) -> None:
        """Check if value exists."""

    @abstractmethod
    def do_update(self) -> None:
        """Update the widget according to the setting."""

    @abstractmethod
    def do_setup(self) -> None:
        """
        Set up the object.

        This is mainly useful to, for example, connect signals, bind properties,
        and other necessary steps when the key is valid.
        """

    def _do_check_key(self) -> None:
        if not self.schema.has_key(self.key):
            logger.warning(f"Key “{self.key}” does not exist. Skipping…")
            raise InvalidSettingError

    def _do_find_schema_id(self) -> None:
        application = cast("Application", Gio.Application.get_default())

        try:
            if settings := application.settings[self.schema_id]:
                self.settings = settings["settings"]
                self.schema = settings["schema"]
        except KeyError:
            pass
        else:
            return

        try:
            for source in application.sources:
                if schema := source.lookup(self.schema_id, True):
                    self.schema = schema
                    break

            if not self.schema:
                raise InvalidSettingError
        except (AttributeError, TypeError) as error:
            application.settings[self.schema_id] = None
            logger.warning(f"Schema ID “{self.schema_id}” does not exist. Skipping…")
            raise InvalidSettingError(error) from error

        self.settings = Gio.Settings.new(self.schema_id)
        application.settings[self.schema_id] = {
            "settings": self.settings,
            "schema": self.schema,
        }

    def _post_init(self) -> bool:
        try:
            self._do_find_schema_id()
            self._do_check_key()
            self.do_check_value()
            self.do_update()
            self.do_setup()
            self.is_valid_setting = True
        except InvalidSettingError:
            pass
        finally:
            self.bind_property(
                "is-valid-setting",
                self,
                "sensitive",
                GObject.BindingFlags.SYNC_CREATE,
            )

        return False
