#!/usr/bin/env python3
"""
Typography System - Professional text rendering with outlines, shadows, and effects.

This module provides high-quality text rendering that looks crisp and professional
in GIFs, with outlines for readability and effects for visual impact.
"""

from PIL import Image, ImageDraw, ImageFont
from typing import Optional


# Typography scale - proportional sizing system
TYPOGRAPHY_SCALE = {
    'h1': 60,      # Large headers
    'h2': 48,      # Medium headers
    'h3': 36,      # Small headers
    'title': 50,   # Title text
    'body': 28,    # Body text
    'small': 20,   # Small text
    'tiny': 16,    # Tiny text
}


def get_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
    """
    Get a font with fallback support.

    Args:
        size: Font size in pixels
        bold: Use bold variant if available

    Returns:
        ImageFont object
    """
    # Try multiple font paths for cross-platform support
    font_paths = [
        # macOS fonts
        "/System/Library/Fonts/Helvetica.ttc",
        "/System/Library/Fonts/SF-Pro.ttf",
        "/Library/Fonts/Arial Bold.ttf" if bold else "/Library/Fonts/Arial.ttf",
        # Linux fonts
        "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        # Windows fonts
        "C:\\Windows\\Fonts\\arialbd.ttf" if bold else "C:\\Windows\\Fonts\\arial.ttf",
    ]

    for font_path in font_paths:
        try:
            return ImageFont.truetype(font_path, size)
        except:
            continue

    # Ultimate fallback
    return ImageFont.load_default()


def draw_text_with_outline(
    frame: Image.Image,
    text: str,
    position: tuple[int, int],
    font_size: int = 40,
    text_color: tuple[int, int, int] = (255, 255, 255),
    outline_color: tuple[int, int, int] = (0, 0, 0),
    outline_width: int = 3,
    centered: bool = False,
    bold: bool = True
) -> Image.Image:
    """
    Draw text with outline for maximum readability.

    This is THE most important function for professional-looking text in GIFs.
    The outline ensures text is readable on any background.

    Args:
        frame: PIL Image to draw on
        text: Text to draw
        position: (x, y) position
        font_size: Font size in pixels
        text_color: RGB color for text fill
        outline_color: RGB color for outline
        outline_width: Width of outline in pixels (2-4 recommended)
        centered: If True, center text at position
        bold: Use bold font variant

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    font = get_font(font_size, bold=bold)

    # Calculate position for centering
    if centered:
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        x = position[0] - text_width // 2
        y = position[1] - text_height // 2
        position = (x, y)

    # Draw outline by drawing text multiple times offset in all directions
    x, y = position
    for offset_x in range(-outline_width, outline_width + 1):
        for offset_y in range(-outline_width, outline_width + 1):
            if offset_x != 0 or offset_y != 0:
                draw.text((x + offset_x, y + offset_y), text, fill=outline_color, font=font)

    # Draw main text on top
    draw.text(position, text, fill=text_color, font=font)

    return frame


def draw_text_with_shadow(
    frame: Image.Image,
    text: str,
    position: tuple[int, int],
    font_size: int = 40,
    text_color: tuple[int, int, int] = (255, 255, 255),
    shadow_color: tuple[int, int, int] = (0, 0, 0),
    shadow_offset: tuple[int, int] = (3, 3),
    centered: bool = False,
    bold: bool = True
) -> Image.Image:
    """
    Draw text with drop shadow for depth.

    Args:
        frame: PIL Image to draw on
        text: Text to draw
        position: (x, y) position
        font_size: Font size in pixels
        text_color: RGB color for text
        shadow_color: RGB color for shadow
        shadow_offset: (x, y) offset for shadow
        centered: If True, center text at position
        bold: Use bold font variant

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    font = get_font(font_size, bold=bold)

    # Calculate position for centering
    if centered:
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        x = position[0] - text_width // 2
        y = position[1] - text_height // 2
        position = (x, y)

    # Draw shadow
    shadow_pos = (position[0] + shadow_offset[0], position[1] + shadow_offset[1])
    draw.text(shadow_pos, text, fill=shadow_color, font=font)

    # Draw main text
    draw.text(position, text, fill=text_color, font=font)

    return frame


def draw_text_with_glow(
    frame: Image.Image,
    text: str,
    position: tuple[int, int],
    font_size: int = 40,
    text_color: tuple[int, int, int] = (255, 255, 255),
    glow_color: tuple[int, int, int] = (255, 200, 0),
    glow_radius: int = 5,
    centered: bool = False,
    bold: bool = True
) -> Image.Image:
    """
    Draw text with glow effect for emphasis.

    Args:
        frame: PIL Image to draw on
        text: Text to draw
        position: (x, y) position
        font_size: Font size in pixels
        text_color: RGB color for text
        glow_color: RGB color for glow
        glow_radius: Radius of glow effect
        centered: If True, center text at position
        bold: Use bold font variant

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    font = get_font(font_size, bold=bold)

    # Calculate position for centering
    if centered:
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        x = position[0] - text_width // 2
        y = position[1] - text_height // 2
        position = (x, y)

    # Draw glow layers with decreasing opacity (simulated with same color at different offsets)
    x, y = position
    for radius in range(glow_radius, 0, -1):
        for offset_x in range(-radius, radius + 1):
            for offset_y in range(-radius, radius + 1):
                if offset_x != 0 or offset_y != 0:
                    draw.text((x + offset_x, y + offset_y), text, fill=glow_color, font=font)

    # Draw main text
    draw.text(position, text, fill=text_color, font=font)

    return frame


def draw_text_in_box(
    frame: Image.Image,
    text: str,
    position: tuple[int, int],
    font_size: int = 40,
    text_color: tuple[int, int, int] = (255, 255, 255),
    box_color: tuple[int, int, int] = (0, 0, 0),
    box_alpha: float = 0.7,
    padding: int = 10,
    centered: bool = True,
    bold: bool = True
) -> Image.Image:
    """
    Draw text in a semi-transparent box for guaranteed readability.

    Args:
        frame: PIL Image to draw on
        text: Text to draw
        position: (x, y) position
        font_size: Font size in pixels
        text_color: RGB color for text
        box_color: RGB color for background box
        box_alpha: Opacity of box (0.0-1.0)
        padding: Padding around text in pixels
        centered: If True, center at position
        bold: Use bold font variant

    Returns:
        Modified frame
    """
    # Create a separate layer for the box with alpha
    overlay = Image.new('RGBA', frame.size, (0, 0, 0, 0))
    draw_overlay = ImageDraw.Draw(overlay)
    draw = ImageDraw.Draw(frame)

    font = get_font(font_size, bold=bold)

    # Get text dimensions
    bbox = draw.textbbox((0, 0), text, font=font)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]

    # Calculate box position
    if centered:
        box_x = position[0] - (text_width + padding * 2) // 2
        box_y = position[1] - (text_height + padding * 2) // 2
        text_x = position[0] - text_width // 2
        text_y = position[1] - text_height // 2
    else:
        box_x = position[0] - padding
        box_y = position[1] - padding
        text_x = position[0]
        text_y = position[1]

    # Draw semi-transparent box
    box_coords = [
        box_x,
        box_y,
        box_x + text_width + padding * 2,
        box_y + text_height + padding * 2
    ]
    alpha_value = int(255 * box_alpha)
    draw_overlay.rectangle(box_coords, fill=(*box_color, alpha_value))

    # Composite overlay onto frame
    frame_rgba = frame.convert('RGBA')
    frame_rgba = Image.alpha_composite(frame_rgba, overlay)
    frame = frame_rgba.convert('RGB')

    # Draw text on top
    draw = ImageDraw.Draw(frame)
    draw.text((text_x, text_y), text, fill=text_color, font=font)

    return frame


def get_text_size(text: str, font_size: int, bold: bool = True) -> tuple[int, int]:
    """
    Get the dimensions of text without drawing it.

    Args:
        text: Text to measure
        font_size: Font size in pixels
        bold: Use bold font variant

    Returns:
        (width, height) tuple
    """
    font = get_font(font_size, bold=bold)
    # Create temporary image to measure
    temp_img = Image.new('RGB', (1, 1))
    draw = ImageDraw.Draw(temp_img)
    bbox = draw.textbbox((0, 0), text, font=font)
    width = bbox[2] - bbox[0]
    height = bbox[3] - bbox[1]
    return (width, height)


def get_optimal_font_size(text: str, max_width: int, max_height: int,
                          start_size: int = 60) -> int:
    """
    Find the largest font size that fits within given dimensions.

    Args:
        text: Text to size
        max_width: Maximum width in pixels
        max_height: Maximum height in pixels
        start_size: Starting font size to try

    Returns:
        Optimal font size
    """
    font_size = start_size
    while font_size > 10:
        width, height = get_text_size(text, font_size)
        if width <= max_width and height <= max_height:
            return font_size
        font_size -= 2
    return 10  # Minimum font size


def scale_font_for_frame(base_size: int, frame_width: int, frame_height: int) -> int:
    """
    Scale font size proportionally to frame dimensions.

    Useful for maintaining relative text size across different GIF dimensions.

    Args:
        base_size: Base font size for 480x480 frame
        frame_width: Actual frame width
        frame_height: Actual frame height

    Returns:
        Scaled font size
    """
    # Use average dimension for scaling
    avg_dimension = (frame_width + frame_height) / 2
    base_dimension = 480  # Reference dimension
    scale_factor = avg_dimension / base_dimension
    return max(10, int(base_size * scale_factor))