"""
Concrete table rendering implementation for pyWebLayout.

This module provides the concrete rendering classes for tables, including:
- TableRenderer: Main table rendering with borders and spacing
- TableRowRenderer: Individual row rendering
- TableCellRenderer: Cell rendering with support for nested content (text, images, links)
"""

from __future__ import annotations
from typing import Tuple, List, Optional, Dict
from PIL import Image, ImageDraw
from dataclasses import dataclass

from pyWebLayout.core.base import Renderable
from pyWebLayout.concrete.box import Box
from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph, Heading, Image as AbstractImage
from pyWebLayout.abstract.interactive_image import InteractiveImage


@dataclass
class TableStyle:
    """Styling configuration for table rendering."""

    # Border configuration
    border_width: int = 1
    border_color: Tuple[int, int, int] = (0, 0, 0)

    # Cell padding
    cell_padding: Tuple[int, int, int, int] = (5, 5, 5, 5)  # top, right, bottom, left

    # Header styling
    header_bg_color: Tuple[int, int, int] = (240, 240, 240)
    header_text_bold: bool = True

    # Cell background
    cell_bg_color: Tuple[int, int, int] = (255, 255, 255)
    alternate_row_color: Optional[Tuple[int, int, int]] = (250, 250, 250)

    # Spacing
    cell_spacing: int = 0  # Space between cells (for separated borders model)


class TableCellRenderer(Box):
    """
    Renders a single table cell with its content.
    Supports paragraphs, headings, images, and links within cells.
    """

    def __init__(self,
                 cell: TableCell,
                 origin: Tuple[int,
                               int],
                 size: Tuple[int,
                             int],
                 draw: ImageDraw.Draw,
                 style: TableStyle,
                 is_header_section: bool = False,
                 canvas: Optional[Image.Image] = None):
        """
        Initialize a table cell renderer.

        Args:
            cell: The abstract TableCell to render
            origin: Top-left position of the cell
            size: Width and height of the cell
            draw: PIL ImageDraw object for rendering
            style: Table styling configuration
            is_header_section: Whether this cell is in the header section
            canvas: Optional PIL Image for pasting images (required for image rendering)
        """
        super().__init__(origin, size)
        self._cell = cell
        self._draw = draw
        self._style = style
        self._is_header_section = is_header_section or cell.is_header
        self._canvas = canvas
        self._children: List[Renderable] = []

    def render(self) -> Image.Image:
        """Render the table cell."""
        # Determine background color
        if self._is_header_section:
            bg_color = self._style.header_bg_color
        else:
            bg_color = self._style.cell_bg_color

        # Draw cell background
        x, y = self._origin
        w, h = self._size
        self._draw.rectangle(
            [x, y, x + w, y + h],
            fill=bg_color,
            outline=self._style.border_color,
            width=self._style.border_width
        )

        # Calculate content area (inside padding)
        padding = self._style.cell_padding
        content_x = x + padding[3]  # left padding
        content_y = y + padding[0]  # top padding
        content_width = w - (padding[1] + padding[3])  # minus left and right padding
        content_height = h - (padding[0] + padding[2])  # minus top and bottom padding

        # Render cell content (text)
        self._render_cell_content(content_x, content_y, content_width, content_height)

        return None  # Cell rendering is done directly on the page

    def _render_cell_content(self, x: int, y: int, width: int, height: int):
        """Render the content inside the cell (text and images) with line wrapping."""
        from pyWebLayout.concrete.text import Line, Text
        from pyWebLayout.style.fonts import Font
        from pyWebLayout.style import FontWeight, Alignment

        current_y = y + 2
        available_height = height - 4  # Account for top/bottom padding

        # Create font for the cell
        font_size = 12
        font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
        if self._is_header_section and self._style.header_text_bold:
            font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"

        font = Font(
            font_path=font_path,
            font_size=font_size,
            weight=FontWeight.BOLD if self._is_header_section and self._style.header_text_bold else FontWeight.NORMAL
        )

        # Word spacing constraints (min, max)
        min_spacing = int(font_size * 0.25)
        max_spacing = int(font_size * 0.5)
        word_spacing = (min_spacing, max_spacing)

        # Line height (baseline spacing)
        line_height = font_size + 4
        ascent, descent = font.font.getmetrics()

        # Render each block in the cell
        for block in self._cell.blocks():
            if isinstance(block, AbstractImage):
                # Render image
                current_y = self._render_image_in_cell(
                    block, x, current_y, width, height - (current_y - y))
            elif isinstance(block, (Paragraph, Heading)):
                # Get words from the block
                from pyWebLayout.abstract.inline import Word as AbstractWord

                word_items = block.words() if callable(block.words) else block.words
                words = list(word_items)

                if not words:
                    continue

                # Create new Word objects with the table cell's font
                # The words from the paragraph may have AbstractStyle, but we need Font objects
                wrapped_words = []
                for word_item in words:
                    # Handle word tuples (index, word_obj)
                    if isinstance(word_item, tuple) and len(word_item) >= 2:
                        word_obj = word_item[1]
                    else:
                        word_obj = word_item

                    # Extract text from the word
                    word_text = word_obj.text if hasattr(word_obj, 'text') else str(word_obj)

                    # Create a new Word with the cell's Font
                    new_word = AbstractWord(word_text, font)
                    wrapped_words.append(new_word)

                # Layout words using Line objects with wrapping
                word_index = 0
                pretext = None

                while word_index < len(wrapped_words):
                    # Check if we have space for another line
                    if current_y + ascent + descent > y + available_height:
                        break  # No more space in cell

                    # Create a new line
                    line = Line(
                        spacing=word_spacing,
                        origin=(x + 2, current_y),
                        size=(width - 4, line_height),
                        draw=self._draw,
                        font=font,
                        halign=Alignment.LEFT
                    )

                    # Add words to this line until it's full
                    line_has_content = False
                    while word_index < len(wrapped_words):
                        word = wrapped_words[word_index]

                        # Try to add word to line
                        success, overflow = line.add_word(word, pretext)
                        pretext = None  # Clear pretext after use

                        if success:
                            line_has_content = True
                            if overflow:
                                # Word was hyphenated, carry over to next line
                                # DON'T increment word_index - we need to add the overflow
                                # to the next line with the same word
                                pretext = overflow
                                break  # Move to next line
                            else:
                                # Word fit completely, move to next word
                                word_index += 1
                        else:
                            # Word doesn't fit on this line
                            if not line_has_content:
                                # Even first word doesn't fit, force it anyway and advance
                                # This prevents infinite loops with words that truly can't fit
                                word_index += 1
                            break

                    # Render the line if it has content
                    if line_has_content or len(line.text_objects) > 0:
                        line.render()
                        current_y += line_height

            if current_y > y + height - 10:  # Don't overflow cell
                break

        # If no structured content, try to get any text representation
        if current_y == y + 2 and hasattr(self._cell, '_text_content'):
            # Use simple text rendering for fallback case
            from PIL import ImageFont
            try:
                pil_font = ImageFont.truetype(font_path, font_size)
            except BaseException:
                pil_font = ImageFont.load_default()

            self._draw.text(
                (x + 2, current_y),
                self._cell._text_content,
                fill=(0, 0, 0),
                font=pil_font
            )

    def _render_image_in_cell(self, image_block: AbstractImage, x: int, y: int,
                              max_width: int, max_height: int) -> int:
        """
        Render an image block inside a table cell.

        Returns:
            The new Y position after the image
        """
        try:
            # Get the image path from the block
            image_path = None
            if hasattr(image_block, 'source'):
                image_path = image_block.source
            elif hasattr(image_block, '_source'):
                image_path = image_block._source
            elif hasattr(image_block, 'path'):
                image_path = image_block.path
            elif hasattr(image_block, 'src'):
                image_path = image_block.src
            elif hasattr(image_block, '_path'):
                image_path = image_block._path
            elif hasattr(image_block, '_src'):
                image_path = image_block._src

            if not image_path:
                return y + 20  # Skip if no image path

            # Load and resize image to fit in cell
            img = Image.open(image_path)

            # Calculate scaling to fit within max dimensions
            # Use more of the cell space for images
            img_width, img_height = img.size
            scale_w = max_width / img_width if img_width > max_width else 1
            scale_h = (max_height - 10) / \
                img_height if img_height > (max_height - 10) else 1
            scale = min(scale_w, scale_h, 1.0)  # Don't upscale

            new_width = int(img_width * scale)
            new_height = int(img_height * scale)

            if scale < 1.0:
                img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)

            # Center image horizontally in cell
            img_x = x + (max_width - new_width) // 2

            # Paste the image onto the canvas if available
            if self._canvas is not None:
                if img.mode == 'RGBA':
                    self._canvas.paste(img, (img_x, y), img)
                else:
                    self._canvas.paste(img, (img_x, y))
            else:
                # Fallback: draw a placeholder if no canvas provided
                self._draw.rectangle(
                    [img_x, y, img_x + new_width, y + new_height],
                    fill=(200, 200, 200),
                    outline=(150, 150, 150)
                )

                # Draw image indicator text
                from PIL import ImageFont
                try:
                    small_font = ImageFont.truetype(
                        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 9)
                except BaseException:
                    small_font = ImageFont.load_default()

                text = f"[Image: {new_width}x{new_height}]"
                bbox = self._draw.textbbox((0, 0), text, font=small_font)
                text_width = bbox[2] - bbox[0]
                text_x = img_x + (new_width - text_width) // 2
                text_y = y + (new_height - 12) // 2
                self._draw.text(
                    (text_x, text_y), text, fill=(
                        100, 100, 100), font=small_font)

            # Set bounds on InteractiveImage objects for tap detection
            if isinstance(image_block, InteractiveImage):
                image_block.set_rendered_bounds(
                    origin=(img_x, y),
                    size=(new_width, new_height)
                )

            return y + new_height + 5  # Add some spacing after image

        except Exception:
            # If image loading fails, just return current position
            return y + 20


class TableRowRenderer(Box):
    """
    Renders a single table row containing multiple cells.
    """

    def __init__(self,
                 row: TableRow,
                 origin: Tuple[int,
                               int],
                 column_widths: List[int],
                 row_height: int,
                 draw: ImageDraw.Draw,
                 style: TableStyle,
                 is_header_section: bool = False,
                 canvas: Optional[Image.Image] = None):
        """
        Initialize a table row renderer.

        Args:
            row: The abstract TableRow to render
            origin: Top-left position of the row
            column_widths: List of widths for each column
            row_height: Height of this row
            draw: PIL ImageDraw object for rendering
            style: Table styling configuration
            is_header_section: Whether this row is in the header section
            canvas: Optional PIL Image for pasting images
        """
        width = sum(column_widths) + style.border_width * (len(column_widths) + 1)
        super().__init__(origin, (width, row_height))
        self._row = row
        self._column_widths = column_widths
        self._row_height = row_height
        self._draw = draw
        self._style = style
        self._is_header_section = is_header_section
        self._canvas = canvas
        self._cell_renderers: List[TableCellRenderer] = []

    def render(self) -> Image.Image:
        """Render the table row by rendering each cell."""
        x, y = self._origin
        current_x = x

        # Render each cell
        cells = list(self._row.cells())
        for i, cell in enumerate(cells):
            if i < len(self._column_widths):
                cell_width = self._column_widths[i]

                # Handle colspan
                if cell.colspan > 1 and i + cell.colspan <= len(self._column_widths):
                    # Sum up widths for spanned columns
                    cell_width = sum(self._column_widths[i:i + cell.colspan])
                    cell_width += self._style.border_width * (cell.colspan - 1)

                # Create and render cell
                cell_renderer = TableCellRenderer(
                    cell,
                    (current_x, y),
                    (cell_width, self._row_height),
                    self._draw,
                    self._style,
                    self._is_header_section,
                    self._canvas
                )
                cell_renderer.render()
                self._cell_renderers.append(cell_renderer)

                current_x += cell_width + self._style.border_width

        return None  # Row rendering is done directly on the page


class TableRenderer(Box):
    """
    Main table renderer that orchestrates the rendering of an entire table.
    Handles layout calculation, row/cell placement, and overall table structure.
    """

    def __init__(self,
                 table: Table,
                 origin: Tuple[int,
                               int],
                 available_width: int,
                 draw: ImageDraw.Draw,
                 style: Optional[TableStyle] = None,
                 canvas: Optional[Image.Image] = None):
        """
        Initialize a table renderer.

        Args:
            table: The abstract Table to render
            origin: Top-left position where the table should be rendered
            available_width: Maximum width available for the table
            draw: PIL ImageDraw object for rendering
            style: Optional table styling configuration
            canvas: Optional PIL Image for pasting images
        """
        self._table = table
        self._draw = draw
        self._style = style or TableStyle()
        self._available_width = available_width
        self._canvas = canvas

        # Calculate table dimensions
        self._column_widths, self._row_heights = self._calculate_dimensions()
        total_width = sum(self._column_widths) + \
            self._style.border_width * (len(self._column_widths) + 1)
        total_height = sum(self._row_heights.values()) + \
            self._style.border_width * (len(self._row_heights) + 1)

        super().__init__(origin, (total_width, total_height))
        self._row_renderers: List[TableRowRenderer] = []

    def _calculate_dimensions(self) -> Tuple[List[int], Dict[str, int]]:
        """
        Calculate column widths and row heights for the table.

        Uses the table optimizer for intelligent column width distribution.

        Returns:
            Tuple of (column_widths, row_heights_dict)
        """
        from pyWebLayout.layout.table_optimizer import optimize_table_layout

        all_rows = list(self._table.all_rows())

        if not all_rows:
            return ([100], {"header": 30, "body": 30, "footer": 30})

        # Use optimizer for column widths!
        column_widths = optimize_table_layout(
            self._table,
            self._available_width,
            sample_size=5,
            style=self._style
        )

        if not column_widths:
            # Fallback if table is empty
            column_widths = [100]

        # Calculate row heights dynamically based on optimized column widths
        header_height = self._calculate_row_height_for_section(
            all_rows, "header", column_widths) if any(
            1 for section, _ in all_rows if section == "header") else 0

        body_height = self._calculate_row_height_for_section(
            all_rows, "body", column_widths)

        footer_height = self._calculate_row_height_for_section(
            all_rows, "footer", column_widths) if any(
            1 for section, _ in all_rows if section == "footer") else 0

        row_heights = {
            "header": header_height,
            "body": body_height,
            "footer": footer_height
        }

        return (column_widths, row_heights)

    def _calculate_row_height_for_section(
            self,
            all_rows: List,
            section: str,
            column_widths: List[int]) -> int:
        """
        Calculate the maximum required height for rows in a specific section.

        Args:
            all_rows: List of all rows in the table
            section: Section name ('header', 'body', or 'footer')
            column_widths: List of column widths

        Returns:
            Maximum height needed for rows in this section
        """
        from pyWebLayout.concrete.text import Text
        from pyWebLayout.style.fonts import Font
        from pyWebLayout.abstract.inline import Word as AbstractWord

        # Font configuration
        font_size = 12
        line_height = font_size + 4
        padding = self._style.cell_padding
        vertical_padding = padding[0] + padding[2]  # top + bottom
        horizontal_padding = padding[1] + padding[3]  # left + right

        max_height = 40  # Minimum height

        for row_section, row in all_rows:
            if row_section != section:
                continue

            row_max_height = 40  # Minimum for this row

            for cell_idx, cell in enumerate(row.cells()):
                if cell_idx >= len(column_widths):
                    continue

                # Get cell width (accounting for colspan)
                cell_width = column_widths[cell_idx]
                if cell.colspan > 1 and cell_idx + \
                        cell.colspan <= len(column_widths):
                    cell_width = sum(
                        column_widths[cell_idx:cell_idx + cell.colspan])
                    cell_width += self._style.border_width * (cell.colspan - 1)

                # Calculate content width (minus padding)
                content_width = cell_width - horizontal_padding - 4  # Extra margin

                cell_height = vertical_padding + 4  # Base height with padding

                # Analyze each block in the cell
                for block in cell.blocks():
                    if isinstance(block, AbstractImage):
                        # Images need more space
                        cell_height = max(cell_height, 120)
                    elif isinstance(block, (Paragraph, Heading)):
                        # Calculate text wrapping height
                        word_items = block.words() if callable(
                            block.words) else block.words
                        words = list(word_items)

                        if not words:
                            continue

                        # Simulate text wrapping to count lines
                        lines_needed = self._estimate_wrapped_lines(
                            words, content_width, font_size)
                        text_height = lines_needed * line_height
                        cell_height = max(
                            cell_height, text_height + vertical_padding + 4)

                row_max_height = max(row_max_height, cell_height)

            max_height = max(max_height, row_max_height)

        return max_height

    def _estimate_wrapped_lines(
            self,
            words: List,
            available_width: int,
            font_size: int) -> int:
        """
        Estimate how many lines are needed to render the given words.

        Args:
            words: List of word objects
            available_width: Available width for text
            font_size: Font size in pixels

        Returns:
            Number of lines needed
        """
        from pyWebLayout.concrete.text import Text
        from pyWebLayout.style.fonts import Font

        # Create a temporary font for measurement
        font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
        font = Font(font_path=font_path, font_size=font_size)

        # Word spacing (approximate)
        word_spacing = int(font_size * 0.25)

        lines = 1
        current_line_width = 0

        for word_item in words:
            # Handle word tuples (index, word_obj)
            if isinstance(word_item, tuple) and len(word_item) >= 2:
                word_obj = word_item[1]
            else:
                word_obj = word_item

            # Extract text from the word
            word_text = word_obj.text if hasattr(
                word_obj, 'text') else str(word_obj)

            # Measure word width
            word_width = font.font.getlength(word_text)

            # Check if word fits on current line
            if current_line_width > 0:  # Not first word on line
                needed_width = current_line_width + word_spacing + word_width
                if needed_width > available_width:
                    # Need new line
                    lines += 1
                    current_line_width = word_width
                else:
                    current_line_width = needed_width
            else:
                # First word on line
                if word_width > available_width:
                    # Word needs to be hyphenated, assume it takes 1 line
                    lines += 1
                    current_line_width = 0
                else:
                    current_line_width = word_width

        return lines

    def render(self) -> Image.Image:
        """Render the complete table."""
        x, y = self._origin
        current_y = y

        # Render caption if present
        if self._table.caption:
            current_y = self._render_caption(x, current_y)
            current_y += 10  # Space after caption

        # Render header rows
        for section, row in self._table.all_rows():
            if section == "header":
                row_height = self._row_heights["header"]
            elif section == "footer":
                row_height = self._row_heights["footer"]
            else:
                row_height = self._row_heights["body"]

            is_header = (section == "header")

            row_renderer = TableRowRenderer(
                row,
                (x, current_y),
                self._column_widths,
                row_height,
                self._draw,
                self._style,
                is_header,
                self._canvas
            )
            row_renderer.render()
            self._row_renderers.append(row_renderer)

            current_y += row_height + self._style.border_width

        return None  # Table rendering is done directly on the page

    def _render_caption(self, x: int, y: int) -> int:
        """Render the table caption and return the new Y position."""
        from PIL import ImageFont

        try:
            font = ImageFont.truetype(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 13)
        except BaseException:
            font = ImageFont.load_default()

        # Center the caption
        bbox = self._draw.textbbox((0, 0), self._table.caption, font=font)
        text_width = bbox[2] - bbox[0]
        caption_x = x + (self._size[0] - text_width) // 2

        self._draw.text((caption_x, y), self._table.caption, fill=(0, 0, 0), font=font)

        return y + 20  # Caption height

    @property
    def height(self) -> int:
        """Get the total height of the rendered table."""
        return int(self._size[1])

    @property
    def width(self) -> int:
        """Get the total width of the rendered table."""
        return int(self._size[0])
