"""Bridge between DesignResolutionEngine and Document IR.

This module converts design system outputs (ResolvedDesign) into the
Document IR model used by emitters.
"""

from __future__ import annotations

from typing import Dict, List

from tokenmoulds.design.engine import (
    OpenTypeSettings,
    ResolvedDesign,
    TrackingSettings,
)
from tokenmoulds.design.color_defaults import (
    BORDER_DEFAULT,
    OFFICE_ACCENT1,
    OFFICE_ACCENT2,
    OFFICE_ACCENT3,
    OFFICE_ACCENT4,
    OFFICE_ACCENT5,
    OFFICE_ACCENT6,
    OFFICE_DK2,
    OFFICE_LT2,
    PURE_BLACK,
    PURE_WHITE,
    SURFACE_DARKEST,
    TEXT_PRIMARY,
)
from tokenmoulds.design.policy_adapter import create_design_engine
from tokenmoulds.design.units import pt_to_twips
from tokenmoulds.ir.model import (
    CharacterTypography,
    ColorTheme,
    DocumentIR,
    HeadingStyle,
    HyperlinkStyle,
    ListStyle,
    OpenTypeFeatures,
    ParagraphSpacing,
    ParagraphStyle,
    PresentationLayout,
    QuoteStyle,
    SlidePlaceholder,
    TypographyScheme,
)


def _convert_opentype_settings(settings: OpenTypeSettings) -> OpenTypeFeatures:
    """Convert engine OpenTypeSettings to IR OpenTypeFeatures."""
    return OpenTypeFeatures(
        ligatures=settings.ligatures,
        number_form=settings.number_form,
        number_spacing=settings.number_spacing,
        contextual_alternates=settings.contextual_alternates,
        stylistic_sets=list(settings.stylistic_sets),
    )


def _convert_tracking_settings(
    tracking: TrackingSettings,
    opentype: OpenTypeSettings,
) -> CharacterTypography:
    """Convert engine TrackingSettings to IR CharacterTypography."""
    return CharacterTypography(
        tracking_twips=tracking.tracking_twips,
        kern_threshold_half_pt=16,  # Enable kerning for 8pt and above
        opentype=_convert_opentype_settings(opentype),
    )


def resolved_design_to_typography_scheme(
    design: ResolvedDesign,
    body_font: str = "Inter",
    heading_font: str = "Inter",
    monospace_font: str = "Courier New",
) -> TypographyScheme:
    """Convert ResolvedDesign to TypographyScheme.

    Args:
        design: Resolved design from the design engine
        body_font: Font family name for body text
        heading_font: Font family name for headings
        monospace_font: Font family name for monospace/code styles

    Returns:
        TypographyScheme for DocumentIR
    """
    # Build size map from type scale
    size_map: Dict[str, float] = {}
    for step in design.type_scale:
        size_map[step.name] = step.size_pt

    # Add role-based sizes
    for role, typo in design.roles.items():
        # Map role names to size map keys
        if "body" in role:
            size_map.setdefault("body", typo.font_size_pt)
        elif "display" in role:
            level = 1 if "primary" in role else 2
            size_map.setdefault(f"h{level}", typo.font_size_pt)

    return TypographyScheme(
        body_font=body_font,
        heading_font=heading_font,
        baseline_emu=design.baseline.grid_emu,
        size_map_pt=size_map,
        monospace_font=monospace_font,
    )


def resolved_design_to_body_style(
    design: ResolvedDesign,
    body_font: str = "Inter",
    text_color: str = TEXT_PRIMARY,
) -> ParagraphStyle:
    """Convert ResolvedDesign to body paragraph style.

    Args:
        design: Resolved design from the design engine
        body_font: Font family name
        text_color: Text color hex code

    Returns:
        ParagraphStyle for body text
    """
    # Get body role typography
    body_typo = design.roles.get("text.body.primary")
    if body_typo:
        font_size = body_typo.font_size_pt
        space_before = body_typo.space_before_pt
        space_after = body_typo.space_after_pt
        # Use derived typography features
        character = _convert_tracking_settings(body_typo.tracking, body_typo.opentype)
    else:
        # Fallback to baseline grid
        font_size = design.baseline.grid_pt
        space_before = 0
        space_after = design.baseline.grid_pt
        character = CharacterTypography()

    return ParagraphStyle(
        style_id="Normal",
        name="Normal",
        font_family=body_font,
        font_size_pt=font_size,
        color=text_color,
        spacing=ParagraphSpacing(
            before_twips=pt_to_twips(space_before),
            after_twips=pt_to_twips(space_after),
        ),
        character=character,
    )


def resolved_design_to_heading_styles(
    design: ResolvedDesign,
    heading_font: str = "Inter",
    text_color: str = TEXT_PRIMARY,
) -> List[HeadingStyle]:
    """Convert ResolvedDesign to heading styles.

    Args:
        design: Resolved design from the design engine
        heading_font: Font family name for headings
        text_color: Text color hex code

    Returns:
        List of HeadingStyle for h1-h6
    """
    headings = []

    # Map heading levels to type scale steps and roles
    heading_scale_map = {
        1: ("h1", "display.primary"),
        2: ("h2", "display.secondary"),
        3: ("h3", "heading.section"),
        4: ("h4", "heading.subsection"),
        5: ("h5", "heading.minor"),
        6: ("h6", "heading.minor"),
    }

    for level, (scale_name, role_name) in heading_scale_map.items():
        # Try to get from type scale
        step = design.get_type_step(scale_name)
        if step:
            font_size = step.size_pt
        else:
            # Fallback to decreasing from h1
            font_size = design.baseline.grid_pt * (3.0 - (level - 1) * 0.4)

        # Calculate spacing based on level
        space_before = design.baseline.grid_pt * max(1, 4 - level * 0.5)
        space_after = design.baseline.grid_pt

        # Get derived typography features from role if available
        role_typo = design.roles.get(role_name)
        if role_typo:
            character = _convert_tracking_settings(
                role_typo.tracking, role_typo.opentype
            )
        else:
            # Fallback: headings get tight tracking and lining figures
            character = CharacterTypography(
                tracking_twips=-20 if level <= 2 else -10,
                kern_threshold_half_pt=16,
                opentype=OpenTypeFeatures(
                    ligatures="standard",
                    number_form="lining",
                    number_spacing="proportional",
                ),
            )

        headings.append(
            HeadingStyle(
                style_id=f"Heading{level}",
                name=f"Heading {level}",
                font_family=heading_font,
                font_size_pt=font_size,
                color=text_color,
                spacing=ParagraphSpacing(
                    before_twips=pt_to_twips(space_before),
                    after_twips=pt_to_twips(space_after),
                ),
                bold=level <= 3,  # Bold for h1-h3
                level=level,
                outline_level=level - 1,
                character=character,
            )
        )

    return headings


def resolved_design_to_list_style(design: ResolvedDesign) -> ListStyle:
    """Convert ResolvedDesign to list style with typographic best practices.

    Implements:
    - Indent based on baseline grid (typically 1em or 1 baseline unit)
    - Hanging indent for clean text alignment
    - Tighter spacing between items than paragraphs
    - Appropriate bullet characters for nesting levels

    Args:
        design: Resolved design from the design engine

    Returns:
        ListStyle for DocumentIR
    """
    baseline_pt = design.baseline.grid_pt

    # Indent = 1 baseline grid unit (approximately 1em at body size)
    indent_pt = baseline_pt

    # Hanging indent = enough for bullet + space (~0.5 baseline)
    # This ensures the bullet hangs and text aligns cleanly
    hanging_pt = baseline_pt * 0.5

    # Space between items = half the normal paragraph spacing
    # This creates tighter list grouping while maintaining readability
    space_between_pt = baseline_pt * 0.25

    return ListStyle(
        indent_twips=pt_to_twips(indent_pt),
        hanging_twips=pt_to_twips(hanging_pt),
        indent_per_level_twips=pt_to_twips(indent_pt),
        # Bullet: small bullet (•), not heavy black circle
        bullet_char="•",
        bullet_font="Symbol",
        # Numbers: right-aligned with period
        number_format="%1.",
        number_alignment="right",
        # Spacing: tighter between items
        space_between_items_twips=pt_to_twips(space_between_pt),
        space_before_list_twips=pt_to_twips(baseline_pt * 0.5),
        space_after_list_twips=pt_to_twips(baseline_pt * 0.5),
        # Nested bullets: bullet → en-dash → mid-dot
        nested_bullet_chars=("•", "–", "·"),
    )


def _build_presentation_layout(
    design: ResolvedDesign,
    background_color: str = PURE_WHITE,
) -> PresentationLayout:
    """Build presentation layout from a grid system.

    Uses the same grid-based approach as document typography:
    - The baseline grid module determines the row height
    - Margins are adjusted so the content area divides cleanly
      into columns × rows
    - All placeholder positions snap to grid intersections

    For 16:9 with a 12pt grid and 12 columns this yields::

        960pt slide → 48pt margins → 864pt content → 72pt/column
        540pt slide → 48pt top / 36pt bottom → 38 rows

        Title:  all 12 columns, rows 0-5   (72pt)
        Gap:    1 row                       (12pt)
        Body:   all 12 columns, rows 7-37  (372pt)

    Args:
        design: Resolved design from the design engine
        background_color: Slide background color

    Returns:
        PresentationLayout for DocumentIR
    """
    from tokenmoulds.design.page_geometry import create_slide_grid
    from tokenmoulds.dtcg.layout_recipe_resolver import (
        compute_default_slide_regions,
        resolve_layout_by_name,
    )

    from tokenmoulds.design.layout_defaults import (
        SLIDE_16_9_HEIGHT_EMU,
        SLIDE_16_9_WIDTH_EMU,
    )

    # Determine slide dimensions
    if design.has_page_geometry and design.page_geometry is not None:
        slide_width = design.page_geometry.width_emu
        slide_height = design.page_geometry.height_emu
    else:
        # Default 16:9
        slide_width = SLIDE_16_9_WIDTH_EMU
        slide_height = SLIDE_16_9_HEIGHT_EMU

    grid = create_slide_grid(
        slide_width_emu=slide_width,
        slide_height_emu=slide_height,
        baseline_grid_emu=design.baseline.grid_emu,
    )

    # Use token-driven layout recipe for title_content (default slide layout)
    regions = compute_default_slide_regions(grid)
    tc_regions = regions.get("title_content", {})
    positions = resolve_layout_by_name("title_content", tc_regions, grid)

    tx, ty, tw, th = positions.get(
        "title", grid.cell_rect(0, 0, grid.columns, min(6, grid.rows // 4))
    )
    bx, by, bw, bh = positions.get(
        "body", grid.cell_rect(0, 7, grid.columns, grid.rows - 7)
    )

    placeholders = [
        SlidePlaceholder(kind="title", x=tx, y=ty, cx=tw, cy=th, text_style="h1"),
        SlidePlaceholder(kind="body", x=bx, y=by, cx=bw, cy=bh, text_style="body"),
    ]

    return PresentationLayout(
        slide_width=slide_width,
        slide_height=slide_height,
        background_color=background_color,
        placeholders=placeholders,
    )


def resolved_design_to_quote_style(
    design: ResolvedDesign,
    body_font: str = "Inter",
    text_color: str = SURFACE_DARKEST,
    border_color: str = BORDER_DEFAULT,
) -> QuoteStyle:
    """Convert ResolvedDesign to blockquote style.

    Args:
        design: Resolved design from the design engine
        body_font: Font family
        text_color: Text color
        border_color: Left border color

    Returns:
        QuoteStyle for DocumentIR
    """
    body_step = design.get_type_step("body")
    font_size = body_step.size_pt if body_step else design.baseline.grid_pt

    return QuoteStyle(
        style_id="Quote",
        name="Quote",
        font_family=body_font,
        font_size_pt=font_size,
        color=text_color,
        spacing=ParagraphSpacing(
            before_twips=pt_to_twips(design.baseline.grid_pt),
            after_twips=pt_to_twips(design.baseline.grid_pt),
        ),
        italic=True,
        border_color=border_color,
        border_width_twips=pt_to_twips(2),
        indent_twips=pt_to_twips(design.spacing.get_pt("indent")),
    )


def create_document_ir_from_design(
    design: ResolvedDesign,
    *,
    org_id: str = "default",
    locale: str = "en-US",
    body_font: str = "Inter",
    heading_font: str = "Inter",
    monospace_font: str = "Courier New",
    color_theme: ColorTheme | None = None,
    text_color: str = TEXT_PRIMARY,
    hyperlink_color: str | None = None,
    hyperlink_visited: str | None = None,
) -> DocumentIR:
    """Create a DocumentIR from ResolvedDesign.

    This is the main function for bridging the design engine output
    to the Document IR used by emitters.

    Args:
        design: Resolved design from the design engine
        org_id: Organization identifier
        locale: Locale code
        body_font: Font family for body text
        heading_font: Font family for headings
        color_theme: Optional color theme (uses defaults if not provided)
        text_color: Default text color
        hyperlink_color: Hyperlink color (defaults to accent1 per Brandwares model)
        hyperlink_visited: Visited hyperlink color (defaults to accent1)

    Returns:
        DocumentIR ready for emitters
    """
    # Default color theme if not provided
    # Uses PowerPoint-optimized Brandwares/Microsoft-grade model:
    # - accent1-2: Brand colors
    # - accent3-4: Tint/shade of accent1 (chart utility)
    # - accent5-6: Cool/warm neutrals (chart utility)
    # - hyperlinks: Match accent1 (no purple surprise)
    if color_theme is None:
        color_theme = ColorTheme(
            dk1=PURE_BLACK,  # Primary text (neutral)
            lt1=PURE_WHITE,  # Primary background (neutral)
            dk2=OFFICE_DK2,  # Secondary text/rules (neutral)
            lt2=OFFICE_LT2,  # Secondary background (neutral tint)
            accent1=OFFICE_ACCENT1,  # Brand primary
            accent2=OFFICE_ACCENT2,  # Brand secondary (Office orange)
            accent3=OFFICE_ACCENT3,  # Light tint of accent1
            accent4=OFFICE_ACCENT4,  # Dark shade of accent1
            accent5=OFFICE_ACCENT5,  # Cool neutral
            accent6=OFFICE_ACCENT6,  # Warm neutral
            hyperlink=hyperlink_color or OFFICE_ACCENT1,
            hyperlink_followed=hyperlink_visited or OFFICE_ACCENT1,
        )

    # Build typography scheme
    typography = resolved_design_to_typography_scheme(
        design, body_font, heading_font, monospace_font
    )

    # Build body style
    body_style = resolved_design_to_body_style(design, body_font, text_color)

    # Build heading styles
    heading_styles = resolved_design_to_heading_styles(design, heading_font, text_color)

    # Build list style
    list_style = resolved_design_to_list_style(design)

    # Build quote style
    quote_style = resolved_design_to_quote_style(design, body_font, text_color)

    # Build presentation layout from page geometry if available
    presentation_layout = _build_presentation_layout(design)

    return DocumentIR(
        org_id=org_id,
        locale=locale,
        color_theme=color_theme,
        typography=typography,
        body_style=body_style,
        heading_styles=heading_styles,
        list_style=list_style,
        quote_style=quote_style,
        hyperlink_style=HyperlinkStyle(
            link_color=hyperlink_color or color_theme.accent1,
            visited_color=hyperlink_visited or color_theme.accent1,
        ),
        presentation_layout=presentation_layout,
        page_geometry=design.page_geometry,
    )


def create_document_ir_from_policy(
    fonts,
    brand,
    *,
    policy: str = "default",
    locale: str = "en-US",
    org_id: str = "default",
    body_font: str = "Inter",
    heading_font: str = "Inter",
    monospace_font: str = "Courier New",
    color_theme: ColorTheme | None = None,
    page_geometry=None,
) -> DocumentIR:
    """Create DocumentIR directly from a policy configuration.

    This is a convenience function that combines PolicyAwareDesignEngine
    with the design bridge to create a DocumentIR in one step.

    Args:
        fonts: Font configuration
        brand: Brand configuration
        policy: Policy preset name
        locale: Locale code
        org_id: Organization identifier
        body_font: Font family for body text
        heading_font: Font family for headings
        monospace_font: Font family for monospace/code styles
        color_theme: Optional color theme
        page_geometry: Optional page geometry for layout calculations

    Returns:
        DocumentIR ready for emitters
    """
    engine = create_design_engine(
        fonts=fonts,
        brand=brand,
        policy=policy,
        locale=locale,
        page_geometry=page_geometry,
    )
    design = engine.resolve()

    return create_document_ir_from_design(
        design,
        org_id=org_id,
        locale=locale,
        body_font=body_font,
        heading_font=heading_font,
        monospace_font=monospace_font,
        color_theme=color_theme,
    )


__all__ = [
    "resolved_design_to_typography_scheme",
    "resolved_design_to_body_style",
    "resolved_design_to_heading_styles",
    "resolved_design_to_list_style",
    "resolved_design_to_quote_style",
    "create_document_ir_from_design",
    "create_document_ir_from_policy",
]
