"""
Rendering mixin for GLWidget - handles OpenGL rendering
"""

import math

from pyPhotoAlbum.gl_imports import *
from PyQt6.QtGui import QPainter, QFont, QColor, QPen
from PyQt6.QtCore import Qt, QRectF
from pyPhotoAlbum.models import TextBoxData


class RenderingMixin:
    """
    Mixin providing OpenGL rendering functionality.

    This mixin handles rendering pages, elements, selection handles,
    and text overlays.
    """

    def paintGL(self):
        """Main rendering function - renders all pages vertically"""
        from pyPhotoAlbum.page_renderer import PageRenderer

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        # Use stored reference to main window
        main_window = getattr(self, "_main_window", None)
        if main_window is None:
            # Fallback to window() if _main_window not set
            main_window = self.window()

        if main_window is None:
            return

        try:
            project = main_window.project
            if not project:
                return
            if not project.pages:
                return
        except AttributeError:
            # Project not yet initialized
            return

        # Set initial zoom and center the page if not done yet
        if not self.initial_zoom_set:
            self.zoom_level = self._calculate_fit_to_screen_zoom()
            self.pan_offset = self._calculate_center_pan_offset(self.zoom_level)
            self.initial_zoom_set = True

            # Update scrollbars now that we have content bounds
            if hasattr(self, "_main_window") and hasattr(self._main_window, "update_scrollbars"):
                self._main_window.update_scrollbars()

        dpi = project.working_dpi

        # Calculate page positions with ghosts
        page_positions = self._get_page_positions()

        # Store page renderers for later use
        self._page_renderers = []

        # Left margin for page rendering
        PAGE_MARGIN = 50

        # Render all pages
        pages_rendered = 0
        for page_info in page_positions:
            page_type, page_or_ghost, y_offset = page_info

            if page_type == "page":
                page = page_or_ghost
                page_width_mm, page_height_mm = page.layout.size

                screen_x = PAGE_MARGIN + self.pan_offset[0]
                screen_y = (y_offset * self.zoom_level) + self.pan_offset[1]

                renderer = PageRenderer(
                    page_width_mm=page_width_mm,
                    page_height_mm=page_height_mm,
                    screen_x=screen_x,
                    screen_y=screen_y,
                    dpi=dpi,
                    zoom=self.zoom_level,
                )

                self._page_renderers.append((renderer, page))

                renderer.begin_render()
                # Pass widget reference for async loading
                page.layout._parent_widget = self
                page.layout.render(dpi=dpi, project=project)
                renderer.end_render()

                # Draw bleed/cut/safe-area guides for this page
                self._draw_page_print_guides(renderer, project)

                pages_rendered += 1

            elif page_type == "ghost":
                ghost = page_or_ghost
                ghost_width_mm, ghost_height_mm = ghost.page_size

                screen_x = PAGE_MARGIN + self.pan_offset[0]
                screen_y = (y_offset * self.zoom_level) + self.pan_offset[1]

                renderer = PageRenderer(
                    page_width_mm=ghost_width_mm,
                    page_height_mm=ghost_height_mm,
                    screen_x=screen_x,
                    screen_y=screen_y,
                    dpi=dpi,
                    zoom=self.zoom_level,
                )

                self._render_ghost_page(ghost, renderer)

        # Update PageRenderer references for selected elements
        for element in self.selected_elements:
            if hasattr(element, "_parent_page"):
                for renderer, page in self._page_renderers:
                    if page is element._parent_page:
                        element._page_renderer = renderer
                        break

        # Draw selection handles for all selected elements
        for element in self.selected_elements:
            self._draw_selection_handles(element)

        # Render text overlays using QPainter
        # Qt will handle OpenGL/QPainter coordination automatically
        self._render_text_overlays()

    def _draw_selection_handles(self, element):
        """Draw selection handles around the given element"""
        if not element:
            return

        main_window = self.window()
        if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages:
            return

        if not hasattr(element, "_page_renderer"):
            return

        renderer = element._page_renderer

        elem_x, elem_y = element.position
        elem_w, elem_h = element.size
        handle_size = 8

        x, y = renderer.page_to_screen(elem_x, elem_y)
        w = elem_w * renderer.zoom
        h = elem_h * renderer.zoom

        center_x = x + w / 2
        center_y = y + h / 2

        # No rotation transformation needed - images are already rotated at PIL level

        if self.rotation_mode:
            glColor3f(1.0, 0.5, 0.0)
        else:
            glColor3f(0.0, 0.5, 1.0)

        glLineWidth(2.0)
        glBegin(GL_LINE_LOOP)
        glVertex2f(x, y)
        glVertex2f(x + w, y)
        glVertex2f(x + w, y + h)
        glVertex2f(x, y + h)
        glEnd()
        glLineWidth(1.0)

        if self.rotation_mode:
            handle_radius = 6
            handles = [(x, y), (x + w, y), (x, y + h), (x + w, y + h)]

            glColor3f(1.0, 0.5, 0.0)
            glBegin(GL_TRIANGLE_FAN)
            glVertex2f(center_x, center_y)
            for angle in range(0, 361, 10):
                rad = math.radians(angle)
                hx = center_x + 3 * math.cos(rad)
                hy = center_y + 3 * math.sin(rad)
                glVertex2f(hx, hy)
            glEnd()

            for hx, hy in handles:
                glColor3f(1.0, 1.0, 1.0)
                glBegin(GL_TRIANGLE_FAN)
                glVertex2f(hx, hy)
                for angle in range(0, 361, 30):
                    rad = math.radians(angle)
                    px = hx + handle_radius * math.cos(rad)
                    py = hy + handle_radius * math.sin(rad)
                    glVertex2f(px, py)
                glEnd()

                glColor3f(1.0, 0.5, 0.0)
                glBegin(GL_LINE_LOOP)
                for angle in range(0, 361, 30):
                    rad = math.radians(angle)
                    px = hx + handle_radius * math.cos(rad)
                    py = hy + handle_radius * math.sin(rad)
                    glVertex2f(px, py)
                glEnd()
        else:
            handles = [
                (x - handle_size / 2, y - handle_size / 2),
                (x + w - handle_size / 2, y - handle_size / 2),
                (x - handle_size / 2, y + h - handle_size / 2),
                (x + w - handle_size / 2, y + h - handle_size / 2),
            ]

            glColor3f(1.0, 1.0, 1.0)
            for hx, hy in handles:
                glBegin(GL_QUADS)
                glVertex2f(hx, hy)
                glVertex2f(hx + handle_size, hy)
                glVertex2f(hx + handle_size, hy + handle_size)
                glVertex2f(hx, hy + handle_size)
                glEnd()

            glColor3f(0.0, 0.5, 1.0)
            for hx, hy in handles:
                glBegin(GL_LINE_LOOP)
                glVertex2f(hx, hy)
                glVertex2f(hx + handle_size, hy)
                glVertex2f(hx + handle_size, hy + handle_size)
                glVertex2f(hx, hy + handle_size)
                glEnd()

    def _render_text_overlays(self):
        """Render text content for TextBoxData elements using QPainter overlay"""
        if not hasattr(self, "_page_renderers") or not self._page_renderers:
            return

        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)

        try:
            for renderer, page in self._page_renderers:
                text_elements = [elem for elem in page.layout.elements if isinstance(elem, TextBoxData)]

                for element in text_elements:
                    if not element.text_content:
                        continue

                    x, y = element.position
                    w, h = element.size

                    screen_x, screen_y = renderer.page_to_screen(x, y)
                    screen_w = w * renderer.zoom
                    screen_h = h * renderer.zoom

                    font_family = element.font_settings.get("family", "Arial")
                    # Use base font size without zoom - zoom is applied via painter transform
                    font_size = int(element.font_settings.get("size", 12))
                    font = QFont(font_family, font_size)
                    painter.setFont(font)

                    font_color = element.font_settings.get("color", (0, 0, 0))
                    if all(isinstance(c, int) and c > 1 for c in font_color):
                        color = QColor(*font_color)
                    else:
                        color = QColor(int(font_color[0] * 255), int(font_color[1] * 255), int(font_color[2] * 255))
                    painter.setPen(QPen(color))

                    # Apply zoom via painter transform so font scales consistently with page
                    painter.save()
                    painter.translate(screen_x, screen_y)
                    painter.scale(renderer.zoom, renderer.zoom)

                    if element.rotation != 0:
                        painter.translate(w / 2, h / 2)
                        painter.rotate(element.rotation)
                        painter.translate(-w / 2, -h / 2)

                    rect = QRectF(0, 0, w, h)

                    alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
                    if element.alignment == "center":
                        alignment = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop
                    elif element.alignment == "right":
                        alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
                    elif element.alignment == "justify":
                        alignment = Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignTop

                    text_flags = Qt.TextFlag.TextWordWrap

                    painter.drawText(rect, int(alignment | text_flags), element.text_content)

                    painter.restore()

        finally:
            painter.end()

    def _render_ghost_page(self, ghost_data, renderer):
        """Render a ghost page using PageRenderer"""
        renderer.begin_render()
        ghost_data.render()
        renderer.end_render()

        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)

        try:
            px, py, pw, ph = ghost_data.get_page_rect()

            screen_x, screen_y = renderer.page_to_screen(px, py)

            # Use base font size without zoom - zoom is applied via painter transform
            font = QFont("Arial", 16, QFont.Weight.Bold)
            painter.setFont(font)
            painter.setPen(QColor(120, 120, 120))

            painter.save()
            painter.translate(screen_x, screen_y)
            painter.scale(renderer.zoom, renderer.zoom)

            rect = QRectF(0, 0, pw, ph)
            painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "Click to Add Page")

            painter.restore()

        finally:
            painter.end()

    def _draw_page_print_guides(self, renderer, project):
        """
        Draw bleed/cut/safe-area guide lines around a page using OpenGL.

        - Green dashed rectangle: bleed boundary (extend backgrounds to here)
        - Magenta rectangle: cut/trim line (finished page edge, only shown when bleed > 0)
        - Red rectangle: safe area (keep text and logos inside)

        Lines are only drawn when the corresponding project settings are non-zero.
        """
        if not getattr(project, "show_print_guides", False):
            return

        bleed_mm = getattr(project, "page_bleed_mm", 0.0)
        safe_mm = getattr(project, "page_safe_area_mm", 0.0)

        if bleed_mm <= 0.0 and safe_mm <= 0.0:
            return

        dpi = project.working_dpi
        zoom = renderer.zoom
        sx = renderer.screen_x
        sy = renderer.screen_y
        sw = renderer.screen_width
        sh = renderer.screen_height

        # Convert mm to screen pixels
        bleed_screen = bleed_mm * dpi / 25.4 * zoom
        safe_screen = safe_mm * dpi / 25.4 * zoom

        glLineWidth(1.0)

        def _draw_rect_outline(x, y, w, h, r, g, b, dashed=False):
            glColor3f(r, g, b)
            if dashed:
                glEnable(GL_LINE_STIPPLE)
                glLineStipple(1, 0x00FF)
            else:
                glDisable(GL_LINE_STIPPLE)
            glBegin(GL_LINE_LOOP)
            glVertex2f(x, y)
            glVertex2f(x + w, y)
            glVertex2f(x + w, y + h)
            glVertex2f(x, y + h)
            glEnd()
            glDisable(GL_LINE_STIPPLE)

        # Bleed boundary – green dashed rectangle outside the page
        if bleed_screen > 0:
            _draw_rect_outline(
                sx - bleed_screen,
                sy - bleed_screen,
                sw + 2 * bleed_screen,
                sh + 2 * bleed_screen,
                0.0,
                0.67,
                0.0,
                dashed=True,
            )

        # Cut/trim line – magenta rectangle at the page edge (only meaningful when bleed > 0)
        if bleed_screen > 0:
            _draw_rect_outline(sx, sy, sw, sh, 0.8, 0.0, 0.8)

        # Safe area – red rectangle inside the page
        if safe_screen > 0:
            _draw_rect_outline(
                sx + safe_screen,
                sy + safe_screen,
                sw - 2 * safe_screen,
                sh - 2 * safe_screen,
                0.8,
                0.0,
                0.0,
            )

        glColor3f(1.0, 1.0, 1.0)  # Reset colour
