"""
Image pan mixin for GLWidget - handles panning images within frames
"""

from typing import TYPE_CHECKING, Optional, Tuple


from pyPhotoAlbum.models import ImageData


class ImagePanMixin:
    # Type hints for expected attributes from mixing class
    drag_start_pos: Optional[Tuple[float, float]]
    zoom_level: float
    """
    Mixin providing image panning functionality.

    This mixin handles Control+drag to pan an image within its frame by
    adjusting the crop_info property.
    """

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

        # Image pan state (for panning image within frame with Control key)
        self.image_pan_mode: bool = False  # True when Control+dragging an ImageData element
        self.image_pan_start_crop: Optional[Tuple[float, float, float, float]] = None  # Starting crop_info

    def _handle_image_pan_move(self, x: float, y: float, element: "ImageData"):
        """
        Handle image panning within a frame during mouse move.

        Args:
            x: Current mouse X position in screen coordinates
            y: Current mouse Y position in screen coordinates
            element: The ImageData element being panned
        """
        if not self.image_pan_mode or not isinstance(element, ImageData):
            return

        if not self.drag_start_pos:
            return

        # Calculate mouse movement in screen pixels
        screen_dx = x - self.drag_start_pos[0]
        screen_dy = y - self.drag_start_pos[1]

        # Get element size in page-local coordinates
        elem_w, elem_h = element.size

        # Convert screen movement to normalized crop coordinates
        # Negative because moving mouse right should pan image left (show more of right side)
        # Scale by zoom level and element size
        crop_dx = -screen_dx / (elem_w * self.zoom_level)
        crop_dy = -screen_dy / (elem_h * self.zoom_level)

        # Get starting crop info
        start_crop = self.image_pan_start_crop
        if not start_crop:
            start_crop = (0, 0, 1, 1)

        # Calculate new crop_info
        crop_width = start_crop[2] - start_crop[0]
        crop_height = start_crop[3] - start_crop[1]

        new_x_min = start_crop[0] + crop_dx
        new_y_min = start_crop[1] + crop_dy
        new_x_max = new_x_min + crop_width
        new_y_max = new_y_min + crop_height

        # Clamp to valid range (0-1) to prevent panning beyond image boundaries
        if new_x_min < 0:
            new_x_min = 0
            new_x_max = crop_width
        if new_x_max > 1:
            new_x_max = 1
            new_x_min = 1 - crop_width

        if new_y_min < 0:
            new_y_min = 0
            new_y_max = crop_height
        if new_y_max > 1:
            new_y_max = 1
            new_y_min = 1 - crop_height

        # Update element's crop_info
        element.crop_info = (new_x_min, new_y_min, new_x_max, new_y_max)
