"""
Page layout and template system for pyPhotoAlbum
"""

from typing import List, Dict, Any, Optional, Tuple, TYPE_CHECKING

if TYPE_CHECKING:
    from PyQt6.QtWidgets import QWidget

from pyPhotoAlbum.models import BaseLayoutElement, ImageData, PlaceholderData, TextBoxData
from pyPhotoAlbum.snapping import SnappingSystem
from pyPhotoAlbum.gl_imports import (
    glBegin,
    glEnd,
    glVertex2f,
    glColor3f,
    glColor4f,
    GL_QUADS,
    GL_LINE_LOOP,
    GL_LINES,
    glLineWidth,
    glEnable,
    glDisable,
    GL_DEPTH_TEST,
    GL_BLEND,
    glBlendFunc,
    GL_SRC_ALPHA,
    GL_ONE_MINUS_SRC_ALPHA,
)


class PageLayout:
    """Class to manage page layout and templates"""

    def __init__(self, width: float = 210, height: float = 297, is_facing_page: bool = False):
        """
        Initialize page layout.

        Args:
            width: Width in mm (doubled automatically if is_facing_page=True)
            height: Height in mm
            is_facing_page: If True, width is doubled for facing page spread
        """
        self.base_width = width  # Store the base single-page width
        self.is_facing_page = is_facing_page
        self.size = (width * 2 if is_facing_page else width, height)
        self.elements: List[BaseLayoutElement] = []
        self.grid_layout: Optional[GridLayout] = None
        self.background_color = (1.0, 1.0, 1.0)  # White background
        self.snapping_system = SnappingSystem()
        self.show_snap_lines = True  # Show snap lines while dragging
        self._parent_widget: Optional["QWidget"] = None  # Set by renderer

    def add_element(self, element: BaseLayoutElement):
        """Add a layout element to the page"""
        if element not in self.elements:
            self.elements.append(element)

    def remove_element(self, element: BaseLayoutElement):
        """Remove a layout element from the page"""
        self.elements.remove(element)

    def set_grid_layout(self, grid: "GridLayout"):
        """Set a grid layout for the page"""
        self.grid_layout = grid

    def render(self, dpi: int = 300, project=None):
        """
        Render all elements on the page in page-local coordinates.

        Note: This method assumes OpenGL transformations have already been set up
        by PageRenderer.begin_render(). All coordinates here are in page-local space.

        Args:
            dpi: Working DPI for converting mm to pixels
            project: Optional project instance for global snapping settings
        """
        # Disable depth testing for 2D rendering
        glDisable(GL_DEPTH_TEST)

        # Convert size from mm to pixels based on DPI
        width_px = self.size[0] * dpi / 25.4
        height_px = self.size[1] * dpi / 25.4

        # All rendering is at page origin (0, 0) in page-local coordinates
        page_x = 0
        page_y = 0

        # Draw drop shadow FIRST (behind everything)
        shadow_offset = 5
        glColor3f(0.5, 0.5, 0.5)
        glBegin(GL_QUADS)
        glVertex2f(page_x + shadow_offset, page_y + height_px)
        glVertex2f(page_x + width_px + shadow_offset, page_y + height_px)
        glVertex2f(page_x + width_px + shadow_offset, page_y + height_px + shadow_offset)
        glVertex2f(page_x + shadow_offset, page_y + height_px + shadow_offset)
        glEnd()

        glBegin(GL_QUADS)
        glVertex2f(page_x + width_px, page_y + shadow_offset)
        glVertex2f(page_x + width_px + shadow_offset, page_y + shadow_offset)
        glVertex2f(page_x + width_px + shadow_offset, page_y + height_px)
        glVertex2f(page_x + width_px, page_y + height_px)
        glEnd()

        # Draw page background (slightly off-white to distinguish from canvas)
        glColor3f(0.98, 0.98, 0.98)
        glBegin(GL_QUADS)
        glVertex2f(page_x, page_y)
        glVertex2f(page_x + width_px, page_y)
        glVertex2f(page_x + width_px, page_y + height_px)
        glVertex2f(page_x, page_y + height_px)
        glEnd()

        # Render elements in list order (list position = z-order)
        # For ImageData elements, request async loading if available
        for element in self.elements:
            # Check if this is an ImageData element that needs async loading
            if isinstance(element, ImageData) and not hasattr(element, "_texture_id"):
                # Try to get async loader from a parent widget
                if hasattr(self, "_async_loader"):
                    loader = self._async_loader
                elif hasattr(self, "_parent_widget") and hasattr(self._parent_widget, "async_image_loader"):
                    loader = self._parent_widget.async_image_loader  # type: ignore[union-attr]
                else:
                    loader = None

                # Request async load if loader is available and not already requested
                if loader and not element._async_load_requested:
                    from pyPhotoAlbum.async_backend import LoadPriority

                    # Determine priority based on visibility (HIGH for now, can be refined)
                    if hasattr(self._parent_widget, "request_image_load"):
                        self._parent_widget.request_image_load(element, priority=LoadPriority.HIGH)  # type: ignore[union-attr]
                    element._async_load_requested = True
                    element._async_loading = True

            element.render()

        # Draw page border LAST (on top of everything)
        glColor3f(0.7, 0.7, 0.7)
        glLineWidth(2.0)
        glBegin(GL_LINE_LOOP)
        glVertex2f(page_x, page_y)
        glVertex2f(page_x + width_px, page_y)
        glVertex2f(page_x + width_px, page_y + height_px)
        glVertex2f(page_x, page_y + height_px)
        glEnd()
        glLineWidth(1.0)

        # Draw center line for facing pages
        if self.is_facing_page:
            center_x = page_x + (width_px / 2)
            glColor3f(0.5, 0.5, 0.5)  # Gray line
            glLineWidth(1.5)
            glBegin(GL_LINES)
            glVertex2f(center_x, page_y)
            glVertex2f(center_x, page_y + height_px)
            glEnd()
            glLineWidth(1.0)

        # Always render snap lines (grid shows when show_grid is on, guides show when show_snap_lines is on)
        self._render_snap_lines(dpi, page_x, page_y, project)

        # Re-enable depth testing
        glEnable(GL_DEPTH_TEST)

    def _render_snap_lines(self, dpi: int, page_x: float, page_y: float, project=None):
        """Render snap lines (grid, edges, guides)"""
        # Use project settings if available, otherwise fall back to local snapping_system
        if project:
            # Use project-level global settings
            snap_to_grid = project.snap_to_grid
            snap_to_edges = project.snap_to_edges
            snap_to_guides = project.snap_to_guides
            grid_size_mm = project.grid_size_mm
            snap_threshold_mm = project.snap_threshold_mm
            show_grid = project.show_grid
            show_snap_lines = project.show_snap_lines
        else:
            # Fall back to per-page settings (backward compatibility)
            snap_to_grid = self.snapping_system.snap_to_grid
            snap_to_edges = self.snapping_system.snap_to_edges
            snap_to_guides = self.snapping_system.snap_to_guides
            grid_size_mm = self.snapping_system.grid_size_mm
            snap_threshold_mm = self.snapping_system.snap_threshold_mm
            show_grid = snap_to_grid  # Old behavior: grid only shows when snapping
            show_snap_lines = self.show_snap_lines

        # Create a temporary snapping system with project settings to get snap lines
        from pyPhotoAlbum.snapping import SnappingSystem

        temp_snap_sys = SnappingSystem(snap_threshold_mm=snap_threshold_mm)
        temp_snap_sys.grid_size_mm = grid_size_mm
        temp_snap_sys.snap_to_grid = snap_to_grid
        temp_snap_sys.snap_to_edges = snap_to_edges
        temp_snap_sys.snap_to_guides = snap_to_guides
        temp_snap_sys.guides = self.snapping_system.guides  # Use page-specific guides

        snap_lines = temp_snap_sys.get_snap_lines(self.size, dpi)

        # Draw grid lines (light gray, fully opaque) - visible when show_grid is enabled
        if show_grid and snap_lines["grid"]:
            glColor3f(0.8, 0.8, 0.8)  # Light gray, fully opaque
            glLineWidth(1.0)
            for orientation, position in snap_lines["grid"]:
                glBegin(GL_LINES)
                if orientation == "vertical":
                    glVertex2f(page_x + position, page_y)
                    glVertex2f(page_x + position, page_y + self.size[1] * dpi / 25.4)
                else:  # horizontal
                    glVertex2f(page_x, page_y + position)
                    glVertex2f(page_x + self.size[0] * dpi / 25.4, page_y + position)
                glEnd()

        # Draw guides (cyan, fully opaque) - only show when show_snap_lines is on
        if show_snap_lines and snap_lines["guides"]:
            glColor3f(0.0, 0.7, 0.9)  # Cyan, fully opaque
            glLineWidth(1.5)
            for orientation, position in snap_lines["guides"]:
                glBegin(GL_LINES)
                if orientation == "vertical":
                    glVertex2f(page_x + position, page_y)
                    glVertex2f(page_x + position, page_y + self.size[1] * dpi / 25.4)
                else:  # horizontal
                    glVertex2f(page_x, page_y + position)
                    glVertex2f(page_x + self.size[0] * dpi / 25.4, page_y + position)
                glEnd()

        glLineWidth(1.0)

    def serialize(self) -> Dict[str, Any]:
        """Serialize page layout to dictionary"""
        return {
            "size": self.size,
            "base_width": self.base_width,
            "is_facing_page": self.is_facing_page,
            "background_color": self.background_color,
            "elements": [elem.serialize() for elem in self.elements],
            "grid_layout": self.grid_layout.serialize() if self.grid_layout else None,
            "snapping_system": self.snapping_system.serialize(),
            "show_snap_lines": self.show_snap_lines,
        }

    def deserialize(self, data: Dict[str, Any]):
        """Deserialize from dictionary"""
        self.size = tuple(data.get("size", (210, 297)))
        self.base_width = data.get("base_width", self.size[0])
        self.is_facing_page = data.get("is_facing_page", False)
        self.background_color = tuple(data.get("background_color", (1.0, 1.0, 1.0)))
        self.elements = []

        # Deserialize elements and sort by z_index to establish list order
        # This ensures backward compatibility with projects that used z_index
        elem_list: List[BaseLayoutElement] = []
        for elem_data in data.get("elements", []):
            elem_type = elem_data.get("type")
            elem: BaseLayoutElement
            if elem_type == "image":
                elem = ImageData()
            elif elem_type == "placeholder":
                elem = PlaceholderData()
            elif elem_type == "textbox":
                elem = TextBoxData()
            else:
                continue

            elem.deserialize(elem_data)
            elem_list.append(elem)

        # Sort by z_index to establish proper list order (lower z_index = earlier in list = behind)
        elem_list.sort(key=lambda e: e.z_index)
        self.elements = elem_list

        # Deserialize grid layout
        grid_data = data.get("grid_layout")
        if grid_data:
            self.grid_layout = GridLayout()
            self.grid_layout.deserialize(grid_data)

        # Deserialize snapping system
        snap_data = data.get("snapping_system")
        if snap_data:
            self.snapping_system.deserialize(snap_data)

        self.show_snap_lines = data.get("show_snap_lines", True)


class GridLayout:
    """Class to manage grid layouts"""

    def __init__(self, rows: int = 1, columns: int = 1, spacing: float = 10.0):
        self.rows = rows
        self.columns = columns
        self.spacing = spacing
        self.merged_cells: List[Tuple[int, int]] = []  # List of (row, col) for merged cells

    def merge_cells(self, row: int, col: int):
        """Merge cells in the grid"""
        self.merged_cells.append((row, col))

    def get_cell_position(
        self, row: int, col: int, page_width: float = 800, page_height: float = 600
    ) -> Tuple[float, float]:
        """Get the position of a grid cell"""
        cell_width = (page_width - (self.spacing * (self.columns + 1))) / self.columns
        cell_height = (page_height - (self.spacing * (self.rows + 1))) / self.rows

        x = self.spacing + (col * (cell_width + self.spacing))
        y = self.spacing + (row * (cell_height + self.spacing))

        return (x, y)

    def get_cell_size(self, page_width: float = 800, page_height: float = 600) -> Tuple[float, float]:
        """Get the size of a grid cell"""
        cell_width = (page_width - (self.spacing * (self.columns + 1))) / self.columns
        cell_height = (page_height - (self.spacing * (self.rows + 1))) / self.rows

        return (cell_width, cell_height)

    def serialize(self) -> Dict[str, Any]:
        """Serialize grid layout to dictionary"""
        return {"rows": self.rows, "columns": self.columns, "spacing": self.spacing, "merged_cells": self.merged_cells}

    def deserialize(self, data: Dict[str, Any]):
        """Deserialize from dictionary"""
        self.rows = data.get("rows", 1)
        self.columns = data.get("columns", 1)
        self.spacing = data.get("spacing", 10.0)
        self.merged_cells = data.get("merged_cells", [])
