"""Page geometry calculations for layout and margins.

Computes document margins, content areas, and grid alignment based on
page dimensions and design parameters.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING

from tokenmoulds.design.units import EMU_PER_INCH, emu_to_pt, pt_to_emu

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from tokenmoulds.themes.aspect_ratios import AspectRatio


# Standard margin presets as percentage of page dimension
MARGIN_PRESETS = {
    "none": 0.0,
    "narrow": 0.05,  # 5% - minimal margins
    "normal": 0.10,  # 10% - standard document margins
    "wide": 0.15,  # 15% - generous whitespace
    "presentation": 0.08,  # 8% - slides have less margin
}

# Minimum margins in EMU (safety margins for printing)
MIN_MARGIN_EMU = int(0.25 * EMU_PER_INCH)  # 0.25 inch / 6.35mm


@dataclass(frozen=True, slots=True)
class PageMargins:
    """Page margins in EMU."""

    top: int
    right: int
    bottom: int
    left: int

    @property
    def horizontal(self) -> int:
        """Total horizontal margin (left + right)."""
        return self.left + self.right

    @property
    def vertical(self) -> int:
        """Total vertical margin (top + bottom)."""
        return self.top + self.bottom

    @classmethod
    def uniform(cls, margin: int) -> PageMargins:
        """Create uniform margins."""
        return cls(top=margin, right=margin, bottom=margin, left=margin)

    @classmethod
    def symmetric(cls, vertical: int, horizontal: int) -> PageMargins:
        """Create symmetric margins (same top/bottom, same left/right)."""
        return cls(top=vertical, right=horizontal, bottom=vertical, left=horizontal)

    def to_dict(self) -> dict[str, int]:
        """Convert to dict for serialization."""
        return {
            "top": self.top,
            "right": self.right,
            "bottom": self.bottom,
            "left": self.left,
        }


@dataclass(frozen=True, slots=True)
class ContentArea:
    """Usable content area within margins."""

    x: int  # Left edge in EMU
    y: int  # Top edge in EMU
    width: int  # Width in EMU
    height: int  # Height in EMU

    @property
    def right(self) -> int:
        """Right edge in EMU."""
        return self.x + self.width

    @property
    def bottom(self) -> int:
        """Bottom edge in EMU."""
        return self.y + self.height

    @property
    def center_x(self) -> int:
        """Horizontal center in EMU."""
        return self.x + self.width // 2

    @property
    def center_y(self) -> int:
        """Vertical center in EMU."""
        return self.y + self.height // 2


@dataclass(frozen=True, slots=True)
class PageGeometry:
    """Complete page geometry with dimensions, margins, and content area.

    This is the bridge between aspect ratios and the design engine.
    It provides the canvas dimensions needed for layout calculations.
    """

    # Page dimensions
    width_emu: int
    height_emu: int

    # Margins
    margins: PageMargins

    # Content area (derived)
    content: ContentArea

    # Grid alignment
    baseline_grid_emu: int = 0
    column_grid_emu: int = 0

    @property
    def width_pt(self) -> float:
        """Page width in points."""
        return emu_to_pt(self.width_emu)

    @property
    def height_pt(self) -> float:
        """Page height in points."""
        return emu_to_pt(self.height_emu)

    @property
    def content_width_pt(self) -> float:
        """Content area width in points."""
        return emu_to_pt(self.content.width)

    @property
    def content_height_pt(self) -> float:
        """Content area height in points."""
        return emu_to_pt(self.content.height)

    @property
    def is_landscape(self) -> bool:
        """True if width > height."""
        return self.width_emu > self.height_emu

    @property
    def is_portrait(self) -> bool:
        """True if height > width."""
        return self.height_emu > self.width_emu

    @property
    def aspect_ratio(self) -> float:
        """Aspect ratio as width/height."""
        return self.width_emu / self.height_emu if self.height_emu else 0

    def snap_to_baseline(self, value_emu: int) -> int:
        """Snap a vertical value to the baseline grid."""
        if self.baseline_grid_emu <= 0:
            return value_emu
        return round(value_emu / self.baseline_grid_emu) * self.baseline_grid_emu

    def snap_to_column(self, value_emu: int) -> int:
        """Snap a horizontal value to the column grid."""
        if self.column_grid_emu <= 0:
            return value_emu
        return round(value_emu / self.column_grid_emu) * self.column_grid_emu


def calculate_margins(
    width_emu: int,
    height_emu: int,
    preset: str = "normal",
    baseline_grid_emu: int = 0,
) -> PageMargins:
    """Calculate margins for page dimensions.

    Args:
        width_emu: Page width in EMU
        height_emu: Page height in EMU
        preset: Margin preset name or custom percentage
        baseline_grid_emu: Optional baseline grid for snapping

    Returns:
        PageMargins instance
    """
    # Get margin percentage
    if preset in MARGIN_PRESETS:
        margin_pct = MARGIN_PRESETS[preset]
    else:
        try:
            margin_pct = float(preset)
        except ValueError:
            logger.warning("Unknown margin preset %r, falling back to 'normal'", preset)
            margin_pct = MARGIN_PRESETS["normal"]

    # Calculate raw margins
    h_margin = int(width_emu * margin_pct)
    v_margin = int(height_emu * margin_pct)

    # Enforce minimum margins
    h_margin = max(h_margin, MIN_MARGIN_EMU)
    v_margin = max(v_margin, MIN_MARGIN_EMU)

    # Snap to baseline grid if provided
    if baseline_grid_emu > 0:
        v_margin = round(v_margin / baseline_grid_emu) * baseline_grid_emu
        # Ensure we don't go below minimum after snapping
        if v_margin < MIN_MARGIN_EMU:
            v_margin += baseline_grid_emu

    return PageMargins.symmetric(v_margin, h_margin)


def create_page_geometry(
    width_emu: int,
    height_emu: int,
    margin_preset: str = "normal",
    baseline_grid_emu: int = 0,
    columns: int = 0,
) -> PageGeometry:
    """Create page geometry from dimensions.

    Args:
        width_emu: Page width in EMU
        height_emu: Page height in EMU
        margin_preset: Margin preset name
        baseline_grid_emu: Baseline grid for vertical rhythm
        columns: Number of columns (0 = no column grid)

    Returns:
        PageGeometry instance
    """
    # Calculate margins
    margins = calculate_margins(
        width_emu,
        height_emu,
        preset=margin_preset,
        baseline_grid_emu=baseline_grid_emu,
    )

    # Calculate content area
    content = ContentArea(
        x=margins.left,
        y=margins.top,
        width=width_emu - margins.horizontal,
        height=height_emu - margins.vertical,
    )

    # Calculate column grid
    column_grid_emu = 0
    if columns > 0:
        column_grid_emu = content.width // columns
        # Snap to reasonable unit
        if baseline_grid_emu > 0:
            column_grid_emu = (
                round(column_grid_emu / baseline_grid_emu) * baseline_grid_emu
            )

    return PageGeometry(
        width_emu=width_emu,
        height_emu=height_emu,
        margins=margins,
        content=content,
        baseline_grid_emu=baseline_grid_emu,
        column_grid_emu=column_grid_emu,
    )


# =========================================================================
# Slide Grid System
# =========================================================================

# Default column counts for standard aspect ratios
SLIDE_COLUMN_DEFAULTS: dict[str, int] = {
    "16:9": 12,  # Divides into 6, 4, 3, 2
    "16:10": 12,
    "4:3": 9,  # Divides into 3
}


@dataclass(frozen=True, slots=True)
class SlideGrid:
    """Grid system for slide layout.

    Provides a column × row grid where:
    - Columns divide the content width evenly
    - Rows are baseline grid lines
    - All placeholder positions snap to grid intersections

    Example (16:9, 12pt grid, 12 columns):
        960pt wide → 48pt margin → 864pt content → 72pt per column
        540pt tall → 48pt top / 36pt bottom → 456pt content → 38 rows
    """

    columns: int
    rows: int
    column_module_emu: int
    row_module_emu: int
    margin_left_emu: int
    margin_right_emu: int
    margin_top_emu: int
    margin_bottom_emu: int
    content_width_emu: int
    content_height_emu: int
    slide_width_emu: int
    slide_height_emu: int

    def snap_position(
        self,
        x: int,
        y: int,
        cx: int,
        cy: int,
    ) -> tuple[int, int, int, int]:
        """Snap an arbitrary placeholder position to the grid.

        Rounds each value to the nearest baseline-grid multiple.
        Useful for aligning hardcoded positions from layout templates.

        Args:
            x: Left edge in EMU
            y: Top edge in EMU
            cx: Width in EMU
            cy: Height in EMU

        Returns:
            Tuple of (x, y, cx, cy) snapped to grid
        """
        g = self.row_module_emu
        if g <= 0:
            return (x, y, cx, cy)
        return (
            round(x / g) * g,
            round(y / g) * g,
            max(g, round(cx / g) * g),
            max(g, round(cy / g) * g),
        )

    def cell_rect(
        self,
        col: int,
        row: int,
        col_span: int = 1,
        row_span: int = 1,
    ) -> tuple[int, int, int, int]:
        """Get (x, y, width, height) in EMU for a grid cell range.

        Args:
            col: Starting column (0-based)
            row: Starting row (0-based)
            col_span: Number of columns to span
            row_span: Number of rows to span

        Returns:
            Tuple of (x, y, width, height) in EMU
        """
        x = self.margin_left_emu + col * self.column_module_emu
        y = self.margin_top_emu + row * self.row_module_emu
        w = col_span * self.column_module_emu
        h = row_span * self.row_module_emu
        return (x, y, w, h)


def _fit_margin_to_grid(
    slide_dim_pt: float,
    grid_pt: float,
    divisions: int,
    target_pct: float = 0.05,
) -> float:
    """Find the margin (in pt) that makes content divide cleanly.

    Picks the margin closest to ``target_pct`` of ``slide_dim_pt`` such that
    ``(slide_dim_pt - 2 * margin) / divisions`` is a whole number of grid
    units, and the margin itself is a grid multiple.
    """
    target = slide_dim_pt * target_pct
    best_margin = round(target / grid_pt) * grid_pt
    best_diff = float("inf")

    max_k = int(slide_dim_pt / (grid_pt * divisions))
    for k in range(1, max_k + 1):
        content = divisions * k * grid_pt
        raw_margin = (slide_dim_pt - content) / 2
        if raw_margin < grid_pt:
            continue
        snapped = round(raw_margin / grid_pt) * grid_pt
        if snapped < grid_pt:
            continue
        diff = abs(snapped - target)
        if diff < best_diff:
            best_diff = diff
            best_margin = snapped

    return best_margin


def create_slide_grid(
    slide_width_emu: int,
    slide_height_emu: int,
    baseline_grid_emu: int,
    columns: int = 0,
    v_margin_pct: float = 0.07,
    h_margin_pct: float = 0.05,
) -> SlideGrid:
    """Create a grid system for slide layout.

    Calculates margins that make the content area divide cleanly into
    ``columns`` × baseline-grid rows.  Both margins and content dimensions
    are multiples of the baseline grid.

    Args:
        slide_width_emu: Slide width in EMU
        slide_height_emu: Slide height in EMU
        baseline_grid_emu: Baseline grid module in EMU
        columns: Number of columns (0 = auto-detect from aspect ratio)
        v_margin_pct: Target vertical margin as fraction of height
        h_margin_pct: Target horizontal margin as fraction of width

    Returns:
        SlideGrid with computed dimensions
    """
    grid_pt = emu_to_pt(baseline_grid_emu)
    slide_w_pt = emu_to_pt(slide_width_emu)
    slide_h_pt = emu_to_pt(slide_height_emu)

    # Auto-detect column count from aspect ratio
    if columns <= 0:
        ratio = slide_width_emu / slide_height_emu
        columns = 12  # sensible default
        for label, default_cols in SLIDE_COLUMN_DEFAULTS.items():
            w, h = (int(x) for x in label.split(":"))
            if abs(ratio - w / h) < 0.05:
                columns = default_cols
                break

    # Horizontal: fit margin so content / columns = k * grid
    h_margin_pt = _fit_margin_to_grid(slide_w_pt, grid_pt, columns, h_margin_pct)
    content_w_pt = slide_w_pt - 2 * h_margin_pt
    col_module_pt = content_w_pt / columns

    # Vertical: both margins are grid multiples, content fills the middle
    v_margin_raw = slide_h_pt * v_margin_pct
    v_margin_snapped = max(grid_pt, round(v_margin_raw / grid_pt) * grid_pt)
    total_v_margin = 2 * v_margin_snapped
    remaining_pt = slide_h_pt - total_v_margin
    num_rows = int(remaining_pt / grid_pt)
    content_h_pt = num_rows * grid_pt
    # Distribute leftover evenly (usually 0 if slide height is grid-aligned)
    leftover = slide_h_pt - total_v_margin - content_h_pt
    top_margin_pt = v_margin_snapped + leftover / 2
    bottom_margin_pt = v_margin_snapped + leftover / 2

    return SlideGrid(
        columns=columns,
        rows=num_rows,
        column_module_emu=pt_to_emu(col_module_pt),
        row_module_emu=baseline_grid_emu,
        margin_left_emu=pt_to_emu(h_margin_pt),
        margin_right_emu=pt_to_emu(h_margin_pt),
        margin_top_emu=pt_to_emu(top_margin_pt),
        margin_bottom_emu=pt_to_emu(bottom_margin_pt),
        content_width_emu=pt_to_emu(content_w_pt),
        content_height_emu=pt_to_emu(content_h_pt),
        slide_width_emu=slide_width_emu,
        slide_height_emu=slide_height_emu,
    )


def page_geometry_from_aspect_ratio(
    aspect_ratio: AspectRatio,
    margin_preset: str = "normal",
    baseline_grid_emu: int = 0,
    columns: int = 0,
) -> PageGeometry:
    """Create page geometry from an AspectRatio.

    Args:
        aspect_ratio: AspectRatio instance
        margin_preset: Margin preset name
        baseline_grid_emu: Baseline grid for vertical rhythm
        columns: Number of columns

    Returns:
        PageGeometry instance
    """
    return create_page_geometry(
        width_emu=aspect_ratio.width_emu,
        height_emu=aspect_ratio.height_emu,
        margin_preset=margin_preset,
        baseline_grid_emu=baseline_grid_emu,
        columns=columns,
    )


__all__ = [
    "PageMargins",
    "ContentArea",
    "PageGeometry",
    "SlideGrid",
    "MARGIN_PRESETS",
    "MIN_MARGIN_EMU",
    "SLIDE_COLUMN_DEFAULTS",
    "calculate_margins",
    "create_page_geometry",
    "create_slide_grid",
    "page_geometry_from_aspect_ratio",
]
