"""Factory functions for creating StyleResolver from various inputs.

This module bridges the gap between the token-based generation pipeline
and the design-based StyleResolver system.
"""

from __future__ import annotations

from typing import Any, Dict, Optional

from tokenmoulds.design.color_defaults import (
    DEFAULT_PRIMARY,
    PURE_WHITE,
    TEXT_PRIMARY,
    TEXT_SECONDARY,
)
from tokenmoulds.design.engine import (
    Baseline,
    LigatureMode,
    NumberForm,
    NumberSpacing,
    OpenTypeSettings,
    ResolvedDesign,
    RoleTypography,
    SpacingScale,
    TrackingSettings,
    TypeScaleStep,
)
from tokenmoulds.design.units import pt_to_emu, pt_to_twips
from tokenmoulds.styles.resolver import StyleResolver


def _make_role_typography(
    font_family: str,
    font_size_pt: float,
    line_height_pt: float,
    space_before_pt: float = 0.0,
    space_after_pt: float = 0.0,
    tracking_twips: int = 0,
    ligatures: LigatureMode = "standard",
    number_form: NumberForm = "default",
    number_spacing: NumberSpacing = "default",
) -> RoleTypography:
    """Create a RoleTypography with sensible defaults."""
    return RoleTypography(
        font_family=font_family,
        font_size_pt=font_size_pt,
        line_height_pt=line_height_pt,
        kerning_emu=0,
        space_before_pt=space_before_pt,
        space_after_pt=space_after_pt,
        opentype=OpenTypeSettings(
            ligatures=ligatures,
            number_form=number_form,
            number_spacing=number_spacing,
            contextual_alternates=True,
        ),
        tracking=TrackingSettings(
            tracking_twips=tracking_twips,
            caps_tracking_twips=80,
            small_caps_tracking_twips=60,
        ),
    )


def create_style_resolver_from_tokens(
    tokens: Dict[str, Any],
    body_color: str = TEXT_PRIMARY,
    heading_color: str = TEXT_PRIMARY,
) -> StyleResolver:
    """Create a StyleResolver from a token payload.

    This bridges the token-based pipeline with the design-based StyleResolver.
    It extracts typography information from tokens to build the required
    ResolvedDesign structure.

    Args:
        tokens: Token payload from TokenGenerator
        body_color: Body text color (hex)
        heading_color: Heading text color (hex)

    Returns:
        StyleResolver configured for the token-based design
    """
    typography = tokens.get("typography", {})
    fonts = typography.get("fonts", {})
    scale_pt = typography.get("scale_pt", {})
    from tokenmoulds.design.layout_defaults import DEFAULT_BASELINE_EMU

    baseline_emu = typography.get("baseline_emu", DEFAULT_BASELINE_EMU)  # 12pt default

    # Extract font names
    body_font = fonts.get("minor", fonts.get("sans", "Inter"))
    heading_font = fonts.get("major", fonts.get("serif", body_font))

    # Calculate baseline in points
    baseline_pt = baseline_emu / 12700  # EMU to points conversion

    # Create Baseline
    baseline = Baseline(grid_emu=baseline_emu, grid_pt=baseline_pt)

    # Build type scale from tokens
    type_scale_steps = []
    scale_names = [
        "footnote",
        "caption",
        "body",
        "large",
        "h6",
        "h5",
        "h4",
        "h3",
        "h2",
        "h1",
    ]
    for idx, name in enumerate(scale_names):
        size_pt = scale_pt.get(name, baseline_pt * (1.0 + idx * 0.1))
        type_scale_steps.append(
            TypeScaleStep(
                index=idx,
                name=name,
                size_pt=size_pt,
                size_emu=pt_to_emu(size_pt),
                size_twips=pt_to_twips(size_pt),
            )
        )

    # Build roles from token styles
    styles = tokens.get("styles", {})
    body_style = styles.get("body", {}).get("base", {})
    heading_levels = styles.get("heading", {}).get("levels", {})

    # Body typography
    body_size = body_style.get("font_size_pt", baseline_pt)
    body_line_height = body_size * 1.4  # Approximate
    body_spacing = body_style.get("paragraph_spacing", {})

    roles: Dict[str, RoleTypography] = {
        "text.body.primary": _make_role_typography(
            font_family=body_font,
            font_size_pt=body_size,
            line_height_pt=body_line_height,
            space_before_pt=body_spacing.get("before_twips", 0) / 20,
            space_after_pt=body_spacing.get("after_twips", 0) / 20,
        ),
    }

    # Heading typography from token levels
    heading_role_map = {
        1: "display.primary",
        2: "display.secondary",
        3: "heading.section",
        4: "heading.subsection",
        5: "heading.minor",
        6: "heading.minor",
        7: "heading.minor",
        8: "heading.minor",
        9: "heading.minor",
    }

    for level in range(1, 10):
        level_key = f"h{level}"
        level_data = heading_levels.get(level_key, {})

        # Get size from level data or scale
        size_twips = level_data.get("font_size_twips")
        if size_twips:
            size_pt = size_twips / 20
        else:
            size_pt = scale_pt.get(level_key, baseline_pt * (2.5 - level * 0.2))

        # Calculate tracking (tighter for larger sizes)
        tracking = int(-10 * (3 - min(level, 3)))  # -20, -10, 0, 0, ...

        # Line height
        line_height = size_pt * 1.2

        # Spacing
        level_spacing = level_data.get("paragraph_spacing", {})
        space_before = level_spacing.get("before_twips", size_pt * 20) / 20
        space_after = level_spacing.get("after_twips", size_pt * 10) / 20

        role_name = heading_role_map[level]
        if role_name not in roles:  # Don't overwrite if already set
            roles[role_name] = _make_role_typography(
                font_family=heading_font,
                font_size_pt=size_pt,
                line_height_pt=line_height,
                space_before_pt=space_before,
                space_after_pt=space_after,
                tracking_twips=tracking,
                ligatures="standardContextual" if level <= 2 else "standard",
                number_form="lining",
            )

    # Caption role (smaller text)
    caption_size = scale_pt.get("caption", body_size * 0.85)
    roles["text.caption"] = _make_role_typography(
        font_family=body_font,
        font_size_pt=caption_size,
        line_height_pt=caption_size * 1.3,
        tracking_twips=15,  # Slightly looser for small text
        number_form="oldStyle",
    )

    # Spacing scale
    spacing = SpacingScale(
        baseline_pt=baseline_pt,
        scale={
            "xs": baseline_pt * 0.25,
            "sm": baseline_pt * 0.5,
            "md": baseline_pt,
            "lg": baseline_pt * 2,
            "xl": baseline_pt * 3,
            "paragraph": baseline_pt,
        },
    )

    # Colors from theme mapping
    colors_map = tokens.get("colors", {}).get("theme_mapping", {})
    design_colors = {
        "text.primary": colors_map.get("dk1", body_color),
        "text.secondary": colors_map.get("dk2", TEXT_SECONDARY),
        "accent.primary": colors_map.get("accent1", DEFAULT_PRIMARY),
        "background": colors_map.get("lt1", PURE_WHITE),
    }

    # Build ResolvedDesign
    resolved_design = ResolvedDesign(
        baseline=baseline,
        type_scale=tuple(type_scale_steps),
        roles=roles,
        spacing=spacing,
        colors=design_colors,
    )

    # Create and return StyleResolver
    return StyleResolver(
        design=resolved_design,
        body_font=body_font,
        heading_font=heading_font,
        body_color=body_color,
        heading_color=heading_color,
    )


def create_style_resolver_from_document_ir(
    document_ir,
    tokens: Optional[Dict[str, Any]] = None,
) -> StyleResolver:
    """Create a StyleResolver from a DocumentIR and optional tokens.

    This is a convenience function when you have a DocumentIR but still
    need the full StyleResolver for emitting all built-in styles.

    Args:
        document_ir: DocumentIR instance
        tokens: Optional token payload for additional design info

    Returns:
        StyleResolver configured from the DocumentIR
    """
    # Extract colors from DocumentIR
    body_color = document_ir.body_style.color
    heading_color = (
        document_ir.heading_styles[0].color
        if document_ir.heading_styles
        else body_color
    )

    if tokens:
        return create_style_resolver_from_tokens(
            tokens,
            body_color=body_color,
            heading_color=heading_color,
        )

    # Fallback: build minimal ResolvedDesign from DocumentIR
    body_font = document_ir.body_style.font_family
    heading_font = (
        document_ir.heading_styles[0].font_family
        if document_ir.heading_styles
        else body_font
    )

    # Get baseline from typography scheme or estimate
    if document_ir.typography:
        baseline_emu = document_ir.typography.baseline_emu
    else:
        baseline_emu = int(document_ir.body_style.font_size_pt * 12700)

    baseline_pt = baseline_emu / 12700
    baseline = Baseline(grid_emu=baseline_emu, grid_pt=baseline_pt)

    # Build minimal roles from DocumentIR styles
    roles = {
        "text.body.primary": _make_role_typography(
            font_family=body_font,
            font_size_pt=document_ir.body_style.font_size_pt,
            line_height_pt=document_ir.body_style.font_size_pt * 1.4,
        ),
    }

    heading_role_map = {
        1: "display.primary",
        2: "display.secondary",
        3: "heading.section",
        4: "heading.subsection",
        5: "heading.minor",
    }

    for heading in document_ir.heading_styles:
        role_name = heading_role_map.get(heading.level)
        if role_name and role_name not in roles:
            roles[role_name] = _make_role_typography(
                font_family=heading.font_family,
                font_size_pt=heading.font_size_pt,
                line_height_pt=heading.font_size_pt * 1.2,
                space_before_pt=heading.spacing.before_twips / 20,
                space_after_pt=heading.spacing.after_twips / 20,
            )

    # Caption
    roles["text.caption"] = _make_role_typography(
        font_family=body_font,
        font_size_pt=document_ir.body_style.font_size_pt * 0.85,
        line_height_pt=document_ir.body_style.font_size_pt * 1.1,
    )

    # Build minimal type scale
    type_scale = tuple(
        TypeScaleStep(
            index=i,
            name=name,
            size_pt=baseline_pt * (0.75 + i * 0.25),
            size_emu=pt_to_emu(baseline_pt * (0.75 + i * 0.25)),
            size_twips=pt_to_twips(baseline_pt * (0.75 + i * 0.25)),
        )
        for i, name in enumerate(
            ["footnote", "caption", "body", "large", "h6", "h5", "h4", "h3", "h2", "h1"]
        )
    )

    spacing = SpacingScale(
        baseline_pt=baseline_pt,
        scale={"paragraph": baseline_pt},
    )

    colors = {
        "text.primary": body_color,
        "accent.primary": document_ir.color_theme.accent1
        if document_ir.color_theme
        else DEFAULT_PRIMARY,
    }

    resolved_design = ResolvedDesign(
        baseline=baseline,
        type_scale=type_scale,
        roles=roles,
        spacing=spacing,
        colors=colors,
    )

    return StyleResolver(
        design=resolved_design,
        body_font=body_font,
        heading_font=heading_font,
        body_color=body_color,
        heading_color=heading_color,
    )
