"""
Rectangle shape implementation.

Rectangles are defined by top-left position, dimensions, and optional corner radii.
"""

from dataclasses import dataclass
from typing import TYPE_CHECKING

from .base import BoundingBox, Shape

if TYPE_CHECKING:
    from .path import Path


@dataclass
class Rectangle(Shape):
    """
    Rectangle shape.

    Attributes:
        x: Top-left x coordinate.
        y: Top-left y coordinate.
        width: Rectangle width (must be positive).
        height: Rectangle height (must be positive).
        rx: X-axis corner radius for rounded corners (default 0).
        ry: Y-axis corner radius for rounded corners (default 0).
        path_length: Optional SVG pathLength attribute for dash scaling.
    """

    x: float
    y: float
    width: float
    height: float
    rx: float = 0.0
    ry: float = 0.0
    path_length: float | None = None

    def __post_init__(self) -> None:
        """Validate rectangle parameters."""
        if self.width <= 0:
            raise ValueError(f"Rectangle width must be positive, got {self.width}")
        if self.height <= 0:
            raise ValueError(f"Rectangle height must be positive, got {self.height}")
        if self.rx < 0:
            raise ValueError(f"Rectangle rx must be non-negative, got {self.rx}")
        if self.ry < 0:
            raise ValueError(f"Rectangle ry must be non-negative, got {self.ry}")

    def get_bounding_box(self) -> BoundingBox:
        """Calculate bounding box of rectangle."""
        return BoundingBox(
            min_x=self.x, min_y=self.y, max_x=self.x + self.width, max_y=self.y + self.height
        )

    def to_path(self) -> "Path":
        """
        Convert rectangle to path representation.

        For rectangles with corner radii, uses proper circular arc approximation.
        For simple rectangles, uses moveto and lineto commands.

        Returns:
            Path object representing this rectangle.
        """
        import math

        from .path import Path, PathCommand

        if self.rx == 0 and self.ry == 0:
            # Simple rectangle without rounded corners
            commands = [
                PathCommand("M", True, [self.x, self.y]),
                PathCommand("L", True, [self.x + self.width, self.y]),
                PathCommand("L", True, [self.x + self.width, self.y + self.height]),
                PathCommand("L", True, [self.x, self.y + self.height]),
                PathCommand("Z", True, []),
            ]
        else:
            # Rounded rectangle with proper circular arc approximation
            # Clamp radii to half the width/height
            rx = min(self.rx, self.width / 2)
            ry = min(self.ry, self.height / 2)

            # Approximate each corner arc with multiple line segments
            # Use 12 segments per arc for a good balance of smoothness and accuracy
            segments_per_arc = 12

            commands = []

            # Start at top-left, after the corner
            commands.append(PathCommand("M", True, [self.x + rx, self.y]))

            # Top edge
            commands.append(PathCommand("L", True, [self.x + self.width - rx, self.y]))

            # Top-right corner arc
            for i in range(1, segments_per_arc + 1):
                angle = math.pi * 1.5 + (math.pi / 2) * (i / segments_per_arc)
                px = self.x + self.width - rx + rx * math.cos(angle)
                py = self.y + ry + ry * math.sin(angle)
                commands.append(PathCommand("L", True, [px, py]))

            # Right edge
            commands.append(
                PathCommand("L", True, [self.x + self.width, self.y + self.height - ry])
            )

            # Bottom-right corner arc
            for i in range(1, segments_per_arc + 1):
                angle = 0 + (math.pi / 2) * (i / segments_per_arc)
                px = self.x + self.width - rx + rx * math.cos(angle)
                py = self.y + self.height - ry + ry * math.sin(angle)
                commands.append(PathCommand("L", True, [px, py]))

            # Bottom edge
            commands.append(PathCommand("L", True, [self.x + rx, self.y + self.height]))

            # Bottom-left corner arc
            for i in range(1, segments_per_arc + 1):
                angle = math.pi / 2 + (math.pi / 2) * (i / segments_per_arc)
                px = self.x + rx + rx * math.cos(angle)
                py = self.y + self.height - ry + ry * math.sin(angle)
                commands.append(PathCommand("L", True, [px, py]))

            # Left edge
            commands.append(PathCommand("L", True, [self.x, self.y + ry]))

            # Top-left corner arc
            for i in range(1, segments_per_arc + 1):
                angle = math.pi + (math.pi / 2) * (i / segments_per_arc)
                px = self.x + rx + rx * math.cos(angle)
                py = self.y + ry + ry * math.sin(angle)
                commands.append(PathCommand("L", True, [px, py]))

            # Close path
            commands.append(PathCommand("Z", True, []))

        return Path(commands)

    def contains_point(self, x: float, y: float) -> bool:
        """Test if point is inside rectangle."""
        return self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height

    def get_path_length(self) -> float:
        """
        Calculate total arc-length of rectangle perimeter.

        If path_length attribute is set (SVG pathLength), returns that value
        for proper dash pattern scaling. Otherwise computes perimeter length.

        For simple rectangles (no rounded corners), returns 2 * (width + height).
        For rounded rectangles, uses arc approximation from to_path().

        Returns:
            Perimeter length in pixels (or pathLength if set).
        """
        # SVG pathLength attribute overrides computed length for dash calculations
        if self.path_length is not None:
            return self.path_length

        import math

        if self.rx == 0 and self.ry == 0:
            # Simple rectangle: perimeter is 2 * (width + height)
            return 2.0 * (self.width + self.height)
        else:
            # Rounded rectangle: need to account for arc lengths
            # Clamp radii
            rx = min(self.rx, self.width / 2)
            ry = min(self.ry, self.height / 2)

            # Straight edge lengths
            straight_horizontal = 2 * (self.width - 2 * rx)
            straight_vertical = 2 * (self.height - 2 * ry)

            # Four quarter-circle arcs (total = full ellipse circumference)
            # Approximation for ellipse circumference: Ramanujan's formula
            h = ((rx - ry) ** 2) / ((rx + ry) ** 2)
            arc_length = math.pi * (rx + ry) * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h)))

            return straight_horizontal + straight_vertical + arc_length

    def get_point_at_distance(self, distance: float) -> tuple[float, float, float]:
        """
        Get point coordinates and tangent angle at specified distance along rectangle perimeter.

        Traverses rectangle edges clockwise starting from top-left corner.

        Args:
            distance: Distance along perimeter from top-left corner in pixels.

        Returns:
            Tuple of (x, y, angle) where:
            - x, y: Point coordinates
            - angle: Tangent angle in radians (0 = right, π/2 = down)

        Notes:
            - If distance > perimeter, wraps around using modulo
            - For simple rectangles, traverses: top edge → right edge → bottom edge → left edge
            - For rounded rectangles, uses path approximation
        """
        import math

        # Get total length and wrap distance
        total_length = self.get_path_length()
        if total_length <= 0:
            return (self.x, self.y, 0.0)

        # Wrap distance using modulo
        distance = distance % total_length

        if self.rx == 0 and self.ry == 0:
            # Simple rectangle - traverse edges directly
            # Top edge (left to right)
            if distance <= self.width:
                x = self.x + distance
                y = self.y
                angle = 0.0  # Right
                return (x, y, angle)
            distance -= self.width

            # Right edge (top to bottom)
            if distance <= self.height:
                x = self.x + self.width
                y = self.y + distance
                angle = math.pi / 2  # Down
                return (x, y, angle)
            distance -= self.height

            # Bottom edge (right to left)
            if distance <= self.width:
                x = self.x + self.width - distance
                y = self.y + self.height
                angle = math.pi  # Left
                return (x, y, angle)
            distance -= self.width

            # Left edge (bottom to top)
            x = self.x
            y = self.y + self.height - distance
            angle = -math.pi / 2  # Up
            return (x, y, angle)
        else:
            # Rounded rectangle - use path conversion
            # This is less efficient but handles the complexity correctly
            path = self.to_path()
            return path.get_point_at_distance(distance)
