#!/usr/bin/env python3
"""
Frame Composer - Utilities for composing visual elements into frames.

Provides functions for drawing shapes, text, emojis, and compositing elements
together to create animation frames.
"""

from PIL import Image, ImageDraw, ImageFont
import numpy as np
from typing import Optional


def create_blank_frame(width: int, height: int, color: tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
    """
    Create a blank frame with solid color background.

    Args:
        width: Frame width
        height: Frame height
        color: RGB color tuple (default: white)

    Returns:
        PIL Image
    """
    return Image.new('RGB', (width, height), color)


def draw_circle(frame: Image.Image, center: tuple[int, int], radius: int,
                fill_color: Optional[tuple[int, int, int]] = None,
                outline_color: Optional[tuple[int, int, int]] = None,
                outline_width: int = 1) -> Image.Image:
    """
    Draw a circle on a frame.

    Args:
        frame: PIL Image to draw on
        center: (x, y) center position
        radius: Circle radius
        fill_color: RGB fill color (None for no fill)
        outline_color: RGB outline color (None for no outline)
        outline_width: Outline width in pixels

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    x, y = center
    bbox = [x - radius, y - radius, x + radius, y + radius]
    draw.ellipse(bbox, fill=fill_color, outline=outline_color, width=outline_width)
    return frame


def draw_rectangle(frame: Image.Image, top_left: tuple[int, int], bottom_right: tuple[int, int],
                   fill_color: Optional[tuple[int, int, int]] = None,
                   outline_color: Optional[tuple[int, int, int]] = None,
                   outline_width: int = 1) -> Image.Image:
    """
    Draw a rectangle on a frame.

    Args:
        frame: PIL Image to draw on
        top_left: (x, y) top-left corner
        bottom_right: (x, y) bottom-right corner
        fill_color: RGB fill color (None for no fill)
        outline_color: RGB outline color (None for no outline)
        outline_width: Outline width in pixels

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    draw.rectangle([top_left, bottom_right], fill=fill_color, outline=outline_color, width=outline_width)
    return frame


def draw_line(frame: Image.Image, start: tuple[int, int], end: tuple[int, int],
              color: tuple[int, int, int] = (0, 0, 0), width: int = 2) -> Image.Image:
    """
    Draw a line on a frame.

    Args:
        frame: PIL Image to draw on
        start: (x, y) start position
        end: (x, y) end position
        color: RGB line color
        width: Line width in pixels

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    draw.line([start, end], fill=color, width=width)
    return frame


def draw_text(frame: Image.Image, text: str, position: tuple[int, int],
              font_size: int = 40, color: tuple[int, int, int] = (0, 0, 0),
              centered: bool = False) -> Image.Image:
    """
    Draw text on a frame.

    Args:
        frame: PIL Image to draw on
        text: Text to draw
        position: (x, y) position (top-left unless centered=True)
        font_size: Font size in pixels
        color: RGB text color
        centered: If True, center text at position

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)

    # Try to use default font, fall back to basic if not available
    try:
        font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
    except:
        font = ImageFont.load_default()

    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.text(position, text, fill=color, font=font)
    return frame


def draw_emoji(frame: Image.Image, emoji: str, position: tuple[int, int], size: int = 60) -> Image.Image:
    """
    Draw emoji text on a frame (requires system emoji support).

    Args:
        frame: PIL Image to draw on
        emoji: Emoji character(s)
        position: (x, y) position
        size: Emoji size in pixels

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)

    # Use Apple Color Emoji font on macOS
    try:
        font = ImageFont.truetype("/System/Library/Fonts/Apple Color Emoji.ttc", size)
    except:
        # Fallback to text-based emoji
        font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size)

    draw.text(position, emoji, font=font, embedded_color=True)
    return frame


def composite_layers(base: Image.Image, overlay: Image.Image,
                     position: tuple[int, int] = (0, 0), alpha: float = 1.0) -> Image.Image:
    """
    Composite one image on top of another.

    Args:
        base: Base image
        overlay: Image to overlay on top
        position: (x, y) position to place overlay
        alpha: Opacity of overlay (0.0 = transparent, 1.0 = opaque)

    Returns:
        Composite image
    """
    # Convert to RGBA for transparency support
    base_rgba = base.convert('RGBA')
    overlay_rgba = overlay.convert('RGBA')

    # Apply alpha
    if alpha < 1.0:
        overlay_rgba = overlay_rgba.copy()
        overlay_rgba.putalpha(int(255 * alpha))

    # Paste overlay onto base
    base_rgba.paste(overlay_rgba, position, overlay_rgba)

    # Convert back to RGB
    return base_rgba.convert('RGB')


def draw_stick_figure(frame: Image.Image, position: tuple[int, int], scale: float = 1.0,
                      color: tuple[int, int, int] = (0, 0, 0), line_width: int = 3) -> Image.Image:
    """
    Draw a simple stick figure.

    Args:
        frame: PIL Image to draw on
        position: (x, y) center position of head
        scale: Size multiplier
        color: RGB line color
        line_width: Line width in pixels

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    x, y = position

    # Scale dimensions
    head_radius = int(15 * scale)
    body_length = int(40 * scale)
    arm_length = int(25 * scale)
    leg_length = int(35 * scale)
    leg_spread = int(15 * scale)

    # Head
    draw.ellipse([x - head_radius, y - head_radius, x + head_radius, y + head_radius],
                 outline=color, width=line_width)

    # Body
    body_start = y + head_radius
    body_end = body_start + body_length
    draw.line([(x, body_start), (x, body_end)], fill=color, width=line_width)

    # Arms
    arm_y = body_start + int(body_length * 0.3)
    draw.line([(x - arm_length, arm_y), (x + arm_length, arm_y)], fill=color, width=line_width)

    # Legs
    draw.line([(x, body_end), (x - leg_spread, body_end + leg_length)], fill=color, width=line_width)
    draw.line([(x, body_end), (x + leg_spread, body_end + leg_length)], fill=color, width=line_width)

    return frame


def create_gradient_background(width: int, height: int,
                               top_color: tuple[int, int, int],
                               bottom_color: tuple[int, int, int]) -> Image.Image:
    """
    Create a vertical gradient background.

    Args:
        width: Frame width
        height: Frame height
        top_color: RGB color at top
        bottom_color: RGB color at bottom

    Returns:
        PIL Image with gradient
    """
    frame = Image.new('RGB', (width, height))
    draw = ImageDraw.Draw(frame)

    # Calculate color step for each row
    r1, g1, b1 = top_color
    r2, g2, b2 = bottom_color

    for y in range(height):
        # Interpolate color
        ratio = y / height
        r = int(r1 * (1 - ratio) + r2 * ratio)
        g = int(g1 * (1 - ratio) + g2 * ratio)
        b = int(b1 * (1 - ratio) + b2 * ratio)

        # Draw horizontal line
        draw.line([(0, y), (width, y)], fill=(r, g, b))

    return frame


def draw_emoji_enhanced(frame: Image.Image, emoji: str, position: tuple[int, int],
                       size: int = 60, shadow: bool = True,
                       shadow_offset: tuple[int, int] = (2, 2)) -> Image.Image:
    """
    Draw emoji with optional shadow for better visual quality.

    Args:
        frame: PIL Image to draw on
        emoji: Emoji character(s)
        position: (x, y) position
        size: Emoji size in pixels (minimum 12)
        shadow: Whether to add drop shadow
        shadow_offset: Shadow offset

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)

    # Ensure minimum size to avoid font rendering errors
    size = max(12, size)

    # Use Apple Color Emoji font on macOS
    try:
        font = ImageFont.truetype("/System/Library/Fonts/Apple Color Emoji.ttc", size)
    except:
        # Fallback to text-based emoji
        try:
            font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size)
        except:
            font = ImageFont.load_default()

    # Draw shadow first if enabled
    if shadow and size >= 20:  # Only draw shadow for larger emojis
        shadow_pos = (position[0] + shadow_offset[0], position[1] + shadow_offset[1])
        # Draw semi-transparent shadow (simulated by drawing multiple times)
        for offset in range(1, 3):
            try:
                draw.text((shadow_pos[0] + offset, shadow_pos[1] + offset),
                         emoji, font=font, embedded_color=True, fill=(0, 0, 0, 100))
            except:
                pass  # Skip shadow if it fails

    # Draw main emoji
    try:
        draw.text(position, emoji, font=font, embedded_color=True)
    except:
        # Fallback to basic drawing if embedded color fails
        draw.text(position, emoji, font=font, fill=(0, 0, 0))

    return frame


def draw_circle_with_shadow(frame: Image.Image, center: tuple[int, int], radius: int,
                            fill_color: tuple[int, int, int],
                            shadow_offset: tuple[int, int] = (3, 3),
                            shadow_color: tuple[int, int, int] = (0, 0, 0)) -> Image.Image:
    """
    Draw a circle with drop shadow.

    Args:
        frame: PIL Image to draw on
        center: (x, y) center position
        radius: Circle radius
        fill_color: RGB fill color
        shadow_offset: (x, y) shadow offset
        shadow_color: RGB shadow color

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    x, y = center

    # Draw shadow
    shadow_center = (x + shadow_offset[0], y + shadow_offset[1])
    shadow_bbox = [
        shadow_center[0] - radius,
        shadow_center[1] - radius,
        shadow_center[0] + radius,
        shadow_center[1] + radius
    ]
    draw.ellipse(shadow_bbox, fill=shadow_color)

    # Draw main circle
    bbox = [x - radius, y - radius, x + radius, y + radius]
    draw.ellipse(bbox, fill=fill_color)

    return frame


def draw_rounded_rectangle(frame: Image.Image, top_left: tuple[int, int],
                          bottom_right: tuple[int, int], radius: int,
                          fill_color: Optional[tuple[int, int, int]] = None,
                          outline_color: Optional[tuple[int, int, int]] = None,
                          outline_width: int = 1) -> Image.Image:
    """
    Draw a rectangle with rounded corners.

    Args:
        frame: PIL Image to draw on
        top_left: (x, y) top-left corner
        bottom_right: (x, y) bottom-right corner
        radius: Corner radius
        fill_color: RGB fill color (None for no fill)
        outline_color: RGB outline color (None for no outline)
        outline_width: Outline width

    Returns:
        Modified frame
    """
    draw = ImageDraw.Draw(frame)
    x1, y1 = top_left
    x2, y2 = bottom_right

    # Draw rounded rectangle using PIL's built-in method
    draw.rounded_rectangle([x1, y1, x2, y2], radius=radius,
                          fill=fill_color, outline=outline_color, width=outline_width)

    return frame


def add_vignette(frame: Image.Image, strength: float = 0.5) -> Image.Image:
    """
    Add a vignette effect (darkened edges) to frame.

    Args:
        frame: PIL Image
        strength: Vignette strength (0.0-1.0)

    Returns:
        Frame with vignette
    """
    width, height = frame.size

    # Create radial gradient mask
    center_x, center_y = width // 2, height // 2
    max_dist = ((width / 2) ** 2 + (height / 2) ** 2) ** 0.5

    # Create overlay
    overlay = Image.new('RGB', (width, height), (0, 0, 0))
    pixels = overlay.load()

    for y in range(height):
        for x in range(width):
            # Calculate distance from center
            dx = x - center_x
            dy = y - center_y
            dist = (dx ** 2 + dy ** 2) ** 0.5

            # Calculate vignette value
            vignette = min(1, (dist / max_dist) * strength)
            value = int(255 * (1 - vignette))
            pixels[x, y] = (value, value, value)

    # Blend with original using multiply
    frame_array = np.array(frame, dtype=np.float32) / 255
    overlay_array = np.array(overlay, dtype=np.float32) / 255

    result = frame_array * overlay_array
    result = (result * 255).astype(np.uint8)

    return Image.fromarray(result)


def draw_star(frame: Image.Image, center: tuple[int, int], size: int,
             fill_color: tuple[int, int, int],
             outline_color: Optional[tuple[int, int, int]] = None,
             outline_width: int = 1) -> Image.Image:
    """
    Draw a 5-pointed star.

    Args:
        frame: PIL Image to draw on
        center: (x, y) center position
        size: Star size (outer radius)
        fill_color: RGB fill color
        outline_color: RGB outline color (None for no outline)
        outline_width: Outline width

    Returns:
        Modified frame
    """
    import math
    draw = ImageDraw.Draw(frame)
    x, y = center

    # Calculate star points
    points = []
    for i in range(10):
        angle = (i * 36 - 90) * math.pi / 180  # 36 degrees per point, start at top
        radius = size if i % 2 == 0 else size * 0.4  # Alternate between outer and inner
        px = x + radius * math.cos(angle)
        py = y + radius * math.sin(angle)
        points.append((px, py))

    # Draw star
    draw.polygon(points, fill=fill_color, outline=outline_color, width=outline_width)

    return frame