"""
OpenGL widget for pyPhotoAlbum rendering - refactored with mixins
"""

from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtCore import Qt
from pyPhotoAlbum.gl_imports import *

# Import all mixins
from pyPhotoAlbum.mixins.viewport import ViewportMixin
from pyPhotoAlbum.mixins.rendering import RenderingMixin
from pyPhotoAlbum.mixins.asset_path import AssetPathMixin
from pyPhotoAlbum.mixins.asset_drop import AssetDropMixin
from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin
from pyPhotoAlbum.mixins.image_pan import ImagePanMixin
from pyPhotoAlbum.mixins.element_manipulation import ElementManipulationMixin
from pyPhotoAlbum.mixins.element_selection import ElementSelectionMixin
from pyPhotoAlbum.mixins.mouse_interaction import MouseInteractionMixin
from pyPhotoAlbum.mixins.interaction_undo import UndoableInteractionMixin
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
from pyPhotoAlbum.mixins.keyboard_navigation import KeyboardNavigationMixin


class GLWidget(
    AsyncLoadingMixin,
    ViewportMixin,
    RenderingMixin,
    AssetPathMixin,
    AssetDropMixin,
    PageNavigationMixin,
    ImagePanMixin,
    ElementManipulationMixin,
    ElementSelectionMixin,
    MouseInteractionMixin,
    UndoableInteractionMixin,
    KeyboardNavigationMixin,
    QOpenGLWidget,
):
    """OpenGL widget for pyPhotoAlbum rendering and user interaction

    This widget orchestrates multiple mixins to provide:
    - Async image loading (non-blocking)
    - Viewport control (zoom, pan)
    - Page rendering (OpenGL)
    - Element selection and manipulation
    - Mouse interaction handling
    - Drag-and-drop asset management
    - Image panning within frames
    - Page navigation and ghost pages
    - Undo/redo integration
    """

    def __init__(self, parent=None):
        super().__init__(parent)

        # Store reference to main window for accessing project
        self._main_window = parent

        # Initialize async loading system
        self._init_async_loading()

        # Set up OpenGL surface format with explicit double buffering
        from PyQt6.QtGui import QSurfaceFormat

        fmt = QSurfaceFormat()
        fmt.setSwapBehavior(QSurfaceFormat.SwapBehavior.DoubleBuffer)
        fmt.setSwapInterval(1)  # Enable vsync
        self.setFormat(fmt)

        # Force full redraws to ensure viewport updates
        self.setUpdateBehavior(QOpenGLWidget.UpdateBehavior.NoPartialUpdate)

        # Enable mouse tracking and drag-drop
        self.setMouseTracking(True)
        self.setAcceptDrops(True)

        # Enable keyboard focus
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.setFocus()

        # Enable gesture support for pinch-to-zoom
        self.grabGesture(Qt.GestureType.PinchGesture)

        # Track pinch gesture state
        self._pinch_scale_factor = 1.0

    def window(self):
        """Override window() to return stored main_window reference.

        This fixes the Qt widget hierarchy issue where window() returns None
        because the GL widget is nested in container widgets.
        """
        return self._main_window if hasattr(self, "_main_window") else super().window()

    def update(self):
        """Override update to force immediate repaint"""
        super().update()
        # Force immediate processing of paint events
        self.repaint()

    def closeEvent(self, event):
        """Handle widget close event."""
        # Cleanup async loading
        self._cleanup_async_loading()
        super().closeEvent(event)

    def _get_project_folder(self):
        """Override AssetPathMixin to access project via main window."""
        main_window = self.window()
        if hasattr(main_window, "project") and main_window.project:
            return getattr(main_window.project, "folder_path", None)
        return None

    def keyPressEvent(self, event):
        """Handle key press events"""
        if event.key() == Qt.Key.Key_Delete or event.key() == Qt.Key.Key_Backspace:
            if self.selected_element:
                main_window = self.window()
                if hasattr(main_window, "delete_selected_element"):
                    main_window.delete_selected_element()

        elif event.key() == Qt.Key.Key_Escape:
            self.selected_element = None
            self.rotation_mode = False
            self.update()

        elif event.key() == Qt.Key.Key_Tab:
            # Toggle rotation mode when an element is selected
            if self.selected_element:
                self.rotation_mode = not self.rotation_mode
                main_window = self.window()
                if hasattr(main_window, "show_status"):
                    mode_text = "Rotation Mode" if self.rotation_mode else "Move/Resize Mode"
                    main_window.show_status(f"Switched to {mode_text}", 2000)
                print(f"Rotation mode: {self.rotation_mode}")
                self.update()
                event.accept()
            else:
                super().keyPressEvent(event)

        elif event.key() == Qt.Key.Key_PageDown:
            # Navigate to next page
            self._navigate_to_next_page()
            event.accept()

        elif event.key() == Qt.Key.Key_PageUp:
            # Navigate to previous page
            self._navigate_to_previous_page()
            event.accept()

        elif event.key() in (Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left, Qt.Key.Key_Right):
            # Arrow key handling
            if self.selected_elements:
                # Move selected elements
                self._move_selected_elements_with_arrow_keys(event.key())
                event.accept()
            else:
                # Move viewport
                self._move_viewport_with_arrow_keys(event.key())
                event.accept()

        else:
            super().keyPressEvent(event)

    def event(self, event):
        """Handle gesture events for pinch-to-zoom"""
        from PyQt6.QtCore import QEvent, Qt as QtCore
        from PyQt6.QtWidgets import QPinchGesture
        from PyQt6.QtGui import QNativeGestureEvent

        # Handle native touchpad gestures (Linux, macOS)
        if event.type() == QEvent.Type.NativeGesture:
            native_event = event
            gesture_type = native_event.gestureType()

            print(f"DEBUG: Native gesture detected - type: {gesture_type}")

            # Check for zoom/pinch gesture
            if gesture_type == QtCore.NativeGestureType.ZoomNativeGesture:
                # Get zoom value (typically a delta around 0)
                value = native_event.value()
                print(f"DEBUG: Zoom value: {value}")

                # Convert to scale factor (value is typically small, like -0.1 to 0.1)
                # Positive value = zoom in, negative = zoom out
                scale_factor = 1.0 + value

                # Get the position of the gesture
                pos = native_event.position()
                mouse_x = pos.x()
                mouse_y = pos.y()

                self._apply_zoom_at_point(mouse_x, mouse_y, scale_factor)
                return True

            # Check for pan gesture (two-finger drag)
            elif gesture_type == QtCore.NativeGestureType.PanNativeGesture:
                # Get the pan delta
                delta = native_event.delta()
                dx = delta.x()
                dy = delta.y()

                print(f"DEBUG: Pan delta: dx={dx}, dy={dy}")

                # Apply pan
                self.pan_offset[0] += dx
                self.pan_offset[1] += dy

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

                self.update()

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

                return True

        # Handle Qt gesture events (fallback for other platforms)
        elif event.type() == QEvent.Type.Gesture:
            print("DEBUG: Qt Gesture event detected")
            gesture_event = event
            pinch = gesture_event.gesture(Qt.GestureType.PinchGesture)

            if pinch:
                print(f"DEBUG: Pinch gesture detected - state: {pinch.state()}, scale: {pinch.totalScaleFactor()}")
                self._handle_pinch_gesture(pinch)
                return True

        return super().event(event)

    def _handle_pinch_gesture(self, pinch):
        """Handle pinch gesture for zooming"""
        from PyQt6.QtCore import Qt as QtCore

        # Check gesture state
        state = pinch.state()

        if state == QtCore.GestureState.GestureStarted:
            # Reset scale factor at gesture start
            self._pinch_scale_factor = 1.0
            return

        elif state == QtCore.GestureState.GestureUpdated:
            # Get current total scale factor
            current_scale = pinch.totalScaleFactor()

            # Calculate incremental change from last update
            if current_scale > 0:
                scale_change = current_scale / self._pinch_scale_factor
                self._pinch_scale_factor = current_scale

                # Get the center point of the pinch gesture
                center_point = pinch.centerPoint()
                mouse_x = center_point.x()
                mouse_y = center_point.y()

                # Calculate world coordinates at the pinch center
                world_x = (mouse_x - self.pan_offset[0]) / self.zoom_level
                world_y = (mouse_y - self.pan_offset[1]) / self.zoom_level

                # Apply incremental zoom change
                new_zoom = self.zoom_level * scale_change

                # Clamp zoom level to reasonable bounds
                if 0.1 <= new_zoom <= 5.0:
                    old_pan_x = self.pan_offset[0]
                    old_pan_y = self.pan_offset[1]

                    self.zoom_level = new_zoom

                    # Adjust pan offset to keep the pinch center point fixed
                    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 (
                        hasattr(self, "is_dragging")
                        and self.is_dragging
                        and hasattr(self, "drag_start_pos")
                        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()

                    # Update status bar
                    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()

        elif state == QtCore.GestureState.GestureFinished or state == QtCore.GestureState.GestureCanceled:
            # Reset on gesture end
            self._pinch_scale_factor = 1.0

    def _apply_zoom_at_point(self, mouse_x, mouse_y, scale_factor):
        """Apply zoom centered at a specific point"""
        # Calculate world coordinates at the zoom center
        world_x = (mouse_x - self.pan_offset[0]) / self.zoom_level
        world_y = (mouse_y - self.pan_offset[1]) / self.zoom_level

        # Apply zoom
        new_zoom = self.zoom_level * scale_factor

        # Clamp zoom level to reasonable bounds
        if 0.1 <= new_zoom <= 5.0:
            old_pan_x = self.pan_offset[0]
            old_pan_y = self.pan_offset[1]

            self.zoom_level = new_zoom

            # Adjust pan offset to keep the zoom center point fixed
            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 (
                hasattr(self, "is_dragging")
                and self.is_dragging
                and hasattr(self, "drag_start_pos")
                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()

            # Update status bar
            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()
