"""
Mouse interaction mixin for GLWidget - coordinates all mouse events
"""

import math
from typing import TYPE_CHECKING, Any, Optional, Set

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QCursor
from pyPhotoAlbum.models import ImageData, BaseLayoutElement

if TYPE_CHECKING:
    from PyQt6.QtWidgets import QMainWindow


class MouseInteractionMixin:
    """
    Mixin providing mouse event handling and coordination.

    This mixin routes mouse events to appropriate other mixins based on
    the current interaction state.
    """

    # Type declarations for attributes provided by other mixins/base classes
    selected_elements: Set[BaseLayoutElement]
    selected_element: Optional[BaseLayoutElement]
    rotation_mode: bool
    rotation_snap_angle: int
    rotation_start_angle: Optional[float]
    pan_offset: list
    zoom_level: float
    image_pan_mode: bool
    current_page_index: int
    _page_renderers: list
    resize_start_pos: Optional[Any]
    resize_start_size: Optional[Any]

    if TYPE_CHECKING:
        def window(self) -> "QMainWindow": ...
        def update(self) -> None: ...
        def setCursor(self, cursor: Any) -> None: ...
        def setFocus(self, *args: Any) -> None: ...
        def _begin_rotate(self, element: Any) -> None: ...
        def _begin_resize(self, element: Any) -> None: ...
        def _begin_image_pan(self, element: Any) -> None: ...
        def _begin_move(self, element: Any) -> None: ...
        def _end_interaction(self) -> None: ...
        def _resize_element(self, dx: float, dy: float) -> None: ...
        def _get_page_at(self, x: float, y: float) -> Any: ...
        def _get_element_at(self, x: float, y: float) -> Optional[BaseLayoutElement]: ...
        def _get_resize_handle_at(self, x: float, y: float) -> Optional[str]: ...
        def _check_ghost_page_click(self, x: float, y: float) -> bool: ...
        def _transfer_element_to_page(self, element: Any, source: Any, target: Any, x: float, y: float, renderer: Any) -> None: ...
        def _handle_image_pan_move(self, x: float, y: float, element: Any) -> None: ...
        def _update_page_status(self, x: float, y: float) -> None: ...

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Mouse interaction state
        self.drag_start_pos = None
        self.drag_start_element_pos = None
        self.is_dragging = False
        self.is_panning = False

    def _handle_rotation_start(self, x: float, y: float):
        """Start rotation interaction for selected element."""
        assert self.selected_element is not None
        self._begin_rotate(self.selected_element)
        self.drag_start_pos = (x, y)
        self.rotation_start_angle = self.selected_element.rotation
        self.is_dragging = True

    def _handle_resize_start(self, x: float, y: float, handle):
        """Start resize interaction for selected element."""
        assert self.selected_element is not None
        self._begin_resize(self.selected_element)
        self.resize_handle = handle
        self.drag_start_pos = (x, y)
        self.resize_start_pos = self.selected_element.position
        self.resize_start_size = self.selected_element.size
        self.is_dragging = True

    def _handle_image_pan_start(self, x: float, y: float, element):
        """Start image pan mode for an ImageData element."""
        self.selected_elements = {element}
        self.drag_start_pos = (x, y)
        self.image_pan_mode = True
        self.image_pan_start_crop = element.crop_info
        self._begin_image_pan(element)
        self.is_dragging = True
        self.setCursor(Qt.CursorShape.SizeAllCursor)

    def _handle_multi_select(self, element):
        """Toggle element in multi-selection."""
        if element in self.selected_elements:
            self.selected_elements.remove(element)
        else:
            self.selected_elements.add(element)

    def _handle_element_drag_start(self, x: float, y: float, element):
        """Start dragging an element."""
        self.selected_elements = {element}
        self.drag_start_pos = (x, y)
        self.drag_start_element_pos = element.position
        if not self.rotation_mode:
            self._begin_move(element)
            self.is_dragging = True

    def mousePressEvent(self, event):
        """Handle mouse press events"""
        self.setFocus()

        if event.button() == Qt.MouseButton.LeftButton:
            self._handle_left_click(event)
        elif event.button() == Qt.MouseButton.MiddleButton:
            self.is_panning = True
            self.drag_start_pos = (event.position().x(), event.position().y())
            self.setCursor(Qt.CursorShape.ClosedHandCursor)

    def _handle_left_click(self, event):
        """Handle left mouse button click."""
        x, y = event.position().x(), event.position().y()
        ctrl_pressed = event.modifiers() & Qt.KeyboardModifier.ControlModifier
        shift_pressed = event.modifiers() & Qt.KeyboardModifier.ShiftModifier

        # Check if clicking on ghost page button
        if self._check_ghost_page_click(x, y):
            return

        # Update current_page_index based on where user clicked
        page, page_index, renderer = self._get_page_at(x, y)
        if page_index >= 0:
            self.current_page_index = page_index

        # Handle interaction with already-selected element
        if len(self.selected_elements) == 1 and self.selected_element:
            if self.rotation_mode:
                self._handle_rotation_start(x, y)
                return
            else:
                handle = self._get_resize_handle_at(x, y)
                if handle:
                    self._handle_resize_start(x, y, handle)
                    return

        # Handle click on element
        element = self._get_element_at(x, y)
        if element:
            if ctrl_pressed and isinstance(element, ImageData) and not self.rotation_mode:
                self._handle_image_pan_start(x, y, element)
            elif ctrl_pressed or shift_pressed:
                self._handle_multi_select(element)
            else:
                self._handle_element_drag_start(x, y, element)
        else:
            if not ctrl_pressed:
                self.selected_elements.clear()

        self.update()

    def _handle_canvas_pan(self, x: float, y: float):
        """Handle canvas panning with middle mouse button."""
        dx = x - self.drag_start_pos[0]
        dy = y - self.drag_start_pos[1]

        self.pan_offset[0] += dx
        self.pan_offset[1] += dy

        if hasattr(self, "clamp_pan_offset"):
            self.clamp_pan_offset()

        self.drag_start_pos = (x, y)
        self.update()

        main_window = self.window()
        if hasattr(main_window, "update_scrollbars"):
            main_window.update_scrollbars()

    def _handle_rotation_move(self, x: float, y: float):
        """Handle element rotation during drag."""
        if self.selected_element is None or not hasattr(self.selected_element, "_page_renderer"):
            return

        renderer = self.selected_element._page_renderer  # type: ignore[attr-defined]
        elem_x, elem_y = self.selected_element.position
        elem_w, elem_h = self.selected_element.size

        center_page_x = elem_x + elem_w / 2
        center_page_y = elem_y + elem_h / 2
        screen_center_x, screen_center_y = renderer.page_to_screen(center_page_x, center_page_y)

        dx = x - screen_center_x
        dy = y - screen_center_y
        angle = math.degrees(math.atan2(dy, dx))
        angle = round(angle / self.rotation_snap_angle) * self.rotation_snap_angle
        angle = angle % 360

        self.selected_element.rotation = angle

        main_window = self.window()
        if hasattr(main_window, "show_status"):
            main_window.show_status(f"Rotation: {angle:.1f}°", 100)

    def _handle_resize_move(self, x: float, y: float):
        """Handle element resize during drag."""
        screen_dx = x - self.drag_start_pos[0]
        screen_dy = y - self.drag_start_pos[1]

        total_dx = screen_dx / self.zoom_level
        total_dy = screen_dy / self.zoom_level

        self._resize_element(total_dx, total_dy)

    def _handle_element_move(self, x: float, y: float):
        """Handle element movement during drag, including page transfer."""
        assert self.selected_element is not None
        current_page, current_page_index, current_renderer = self._get_page_at(x, y)

        if current_page and hasattr(self.selected_element, "_parent_page"):
            source_page = self.selected_element._parent_page  # type: ignore[attr-defined]

            if current_page is not source_page:
                self._transfer_element_to_page(self.selected_element, source_page, current_page, x, y, current_renderer)
            else:
                self._move_element_within_page(x, y, source_page)
        else:
            # No page context - simple move without snapping
            total_dx = (x - self.drag_start_pos[0]) / self.zoom_level
            total_dy = (y - self.drag_start_pos[1]) / self.zoom_level

            new_x = self.drag_start_element_pos[0] + total_dx
            new_y = self.drag_start_element_pos[1] + total_dy

            self.selected_element.position = (new_x, new_y)

    def _move_element_within_page(self, x: float, y: float, page):
        """Move element within its current page with snapping."""
        total_dx = (x - self.drag_start_pos[0]) / self.zoom_level
        total_dy = (y - self.drag_start_pos[1]) / self.zoom_level

        new_x = self.drag_start_element_pos[0] + total_dx
        new_y = self.drag_start_element_pos[1] + total_dy

        assert self.selected_element is not None
        main_window = self.window()
        snap_sys = page.layout.snapping_system
        page_size = page.layout.size
        dpi = main_window.project.working_dpi  # type: ignore[attr-defined]

        snapped_pos = snap_sys.snap_position(
            position=(new_x, new_y),
            size=self.selected_element.size,
            page_size=page_size,
            dpi=dpi,
            project=main_window.project,  # type: ignore[attr-defined]
        )

        self.selected_element.position = snapped_pos

    def mouseMoveEvent(self, event):
        """Handle mouse move events"""
        x, y = event.position().x(), event.position().y()

        self._update_page_status(x, y)

        # Canvas panning (middle mouse button)
        if self.is_panning and self.drag_start_pos:
            self._handle_canvas_pan(x, y)
            return

        if not self.is_dragging or not self.drag_start_pos:
            return

        if not self.selected_element:
            return

        # Dispatch to appropriate handler based on interaction mode
        if self.image_pan_mode:
            self._handle_image_pan_move(x, y, self.selected_element)
        elif self.rotation_mode:
            self._handle_rotation_move(x, y)
        elif self.resize_handle:
            self._handle_resize_move(x, y)
        else:
            self._handle_element_move(x, y)

        self.update()

    def mouseReleaseEvent(self, event):
        """Handle mouse release events"""
        if event.button() == Qt.MouseButton.LeftButton:
            self._end_interaction()

            self.is_dragging = False
            self.drag_start_pos = None
            self.drag_start_element_pos = None
            self.resize_handle = None
            self.rotation_start_angle = None
            self.image_pan_mode = False
            self.image_pan_start_crop = None
            self.snap_state = {"is_snapped": False, "last_position": None, "last_size": None}
            self.setCursor(Qt.CursorShape.ArrowCursor)

        elif event.button() == Qt.MouseButton.MiddleButton:
            self.is_panning = False
            self.drag_start_pos = None
            self.setCursor(Qt.CursorShape.ArrowCursor)

    def mouseDoubleClickEvent(self, event):
        """Handle mouse double-click events"""
        if event.button() == Qt.MouseButton.LeftButton:
            x, y = event.position().x(), event.position().y()
            element = self._get_element_at(x, y)

            from pyPhotoAlbum.models import TextBoxData

            if isinstance(element, TextBoxData):
                self._edit_text_element(element)
                return

        super().mouseDoubleClickEvent(event)

    def wheelEvent(self, event):
        """Handle mouse wheel events for scrolling or zooming (with Ctrl)"""
        delta = event.angleDelta().y()
        ctrl_pressed = event.modifiers() & Qt.KeyboardModifier.ControlModifier

        if ctrl_pressed:
            # Ctrl + Wheel: Zoom centered on mouse position
            mouse_x = event.position().x()
            mouse_y = event.position().y()

            world_x = (mouse_x - self.pan_offset[0]) / self.zoom_level
            world_y = (mouse_y - self.pan_offset[1]) / self.zoom_level

            zoom_factor = 1.1 if delta > 0 else 0.9
            new_zoom = self.zoom_level * zoom_factor

            if 0.1 <= new_zoom <= 5.0:
                old_zoom = self.zoom_level
                old_pan_x = self.pan_offset[0]
                old_pan_y = self.pan_offset[1]

                self.zoom_level = new_zoom

                self.pan_offset[0] = mouse_x - world_x * self.zoom_level
                self.pan_offset[1] = mouse_y - world_y * self.zoom_level

                # If dragging, adjust drag_start_pos to account for pan_offset change
                if self.is_dragging and self.drag_start_pos:
                    pan_delta_x = self.pan_offset[0] - old_pan_x
                    pan_delta_y = self.pan_offset[1] - old_pan_y
                    self.drag_start_pos = (self.drag_start_pos[0] + pan_delta_x, self.drag_start_pos[1] + pan_delta_y)

                # Clamp pan offset to content bounds
                if hasattr(self, "clamp_pan_offset"):
                    self.clamp_pan_offset()

                self.update()

                main_window = self.window()
                if hasattr(main_window, "status_bar"):
                    main_window.status_bar.showMessage(f"Zoom: {int(self.zoom_level * 100)}%", 2000)

                # Update scrollbars if available
                if hasattr(main_window, "update_scrollbars"):
                    main_window.update_scrollbars()
        else:
            # Regular wheel: Two-finger scroll (vertical and horizontal)
            delta_x = event.angleDelta().x()
            delta_y = event.angleDelta().y()

            scroll_amount_x = delta_x * 0.5
            scroll_amount_y = delta_y * 0.5

            old_pan_x = self.pan_offset[0]
            old_pan_y = self.pan_offset[1]

            self.pan_offset[0] += scroll_amount_x
            self.pan_offset[1] += scroll_amount_y

            # Clamp pan offset to content bounds
            if hasattr(self, "clamp_pan_offset"):
                self.clamp_pan_offset()

            # If dragging, adjust drag_start_pos to account for pan_offset change
            if self.is_dragging and self.drag_start_pos:
                pan_delta_x = self.pan_offset[0] - old_pan_x
                pan_delta_y = self.pan_offset[1] - old_pan_y
                self.drag_start_pos = (self.drag_start_pos[0] + pan_delta_x, self.drag_start_pos[1] + pan_delta_y)

            self.update()

            # Update scrollbars if available
            main_window = self.window()
            if hasattr(main_window, "update_scrollbars"):
                main_window.update_scrollbars()

    def _edit_text_element(self, text_element):
        """Open dialog to edit text element"""
        from pyPhotoAlbum.text_edit_dialog import TextEditDialog

        dialog = TextEditDialog(text_element, self)
        if dialog.exec() == TextEditDialog.DialogCode.Accepted:
            values = dialog.get_values()

            text_element.text_content = values["text_content"]
            text_element.font_settings = values["font_settings"]
            text_element.alignment = values["alignment"]

            self.update()

            print(f"Updated text element: {values['text_content'][:50]}...")
