# This file is part of Gajim.
#
# SPDX-License-Identifier: GPL-3.0-only

from __future__ import annotations

from typing import Any
from typing import cast
from typing import TYPE_CHECKING

import logging
from collections.abc import Iterator

from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import Gtk

from gajim.common import app
from gajim.common.const import AvatarSize
from gajim.common.i18n import _

from gajim.gtk.chat_list_row import ChatListRow
from gajim.gtk.menus import get_workspace_menu
from gajim.gtk.structs import ChatListEntryParam
from gajim.gtk.util import GajimPopover
from gajim.gtk.util import get_listbox_row_count
from gajim.gtk.util import iterate_listbox_children
from gajim.gtk.util import open_window

if TYPE_CHECKING:
    # Simplifies testing
    from gajim.gtk.chat_list_stack import ChatListStack
    from gajim.gtk.chat_page import ChatPage

log = logging.getLogger('gajim.gtk.workspace_sidebar')


class WorkspaceSideBar(Gtk.ListBox):
    def __init__(self, chat_page: ChatPage) -> None:
        Gtk.ListBox.__init__(self)
        self.set_vexpand(True)
        self.set_valign(Gtk.Align.START)
        self.get_style_context().add_class('workspace-sidebar')

        formats_workspace = Gdk.ContentFormats.new_for_gtype(Workspace)
        formats_chat_list_row = Gdk.ContentFormats.new_for_gtype(ChatListRow)
        formats = formats_workspace.union(formats_chat_list_row)

        drop_target = Gtk.DropTarget(formats=formats, actions=Gdk.DragAction.MOVE)
        drop_target.connect('drop', self._on_drop)
        self.add_controller(drop_target)

        self.connect('row-activated', self._on_row_activated)

        self.append(AddWorkspace('add'))

        chat_list_stack = chat_page.get_chat_list_stack()
        chat_list_stack.connect('unread-count-changed',
                                self._on_unread_count_changed)
        chat_list_stack.connect('chat-selected',
                                self._on_chat_selected)
        self._workspaces: dict[str, Workspace] = {}

    def get_row_count(self) -> int:
        return get_listbox_row_count(self)

    def _iterate_rows(self) -> Iterator[CommonWorkspace]:
        for row in iterate_listbox_children(self):
            yield cast(CommonWorkspace, row)

    def _on_drop(self,
        _drop_target: Gtk.DropTarget,
        value: GObject.Value,
        _x: float,
        y: float
    ) -> bool:
        target_workspace = self.get_row_at_y(int(y))

        if not value or not isinstance(target_workspace, Workspace):
            # Reject drop
            return False

        if isinstance(value, Workspace):
            target_index = target_workspace.get_index()
            self.remove(value)
            self.insert(value, target_index)
            target_workspace.set_state_flags(Gtk.StateFlags.NORMAL, True)

            self.store_workspace_order()
            app.window.activate_workspace(value.workspace_id)
            return True

        if isinstance(value, ChatListRow):
            params = ChatListEntryParam(
                workspace_id=target_workspace.workspace_id,
                source_workspace_id=value.workspace_id,
                account=value.account,
                jid=value.jid
            )
            app.window.activate_action(
                'win.move-chat-to-workspace',
                params.to_variant()
            )
            return True

        # Reject drop
        log.debug('Unknown item type dropped')
        return False

    def _on_unread_count_changed(self,
                                 _chat_list_stack: ChatListStack,
                                 workspace_id: str,
                                 count: int
                                 ) -> None:
        workspace = self._workspaces[workspace_id]
        workspace.set_unread_count(count)

    def _on_chat_selected(self,
                          _chat_list_stack: ChatListStack,
                          workspace_id: str,
                          *args: Any) -> None:
        self.activate_workspace(workspace_id)

    @staticmethod
    def _on_row_activated(_listbox: Gtk.ListBox,
                          row: CommonWorkspace
                          ) -> None:
        if row.workspace_id == 'add':
            open_window('WorkspaceDialog')
        else:
            app.window.activate_workspace(row.workspace_id)

    def add_workspace(self, workspace_id: str) -> None:
        row = Workspace(workspace_id)
        self._workspaces[workspace_id] = row

        # Insert row before AddWorkspace row
        self.insert(row, self.get_row_count() - 1)

    def store_workspace_order(self) -> None:
        workspaces = list(self._iterate_rows())
        order = [row.workspace_id for row in workspaces]
        order.remove('add')
        app.settings.set_app_setting('workspace_order', order)

    def remove_workspace(self, workspace_id: str) -> None:
        row = self._workspaces.pop(workspace_id)
        self.remove(row)

    def get_other_workspace(self,
                            exclude_workspace_id: str
                            ) -> str | None:

        for workspace in self._workspaces.values():
            if workspace.workspace_id != exclude_workspace_id:
                return workspace.workspace_id
        return None

    def activate_workspace(self, workspace_id: str) -> None:
        row = cast(CommonWorkspace | None, self.get_selected_row())
        if row is not None and row.workspace_id == workspace_id:
            return

        row = self._workspaces[workspace_id]
        self.select_row(row)

    def activate_workspace_number(self, number: int) -> None:
        row = cast(CommonWorkspace | None, self.get_row_at_index(number))
        if row is not None and row.workspace_id != 'add':
            app.window.activate_workspace(row.workspace_id)

    def get_active_workspace(self) -> str | None:
        row = cast(CommonWorkspace | None, self.get_selected_row())
        if row is None:
            return None
        return row.workspace_id

    def get_first_workspace(self) -> str:
        for row in self._iterate_rows():
            return row.workspace_id
        return ''

    def get_workspace_by_id(self,
                            workspace_id: str
                            ) -> CommonWorkspace | None:
        for row in self._iterate_rows():
            if row.workspace_id == workspace_id:
                return row
        return None

    def update_avatar(self, workspace_id: str) -> None:
        row = self._workspaces[workspace_id]
        row.update_avatar()


class CommonWorkspace(Gtk.ListBoxRow):
    def __init__(self, workspace_id: str) -> None:
        Gtk.ListBoxRow.__init__(self)
        self.workspace_id = workspace_id


class Workspace(CommonWorkspace):
    def __init__(self, workspace_id: str) -> None:
        CommonWorkspace.__init__(self, workspace_id)
        self.get_style_context().add_class('workspace-sidebar-item')

        self._unread_label = Gtk.Label()
        self._unread_label.get_style_context().add_class(
            'unread-counter')
        self._unread_label.set_visible(False)
        self._unread_label.set_halign(Gtk.Align.END)
        self._unread_label.set_valign(Gtk.Align.START)

        self._image = WorkspaceAvatar(workspace_id)
        self._image.set_halign(Gtk.Align.CENTER)

        selection_bar = Gtk.Box()
        selection_bar.set_size_request(6, -1)
        selection_bar.get_style_context().add_class('selection-bar')

        self._popover_menu = GajimPopover(None)

        item_box = Gtk.Box()
        item_box.append(selection_bar)
        item_box.append(self._image)
        item_box.append(self._popover_menu)

        overlay = Gtk.Overlay()
        overlay.set_child(item_box)
        overlay.add_overlay(self._unread_label)

        self._drag_hotspot_x: float = 0
        self._drag_hotspot_y: float = 0

        drag_source = Gtk.DragSource(actions=Gdk.DragAction.MOVE)
        drag_source.connect('prepare', self._on_prepare)
        drag_source.connect('drag-begin', self._on_drag_begin)
        drag_source.connect('drag-end', self._on_drag_end)
        self.add_controller(drag_source)

        controller = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY)
        controller.connect('pressed', self._on_pressed)
        self.add_controller(controller)

        self.set_child(overlay)

    def _on_pressed(
        self,
        _gesture_click: Gtk.GestureClick,
        n_press: int,
        x: float,
        y: float,
    ) -> bool:
        menu = get_workspace_menu(self.workspace_id)
        self._popover_menu.set_menu_model(menu)
        self._popover_menu.set_pointing_to_coord(x=x, y=y)
        self._popover_menu.popup()
        return Gdk.EVENT_STOP

    def update_avatar(self) -> None:
        self._image.update()

    def set_unread_count(self, count: int) -> None:
        if count < 1000:
            self._unread_label.set_text(str(count))
        else:
            self._unread_label.set_text('999+')
        self._unread_label.set_visible(bool(count))

    def _on_prepare(self,
        _drag_source: Gtk.DragSource,
        x: float,
        y: float
    ) -> Gdk.ContentProvider:
        self._drag_hotspot_x = x
        self._drag_hotspot_y = y

        value = GObject.Value()
        value.init(Workspace)
        value.set_object(self)

        return Gdk.ContentProvider.new_for_value(value)

    def _on_drag_begin(self, _drag_source: Gtk.DragSource, drag: Gdk.Drag) -> None:
        texture = app.app.avatar_storage.get_workspace_texture(
            self.workspace_id, AvatarSize.WORKSPACE, 1)
        if texture is not None:
            Gtk.DragIcon.set_from_paintable(
                drag,
                texture,
                int(self._drag_hotspot_x),
                int(self._drag_hotspot_y)
            )

        app.window.highlight_dnd_targets(self, True)

    def _on_drag_end(self,
        _drag_source: Gtk.DragSource,
        _drag: Gdk.Drag,
        _delete_data: bool
    ) -> None:
        app.window.highlight_dnd_targets(self, False)


class AddWorkspace(CommonWorkspace):
    def __init__(self, workspace_id: str) -> None:
        CommonWorkspace.__init__(self, workspace_id)
        self.set_selectable(False)
        self.set_tooltip_text(_('Add Workspace'))
        self.get_style_context().add_class('workspace-add')
        button = Gtk.Button(icon_name='feather-plus-symbolic')
        button.connect('clicked', self._on_add_clicked)
        self.set_child(button)

    @staticmethod
    def _on_add_clicked(_button: Gtk.Button) -> None:
        open_window('WorkspaceDialog')


class WorkspaceAvatar(Gtk.Image):
    def __init__(self, workspace_id: str) -> None:
        Gtk.Image.__init__(self, pixel_size=AvatarSize.WORKSPACE)
        self._workspace_id = workspace_id
        self.get_style_context().add_class('workspace-avatar')
        self.update()

    def update(self) -> None:
        app.app.avatar_storage.invalidate_cache(self._workspace_id)
        scale = self.get_scale_factor()
        texture = app.app.avatar_storage.get_workspace_texture(
            self._workspace_id, AvatarSize.WORKSPACE, scale)
        self.set_from_paintable(texture)
        name = app.settings.get_workspace_setting(self._workspace_id, 'name')
        self.set_tooltip_text(name)
