"""
Dynamic page layout engine for the universal data report generator.
Provides pre-defined layout templates and layout calculation utilities.
Supports dynamic slide dimensions via LayoutContext for custom templates.
"""
from pptx.util import Emu, Pt
from pptx.dml.color import RGBColor
from dataclasses import dataclass, field
from typing import Optional


# ==============================================================================
# DEFAULT CONSTANTS (backward compatible)
# ==============================================================================

SLIDE_WIDTH = 16256000
SLIDE_HEIGHT = 9144000
MARGIN_LEFT = Emu(762000)
MARGIN_RIGHT = Emu(762000)
MARGIN_TOP = Emu(254000)
CONTENT_TOP_BASE = Emu(1600200)
FOOTER_TOP = Emu(8824000)
FOOTER_HEIGHT = Emu(320000)
CONTENT_WIDTH = SLIDE_WIDTH - MARGIN_LEFT - MARGIN_RIGHT


# ==============================================================================
# LAYOUT CONTEXT
# ==============================================================================

@dataclass
class LayoutContext:
    """Encapsulates slide dimensions and content geometry for a specific template."""
    slide_width: int = SLIDE_WIDTH
    slide_height: int = SLIDE_HEIGHT
    content_top: int = field(default_factory=lambda: int(CONTENT_TOP_BASE))
    footer_top: int = FOOTER_TOP
    margin_left: int = field(default_factory=lambda: int(MARGIN_LEFT))
    margin_right: int = field(default_factory=lambda: int(MARGIN_RIGHT))
    margin_top: int = field(default_factory=lambda: int(MARGIN_TOP))

    @property
    def content_width(self) -> int:
        return self.slide_width - self.margin_left - self.margin_right

    @classmethod
    def from_template_profile(cls, profile) -> 'LayoutContext':
        """Build LayoutContext from a TemplateProfile (template_parser)."""
        # Import here to avoid circular dependency at module load
        from template_parser import TemplateProfile
        if not isinstance(profile, TemplateProfile):
            raise TypeError("profile must be a TemplateProfile instance")
        
        # Use content top from content master if available
        content_top = profile.get_content_top("content")
        margins = profile.safe_margins
        
        return cls(
            slide_width=profile.slide_width,
            slide_height=profile.slide_height,
            content_top=content_top,
            footer_top=profile.slide_height - int(Emu(320000)),  # default footer area
            margin_left=margins.get("left", int(MARGIN_LEFT)),
            margin_right=margins.get("right", int(MARGIN_RIGHT)),
            margin_top=margins.get("top", int(MARGIN_TOP)),
        )


# ==============================================================================
# LAYOUT ZONES
# ==============================================================================

@dataclass
class LayoutZone:
    x: int
    y: int
    width: int
    height: int
    zone_type: str


def _resolve_ctx(ctx: Optional[LayoutContext] = None) -> LayoutContext:
    return ctx if ctx is not None else LayoutContext()


def calculate_content_area(content_top_emu: int = None,
                           ctx: Optional[LayoutContext] = None) -> LayoutZone:
    ctx = _resolve_ctx(ctx)
    top = content_top_emu if content_top_emu is not None else ctx.content_top
    height = ctx.footer_top - top - int(Emu(100000))
    return LayoutZone(
        x=ctx.margin_left,
        y=top,
        width=ctx.content_width,
        height=max(int(height), int(Emu(500000))),
        zone_type='content_area',
    )


def get_kpi_grid(content_top_emu: int = None, cols: int = 3, rows: int = 2,
                 card_width_emu: int = 4699000, card_height_emu: int = 3048000,
                 gap_x_emu: int = 444500, gap_y_emu: int = 381000,
                 ctx: Optional[LayoutContext] = None) -> list[LayoutZone]:
    ctx = _resolve_ctx(ctx)
    start_y = max(ctx.content_top, content_top_emu or ctx.content_top)
    zones = []
    for row in range(rows):
        for col in range(cols):
            x = ctx.margin_left + col * (card_width_emu + gap_x_emu)
            y = start_y + row * (card_height_emu + gap_y_emu)
            zones.append(LayoutZone(x=x, y=y, width=card_width_emu, height=card_height_emu, zone_type='kpi_card'))
    return zones


def get_chart_left_zone(content_top_emu: int = None, chart_ratio: float = 0.6,
                        ctx: Optional[LayoutContext] = None) -> LayoutZone:
    content = calculate_content_area(content_top_emu, ctx)
    chart_w = int(content.width * chart_ratio) - int(Emu(200000))
    return LayoutZone(
        x=content.x,
        y=content.y,
        width=max(chart_w, int(Emu(1000000))),
        height=content.height,
        zone_type='chart_left',
    )


def get_insight_right_zone(content_top_emu: int = None, chart_ratio: float = 0.6,
                           ctx: Optional[LayoutContext] = None) -> LayoutZone:
    content = calculate_content_area(content_top_emu, ctx)
    chart_w = int(content.width * chart_ratio)
    text_left = content.x + chart_w + int(Emu(200000))
    text_w = content.x + content.width - text_left
    return LayoutZone(
        x=text_left,
        y=content.y,
        width=max(text_w, int(Emu(800000))),
        height=content.height,
        zone_type='insight_right',
    )


def get_full_width_zone(content_top_emu: int = None,
                        ctx: Optional[LayoutContext] = None) -> LayoutZone:
    return calculate_content_area(content_top_emu, ctx)


def get_two_column_zones(content_top_emu: int = None, gap_emu: int = 381000,
                         ctx: Optional[LayoutContext] = None) -> tuple[LayoutZone, LayoutZone]:
    content = calculate_content_area(content_top_emu, ctx)
    half_w = (content.width - gap_emu) // 2
    left = LayoutZone(x=content.x, y=content.y, width=half_w, height=content.height, zone_type='column_left')
    right = LayoutZone(x=content.x + half_w + gap_emu, y=content.y, width=half_w, height=content.height, zone_type='column_right')
    return left, right


def get_two_row_zones(content_top_emu: int = None, gap_emu: int = 381000,
                      top_ratio: float = 0.55,
                      ctx: Optional[LayoutContext] = None) -> tuple[LayoutZone, LayoutZone]:
    content = calculate_content_area(content_top_emu, ctx)
    top_h = int(content.height * top_ratio)
    top = LayoutZone(x=content.x, y=content.y, width=content.width, height=top_h, zone_type='row_top')
    bottom = LayoutZone(
        x=content.x,
        y=content.y + top_h + gap_emu,
        width=content.width,
        height=content.height - top_h - gap_emu,
        zone_type='row_bottom',
    )
    return top, bottom


def get_card_grid(n: int, content_top_emu: int = None, max_cols: int = 3,
                  ctx: Optional[LayoutContext] = None) -> list[LayoutZone]:
    content = calculate_content_area(content_top_emu, ctx)
    cols = min(max_cols, n)
    rows = (n + cols - 1) // cols
    card_w = (content.width - (cols - 1) * int(Emu(254000))) // cols
    card_h = (content.height - (rows - 1) * int(Emu(254000))) // rows

    zones = []
    for i in range(n):
        col = i % cols
        row = i // cols
        x = content.x + col * (card_w + int(Emu(254000)))
        y = content.y + row * (card_h + int(Emu(254000)))
        zones.append(LayoutZone(x=x, y=y, width=card_w, height=card_h, zone_type=f'card_{i}'))
    return zones


def get_alert_card_zones(n: int, content_top_emu: int = None,
                         ctx: Optional[LayoutContext] = None) -> list[LayoutZone]:
    return get_card_grid(n, content_top_emu, max_cols=3, ctx=ctx)


def get_issue_card_zones(n: int, content_top_emu: int = None,
                         ctx: Optional[LayoutContext] = None) -> list[LayoutZone]:
    content = calculate_content_area(content_top_emu, ctx)
    card_h = int(Emu(2032000))
    gap = int(Emu(254000))
    start_y = content.y
    zones = []
    for i in range(min(n, 3)):
        y = start_y + i * (card_h + gap)
        zones.append(LayoutZone(x=content.x, y=y, width=content.width, height=card_h, zone_type=f'issue_{i}'))
    return zones


def get_table_zone(content_top_emu: int = None, ratio: float = 0.5,
                   ctx: Optional[LayoutContext] = None) -> LayoutZone:
    content = calculate_content_area(content_top_emu, ctx)
    return LayoutZone(
        x=content.x,
        y=content.y + int(content.height * ratio) + int(Emu(200000)),
        width=content.width,
        height=int(content.height * (1 - ratio)),
        zone_type='table_bottom',
    )


def detect_layout_slots(slide, ctx: Optional[LayoutContext] = None) -> dict:
    ctx = _resolve_ctx(ctx)
    slots = {
        'has_header': False,
        'has_footer': False,
        'has_page_title': False,
        'content_top': ctx.content_top,
        'content_width': ctx.content_width,
        'content_height': ctx.footer_top - ctx.content_top - int(Emu(100000)),
    }
    for shape in slide.shapes:
        if shape.has_text_frame:
            text = shape.text_frame.text
            if 'page_title' in text or '报告' in text:
                slots['has_page_title'] = True
            if '数据来源' in text:
                slots['has_footer'] = True
                slots['footer_top'] = int(shape.top)
        if shape.top + shape.height < Emu(1300000):
            slots['has_header'] = True
    return slots


def ensure_safe_position(shape, slide_width: int, slide_height: int) -> bool:
    margin = Emu(254000)
    adjusted = False
    if shape.left < 0:
        shape.left = margin
        adjusted = True
    if shape.top < 0:
        shape.top = margin
        adjusted = True
    if shape.left + shape.width > slide_width:
        shape.left = slide_width - shape.width - margin
        adjusted = True
    if shape.top + shape.height > slide_height:
        shape.top = slide_height - shape.height - margin
        adjusted = True
    return adjusted


def calculate_fill_ratio(slide, content_top_emu: int = None,
                         ctx: Optional[LayoutContext] = None) -> float:
    content = calculate_content_area(content_top_emu, ctx)
    total_area = content.width * content.height
    if total_area <= 0:
        return 0.0
    filled_area = 0
    for shape in slide.shapes:
        sx = int(shape.left)
        sy = int(shape.top)
        sw = int(shape.width)
        sh = int(shape.height)
        if sy < content.y:
            continue
        if sy > content.y + content.height:
            continue
        overlap_x = max(0, min(sx + sw, content.x + content.width) - max(sx, content.x))
        overlap_y = max(0, min(sy + sh, content.y + content.height) - max(sy, content.y))
        filled_area += overlap_x * overlap_y
    return min(1.0, filled_area / total_area)


if __name__ == '__main__':
    ca = calculate_content_area()
    print(f"Content area: {ca.width}x{ca.height}")
    kpis = get_kpi_grid()
    for i, z in enumerate(kpis):
        print(f"KPI {i}: x={z.x}, y={z.y}")
    print(f"Fill ratio test: hypothetical")