from gi.repository import Adw, Gdk, Gio, GObject, Gtk

from iotas.ui_utils import check_for_search_starting


@Gtk.Template(resource_path="/org/gnome/World/Iotas/ui/render_search_header_bar.ui")
class RenderSearchHeaderBar(Adw.Bin):
    __gtype_name__ = "RenderSearchHeaderBar"
    __gsignals__ = {
        "resumed": (GObject.SignalFlags.RUN_FIRST, None, ()),
    }

    _entry = Gtk.Template.Child()
    _backward_button = Gtk.Template.Child()
    _forward_button = Gtk.Template.Child()

    def __init__(self) -> None:
        super().__init__()
        self.__active = False

        self.__current_matches = 0
        self.__current_match = 0
        self.__current_term = ""

        self.__limit_notify_timeout = None
        self.__found_signal_handler_id = None
        self.__failed_to_find_signal_handler_id = None
        self.__term_changed_signal_handler_id = None

    def setup(self) -> None:
        """Perform initial setup."""
        self.__setup_actions()

    def check_if_starting(
        self, controller: Gtk.EventControllerKey, keyval: int, state: Gdk.ModifierType
    ) -> bool:
        """Check if starting search via type to search.

        :param Gtk.EventControllerKey controller: The key controller
        :param int keyval: The key value
        :param Gdk.ModifierType state: Any modifier state
        :return: Whether search is starting
        :rtype: bool
        """
        if not check_for_search_starting(controller, keyval, state):
            return False

        self._entry.set_text("")
        if controller.forward(self._entry.text):
            return True

        return False

    def enter(self, render_view, resuming: bool, type_to_search: bool) -> None:
        """Enter search.

        The render view is passed in each time as, depending on configuration, it may be a new
        instance each time.

        :param bool render_view: WebKit view
        :param bool resuming: Whether resuming search (via keyboard shortcut)
        :param bool type_to_search: Starting from type to search
        """
        if type_to_search or resuming:
            # Used when starting search by typing; the initial character will already be populated
            # in the GtkText.
            self._entry.text.grab_focus()
            self._entry.text.set_position(-1)
        else:
            self._entry.select_all_and_focus()

        if self.__found_signal_handler_id is None:
            self.__render_view = render_view
            self.__controller = render_view.get_find_controller()

            if not type_to_search and not resuming:
                self._entry.set_text("")

            self.__term_changed_signal_handler_id = self._entry.text.get_buffer().connect(
                "notify::text", lambda _o, _v: self.__do_search()
            )
            self.__found_signal_handler_id = self.__controller.connect(
                "found-text", self.__on_found_text
            )
            self.__failed_to_find_signal_handler_id = self.__controller.connect(
                "failed-to-find-text", lambda _c: self.__on_failed_to_find_text()
            )

            self.__current_matches = 0
            self.__current_match = 0
            self.__occurrences_count_changed()
            # If we're here via type-to-search we may already have some text
            self.__current_term = self._entry.get_text()

            # Part of the effort to delay all WebKit initialisation
            if "WebKit" not in globals():
                import gi

                gi.require_version("WebKit", "6.0")
                global WebKit
                from gi.repository import WebKit

            if resuming:
                # Blank to avoid found callback thinking we're just iterating results
                self.__current_term = ""
                self.__do_search()
            else:
                self.__resuming = False

        self.active = True

    def exit(self) -> None:
        """Exit search."""
        self.__controller.search_finish()
        self.__controller.disconnect(self.__found_signal_handler_id)
        self.__found_signal_handler_id = None
        self.__controller.disconnect(self.__failed_to_find_signal_handler_id)
        self._entry.text.get_buffer().disconnect(self.__term_changed_signal_handler_id)
        self.active = False

    def disable_actions(self) -> bool:
        """Disable actions."""
        self.__action_group.lookup_action("backward").set_enabled(False)
        self.__action_group.lookup_action("forward").set_enabled(False)

    def focus(self) -> None:
        """Grab focus to entry."""
        self._entry.text.grab_focus()

    @GObject.Property(type=bool, default=False)
    def active(self) -> bool:
        return self.__active

    @active.setter
    def active(self, value: bool) -> None:
        self.__active = value

    def __setup_actions(self) -> None:
        action_group = Gio.SimpleActionGroup.new()
        app = Gio.Application.get_default()

        action = Gio.SimpleAction.new("forward")
        action.connect("activate", lambda _o, _p: self.__move_forward())
        action_group.add_action(action)
        app.set_accels_for_action("render-search.forward", ["<Control>g"])
        action.set_enabled(False)

        action = Gio.SimpleAction.new("backward")
        action.connect("activate", lambda _o, _p: self.__move_backward())
        action_group.add_action(action)
        app.set_accels_for_action("render-search.backward", ["<Shift><Control>g"])
        action.set_enabled(False)

        self.__action_group = action_group
        app.get_active_window().insert_action_group("render-search", action_group)

    def __on_found_text(self, controller, match_count: int) -> None:
        if controller.get_search_text() == self.__current_term:
            return

        self.__current_term = controller.get_search_text()
        self.__current_matches = match_count
        self.__occurrences_count_changed()
        self.__current_match = 1
        self._entry.set_occurrence_position(1)

    def __on_failed_to_find_text(self) -> None:
        self.__current_match = 0
        self.__current_matches = 0
        self.__occurrences_count_changed()

    def __do_search(self) -> None:
        self.__controller.search(
            self._entry.get_text(),
            WebKit.FindOptions.CASE_INSENSITIVE | WebKit.FindOptions.WRAP_AROUND,
            2**32 - 1,
        )

    def __move_forward(self) -> None:
        """Move to next search match."""

        if not self.__active and self._entry.get_text() != "":
            self.emit("resumed")
            return

        self.__current_match += 1
        if self.__current_match > self.__current_matches:
            self.__current_match = 1
        self._entry.set_occurrence_position(self.__current_match)
        self.__controller.search_next()

    def __move_backward(self) -> None:
        """Move to previous search match."""

        if not self.__active and self._entry.get_text() != "":
            self.emit("resumed")
            return

        self.__current_match -= 1
        if self.__current_match < 1:
            self.__current_match = self.__current_matches
        self._entry.set_occurrence_position(self.__current_match)
        self.__controller.search_previous()

    def __occurrences_count_changed(self) -> None:
        self._entry.set_occurrence_count(self.__current_matches)
        search_can_move = self.__current_matches > 0
        self.__action_group.lookup_action("backward").set_enabled(search_can_move)
        self.__action_group.lookup_action("forward").set_enabled(search_can_move)
