# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

"""Shared behaviour for chat-style ListViews (DM chat and channel chat)."""

from gi.repository import GLib, Gtk


class ChatScrollMixin:
    """Scroll management, context-menu helpers, and emoji picker.

    Host class must provide:
        _scroll             Gtk.ScrolledWindow
        _list_view          Gtk.ListView
        _store              Gio.ListStore
        _scroll_down_revealer  Gtk.Revealer
        _unread_btn_revealer   Gtk.Revealer
        _text_entry         Gtk.Entry / Gtk.Text
        _context_popover    Gtk.PopoverMenu
        _window             main application window
        _is_sticky          bool
        _restoring_scroll   bool
        _context_menu_open  bool
        _frozen_scroll_value float
        _unread_pos         int
        _load_older_messages()  method
    """

    # ─── Scroll state ────────────────────────────────────────

    def _is_at_bottom(self) -> bool:
        adj = self._scroll.get_vadjustment()
        return adj.get_value() + adj.get_page_size() >= adj.get_upper() - 1.0

    def _on_edge_overshot(self, scrolled_window, pos):
        if pos == Gtk.PositionType.TOP:
            self._load_older_messages()

    def _on_scroll_value_changed(self, adj):
        if self._restoring_scroll:
            return
        if self._context_menu_open:
            adj.set_value(self._frozen_scroll_value)
            return
        at_bottom = self._is_at_bottom()
        self._is_sticky = at_bottom
        self._scroll_down_revealer.set_reveal_child(not at_bottom)
        self._update_unread_btn_visibility()

    def _on_scroll_upper_changed(self, adj, *args):
        if self._restoring_scroll:
            return
        if self._context_menu_open:
            adj.set_value(self._frozen_scroll_value)
            return
        if self._is_sticky:
            self._scroll_down()
        else:
            self._scroll_down_revealer.set_reveal_child(not self._is_at_bottom())

    def _is_item_visible(self, position: int) -> bool:
        adj = self._scroll.get_vadjustment()
        top = adj.get_value()
        bottom = top + adj.get_page_size()
        n = self._store.get_n_items()
        if position < 0 or position >= n or n == 0:
            return False
        item_y = (position / n) * adj.get_upper()
        return top <= item_y < bottom

    def _update_unread_btn_visibility(self):
        if self._unread_pos < 0:
            self._unread_btn_revealer.set_reveal_child(False)
            return
        visible = self._is_item_visible(self._unread_pos)
        if visible:
            self._unread_pos = -1
            self._unread_btn_revealer.set_reveal_child(False)
        else:
            self._unread_btn_revealer.set_reveal_child(True)

    # ─── Scroll actions ──────────────────────────────────────

    def _scroll_down(self):
        if self._context_menu_open or self._is_at_bottom():
            return
        n = self._store.get_n_items()
        if n > 0:
            GLib.idle_add(self._do_scroll_down)

    def _do_scroll_down(self):
        if self._context_menu_open:
            return False
        n = self._store.get_n_items()
        if n > 0:
            self._list_view.scroll_to(n - 1, Gtk.ListScrollFlags.FOCUS, None)
        return False

    def _scroll_to_bottom(self):
        adj = self._scroll.get_vadjustment()
        adj.set_value(adj.get_upper())
        return False

    def _restore_scroll_position(self, value):
        adj = self._scroll.get_vadjustment()
        upper = adj.get_upper() - adj.get_page_size()
        adj.set_value(min(value, upper))
        self._restoring_scroll = False
        self._is_sticky = self._is_at_bottom()
        self._scroll_down_revealer.set_reveal_child(not self._is_sticky)
        return False

    def _scroll_to_item(self, position):
        n = self._store.get_n_items()
        if position < 0 or position >= n:
            return False
        self._list_view.scroll_to(position, Gtk.ListScrollFlags.FOCUS, None)
        return False

    def _on_scroll_to_bottom_clicked(self, btn):
        self._is_sticky = True
        self._scroll_down()

    def _on_scroll_to_unread_clicked(self, btn):
        if self._unread_pos >= 0:
            self._scroll_to_item(self._unread_pos)
            self._unread_pos = -1
            self._unread_btn_revealer.set_reveal_child(False)

    # ─── Context menu helpers ─────────────────────────────────

    def _find_outer_at(self, x, y):
        target = self._list_view.pick(x, y, Gtk.PickFlags.DEFAULT)
        while target is not None and target is not self._list_view:
            if hasattr(target, "_chat_item"):
                return target
            target = target.get_parent()
        return None

    def _on_context_popover_closed(self, popover):
        self._context_menu_open = False
        self._is_sticky = self._is_at_bottom()

    def _on_list_long_press(self, gesture, x, y):
        self._on_list_right_click(None, 1, x, y)

    # ─── Emoji ───────────────────────────────────────────────

    def _on_emoji_clicked(self, btn):
        chooser = Gtk.EmojiChooser()
        chooser.set_parent(btn)
        chooser.connect(
            "emoji-picked",
            lambda c, emoji: self._text_entry.insert_text(emoji, self._text_entry.get_position()),
        )
        chooser.connect("closed", lambda c: c.unparent())
        chooser.popup()
