"""
Viewport mixin for GLWidget - handles zoom and pan
"""

from pyPhotoAlbum.gl_imports import *


class ViewportMixin:
    """
    Mixin providing viewport zoom and pan functionality.

    This mixin manages the zoom level and pan offset for the OpenGL canvas,
    including fit-to-screen calculations and OpenGL initialization.
    """

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

        # Zoom and pan state
        self.zoom_level = 1.0
        self.pan_offset = [0, 0]
        self.initial_zoom_set = False  # Track if we've set initial fit-to-screen zoom

        # Track previous viewport size to detect scrollbar-induced resizes
        self._last_viewport_size = (0, 0)

    def initializeGL(self):
        """Initialize OpenGL resources"""
        glClearColor(1.0, 1.0, 1.0, 1.0)
        glEnable(GL_DEPTH_TEST)

    def resizeGL(self, w, h):
        """Handle window resizing"""
        glViewport(0, 0, w, h)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, w, h, 0, -1, 1)
        glMatrixMode(GL_MODELVIEW)

        # Detect if this is a small resize (likely scrollbar visibility change)
        # Scrollbars are typically 14-20 pixels wide
        last_w, last_h = self._last_viewport_size
        width_change = abs(w - last_w)
        height_change = abs(h - last_h)
        is_small_resize = width_change <= 20 and height_change <= 20
        is_first_resize = last_w == 0 and last_h == 0

        # Recalculate centering if we have a project loaded
        # Recenter on:
        # 1. First resize (initial setup)
        # 2. Large resizes (window resize, NOT scrollbar changes)
        # Don't recenter on small resizes (scrollbar visibility changes during zoom)
        if self.initial_zoom_set and (is_first_resize or not is_small_resize):
            # Maintain current zoom level, just recenter
            self.pan_offset = self._calculate_center_pan_offset(self.zoom_level)

        # Update tracked viewport size
        self._last_viewport_size = (w, h)

        self.update()

        # Update scrollbars when viewport size changes
        main_window = self.window()
        if hasattr(main_window, "update_scrollbars"):
            main_window.update_scrollbars()

    def _calculate_fit_to_screen_zoom(self):
        """
        Calculate zoom level to fit first page to screen.

        Returns:
            float: Zoom level (1.0 = 100%, 0.5 = 50%, etc.)
        """
        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return 1.0

        window_width = self.width()
        window_height = self.height()

        # Get first page dimensions in mm
        first_page = main_window.project.pages[0]
        page_width_mm, page_height_mm = first_page.layout.size

        # Convert to pixels
        dpi = main_window.project.working_dpi
        page_width_px = page_width_mm * dpi / 25.4
        page_height_px = page_height_mm * dpi / 25.4

        # Calculate zoom to fit with margins
        margin = 100  # pixels
        zoom_w = (window_width - margin * 2) / page_width_px
        zoom_h = (window_height - margin * 2) / page_height_px

        # Use the smaller zoom to ensure entire page fits
        return min(zoom_w, zoom_h, 1.0)  # Don't zoom in beyond 100%

    def _calculate_center_pan_offset(self, zoom_level):
        """
        Calculate pan offset to center the first page in the viewport.

        Args:
            zoom_level: The current zoom level to use for calculations

        Returns:
            list: [x_offset, y_offset] to center the page
        """
        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return [0, 0]

        window_width = self.width()
        window_height = self.height()

        # Get first page dimensions in mm
        first_page = main_window.project.pages[0]
        page_width_mm, page_height_mm = first_page.layout.size

        # Convert to pixels
        dpi = main_window.project.working_dpi
        page_width_px = page_width_mm * dpi / 25.4
        page_height_px = page_height_mm * dpi / 25.4

        # Apply zoom to get screen dimensions
        screen_page_width = page_width_px * zoom_level
        screen_page_height = page_height_px * zoom_level

        # Calculate offsets to center the page
        # PAGE_MARGIN from rendering.py is 50
        PAGE_MARGIN = 50
        x_offset = (window_width - screen_page_width) / 2 - PAGE_MARGIN
        y_offset = (window_height - screen_page_height) / 2

        return [x_offset, y_offset]

    def get_content_bounds(self):
        """
        Calculate the total bounds of all content (pages).

        Returns:
            dict: {'min_x', 'max_x', 'min_y', 'max_y', 'width', 'height'} in pixels
        """
        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return {"min_x": 0, "max_x": 800, "min_y": 0, "max_y": 600, "width": 800, "height": 600}

        dpi = main_window.project.working_dpi
        PAGE_MARGIN = 50
        PAGE_SPACING = 50

        # Calculate total dimensions
        total_height = PAGE_MARGIN
        max_width = 0

        for page in main_window.project.pages:
            page_width_mm, page_height_mm = page.layout.size
            page_width_px = page_width_mm * dpi / 25.4
            page_height_px = page_height_mm * dpi / 25.4

            screen_page_width = page_width_px * self.zoom_level
            screen_page_height = page_height_px * self.zoom_level

            total_height += screen_page_height + PAGE_SPACING
            max_width = max(max_width, screen_page_width)

        total_width = max_width + PAGE_MARGIN * 2
        total_height += PAGE_MARGIN

        return {
            "min_x": 0,
            "max_x": total_width,
            "min_y": 0,
            "max_y": total_height,
            "width": total_width,
            "height": total_height,
        }

    def _clamp_vertical_pan(self, viewport_height: float) -> float:
        """Clamp vertical pan offset and return the original value before clamping."""
        bounds = self.get_content_bounds()
        content_height = bounds["height"]

        # Save original for page selection (prevents clamping from changing which page we target)
        original_pan_y: float = self.pan_offset[1]

        if content_height > viewport_height:
            max_pan_up = 0  # Can't pan beyond top edge
            min_pan_up = -(content_height - viewport_height)  # Can't pan beyond bottom edge
            self.pan_offset[1] = max(min_pan_up, min(max_pan_up, self.pan_offset[1]))

        return original_pan_y

    def _build_page_centerlines(self, pages, dpi: float) -> list:
        """Build list of (center_y, center_x, width) tuples for each page."""
        PAGE_MARGIN = 50
        PAGE_SPACING = 50

        centerlines = []
        current_y = PAGE_MARGIN

        for page in pages:
            page_width_mm, page_height_mm = page.layout.size
            screen_page_width = page_width_mm * dpi / 25.4 * self.zoom_level
            screen_page_height = page_height_mm * dpi / 25.4 * self.zoom_level

            page_center_y = current_y + screen_page_height / 2
            page_center_x = PAGE_MARGIN + screen_page_width / 2

            centerlines.append((page_center_y, page_center_x, screen_page_width))
            current_y += screen_page_height + PAGE_SPACING

        return centerlines

    def _interpolate_target_centerline(self, centerlines: list, viewport_center_y: float) -> tuple:
        """Find target centerline by interpolating between pages based on viewport position."""
        if not centerlines:
            return 0, 0

        # Find the page index we're at or past
        page_idx = self._find_page_at_viewport_y(centerlines, viewport_center_y)

        if page_idx == 0:
            return centerlines[0][1], centerlines[0][2]

        # Interpolate between previous and current page
        prev_y, prev_x, prev_w = centerlines[page_idx - 1]
        curr_y, curr_x, curr_w = centerlines[page_idx]

        if curr_y == prev_y:
            return curr_x, curr_w

        t = max(0, min(1, (viewport_center_y - prev_y) / (curr_y - prev_y)))
        return prev_x + t * (curr_x - prev_x), prev_w + t * (curr_w - prev_w)

    def _find_page_at_viewport_y(self, centerlines: list, viewport_center_y: float) -> int:
        """Find index of page at or after viewport Y position."""
        for i, (page_y, _, _) in enumerate(centerlines):
            if viewport_center_y <= page_y:
                return i
        return len(centerlines) - 1  # Below all pages - use last

    def _clamp_horizontal_pan(self, viewport_width: float, target_centerline_x: float, target_page_width: float):
        """Clamp horizontal pan to keep viewport centered on target page."""
        ideal_pan_x = viewport_width / 2 - target_centerline_x

        if target_page_width > viewport_width:
            max_deviation = (target_page_width / 2) + (viewport_width / 4)
        else:
            max_deviation = 100  # Small margin to avoid jitter

        min_pan_x = ideal_pan_x - max_deviation
        max_pan_x = ideal_pan_x + max_deviation

        self.pan_offset[0] = max(min_pan_x, min(max_pan_x, self.pan_offset[0]))

    def clamp_pan_offset(self):
        """
        Clamp pan offset to prevent scrolling beyond content bounds.

        Pan offset semantics:
        - Positive pan_offset = content moved right/down (viewing top-left)
        - Negative pan_offset = content moved left/up (viewing bottom-right)

        For horizontal clamping, we use a centerline-based approach that interpolates
        between page centers based on vertical scroll position. This prevents jumps
        when zooming on pages of different widths.
        """
        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return

        viewport_width = self.width()
        viewport_height = self.height()

        # Vertical clamping (returns original pan_y for page selection)
        original_pan_y = self._clamp_vertical_pan(viewport_height)

        # Build page centerline data
        dpi = main_window.project.working_dpi
        centerlines = self._build_page_centerlines(main_window.project.pages, dpi)
        if not centerlines:
            return

        # Find target centerline by interpolating based on viewport position
        viewport_center_y = -original_pan_y + viewport_height / 2
        target_x, target_width = self._interpolate_target_centerline(centerlines, viewport_center_y)

        # Horizontal clamping
        self._clamp_horizontal_pan(viewport_width, target_x, target_width)
