"""Pure token-builder helpers for the DTCG generator."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional

from tokenmoulds.colors.transforms import contrast_color, darken, lighten
from tokenmoulds.design.units import EMU_PER_POINT, pt_to_twips as _pt_to_twips

if TYPE_CHECKING:
    from tokenmoulds.design.engine import ResolvedDesign


def build_color_tokens(
    raw: Mapping[str, Any], base_colors: Mapping[str, str]
) -> Dict[str, Any]:
    from tokenmoulds.design.color_defaults import (
        DEFAULT_PRIMARY,
        DEFAULT_SECONDARY,
        SURFACE_BLACK,
        SURFACE_DARK,
        SURFACE_LIGHTEST,
        SURFACE_MEDIUM,
        SURFACE_WHITE,
        TEXT_INVERSE,
        TEXT_MUTED,
        TEXT_PRIMARY,
        TEXT_SECONDARY,
    )

    raw_colors = raw.get("colors", {})
    surfaces = raw_colors.get("surfaces", {})
    text_raw = raw_colors.get("text", {})

    primary = base_colors.get("primary", DEFAULT_PRIMARY)
    secondary = base_colors.get("secondary", DEFAULT_SECONDARY)

    palette = {
        "primary": {
            "base": primary,
            "light": lighten(primary, 0.2),
            "dark": darken(primary, 0.2),
            "contrast": contrast_color(primary, 4.5),
        },
        "secondary": {
            "base": secondary,
            "light": lighten(secondary, 0.2),
            "dark": darken(secondary, 0.2),
            "contrast": contrast_color(secondary, 4.5),
        },
        "neutral": {
            "white": surfaces.get("white", SURFACE_WHITE),
            "light": surfaces.get("light", SURFACE_LIGHTEST),
            "medium": surfaces.get("medium", SURFACE_MEDIUM),
            "dark": surfaces.get("dark", SURFACE_DARK),
            "black": surfaces.get("black", SURFACE_BLACK),
        },
    }

    # OOXML theme mapping uses tints/shades for a cohesive Office palette
    # accent1/2: Primary and secondary brand colors
    # accent3/4: Lightened tints (for backgrounds, subtle accents)
    # accent5/6: Darkened shades (for emphasis, borders)
    # This creates better chart series and table styles than hue rotation
    theme_mapping = {
        "dk1": palette["neutral"]["black"],
        "lt1": palette["neutral"]["white"],
        "dk2": palette["neutral"]["dark"],
        "lt2": palette["neutral"]["light"],
        "accent1": primary,
        "accent2": secondary,
        "accent3": palette["primary"]["light"],  # Tint of primary
        "accent4": palette["secondary"]["light"],  # Tint of secondary
        "accent5": palette["primary"]["dark"],  # Shade of primary
        "accent6": palette["secondary"]["dark"],  # Shade of secondary
        "hlink": text_raw.get("link", darken(primary, 0.1)),
        "folHlink": text_raw.get("link_visited", darken(primary, 0.2)),
    }

    text = {
        "primary": text_raw.get("primary", TEXT_PRIMARY),
        "secondary": text_raw.get("secondary", TEXT_SECONDARY),
        "inverse": text_raw.get("inverse", TEXT_INVERSE),
        "muted": text_raw.get("tertiary", TEXT_MUTED),
    }

    return {
        "palette": palette,
        "theme_mapping": theme_mapping,
        "text": text,
    }


def build_typography_tokens(
    raw: Mapping[str, Any],
    font_pair: Mapping[str, str],
    baseline_emu: int,
) -> Dict[str, Any]:
    families = raw.get("typography", {}).get("families", {})
    fonts = {
        "major": font_pair.get("major", families.get("sans", "Inter")),
        "minor": font_pair.get("minor", families.get("sans", "Inter")),
        "serif": font_pair.get("serif", families.get("serif", "Roboto Slab")),
        "mono": families.get("mono", "Consolas"),
    }

    line_height = raw.get("typography", {}).get("line_heights", {}).get("normal", 1.4)

    # Use resolved type scale from DesignResolutionEngine if available
    resolved: Optional[Any] = raw.get("resolved_design")
    if resolved is not None and hasattr(resolved, "type_scale"):
        # Build named scale from the baseline-snapped modular type scale
        scale_emu: Dict[str, int] = {}
        scale_pt: Dict[str, float] = {}
        for step in resolved.type_scale:
            scale_emu[step.name] = step.size_emu
            scale_pt[step.name] = round(step.size_pt, 2)
        # Also provide size_N aliases for backward compatibility
        for i, step in enumerate(resolved.type_scale):
            scale_emu[f"size_{i + 1}"] = step.size_emu
            scale_pt[f"size_{i + 1}"] = round(step.size_pt, 2)
    else:
        # Fallback: hardcoded multiplier scale
        scale_multipliers = [0.75, 0.875, 1, 1.125, 1.25, 1.5, 1.75, 2, 2.5, 3]
        base_size_pt = baseline_emu / EMU_PER_POINT
        scale_emu = {}
        scale_pt = {}
        for i, multiplier in enumerate(scale_multipliers):
            size_pt = base_size_pt * multiplier
            size_emu_val = int(size_pt * EMU_PER_POINT)
            scale_name = f"size_{i + 1}"
            scale_emu[scale_name] = size_emu_val
            scale_pt[scale_name] = round(size_pt, 2)

    return {
        "fonts": fonts,
        "scale_emu": scale_emu,
        "scale_pt": scale_pt,
        "line_height": line_height,
        "baseline_emu": baseline_emu,
    }


def build_spacing_tokens(baseline_emu: int) -> Dict[str, Any]:
    spacing_multipliers = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8]
    scale_emu: Dict[str, int] = {}
    scale_pt: Dict[str, float] = {}
    for i, multiplier in enumerate(spacing_multipliers):
        spacing_emu = int(baseline_emu * multiplier)
        spacing_pt = spacing_emu / EMU_PER_POINT
        scale_name = f"space_{i + 1}"
        scale_emu[scale_name] = spacing_emu
        scale_pt[scale_name] = round(spacing_pt, 2)
    return {
        "scale_emu": scale_emu,
        "scale_pt": scale_pt,
        "baseline_emu": baseline_emu,
    }


def build_grid_tokens(baseline_emu: int) -> Dict[str, Any]:
    return {
        "baseline": {
            "emu": baseline_emu,
            "pt": baseline_emu / EMU_PER_POINT,
            "description": "Base grid unit for vertical rhythm",
        },
        "margins": {
            "top_emu": baseline_emu * 4,
            "bottom_emu": baseline_emu * 4,
            "left_emu": baseline_emu * 4,
            "right_emu": baseline_emu * 4,
            "description": "Page margins aligned to baseline grid",
        },
    }


def _baseline_twips_from_emu(baseline_emu: int) -> float:
    return baseline_emu / 635


def _snap_to_baseline_twips(value_twips: float, baseline_twips: float) -> int:
    if baseline_twips <= 0:
        return int(round(value_twips))
    snapped = round(value_twips / baseline_twips) * baseline_twips
    return int(round(snapped))


def _snap_pt_to_twips(value_pt: float, baseline_twips: float) -> int:
    return _snap_to_baseline_twips(value_pt * 20, baseline_twips)


def _line_height_twips(
    font_size_pt: float, multiplier: float, baseline_twips: float
) -> int:
    raw_twips = font_size_pt * multiplier * 20
    return _snap_to_baseline_twips(raw_twips, baseline_twips)


def build_style_tokens(
    colors: Mapping[str, Any],
    typography: Mapping[str, Any],
    spacing: Mapping[str, Any],
    *,
    brand_tone: float,
    resolved_design: Optional["ResolvedDesign"] = None,
) -> Dict[str, Any]:
    theme = colors["theme_mapping"]
    palette = colors["palette"]
    fonts = typography["fonts"]
    scale_pt = typography["scale_pt"]
    spacing_pt = spacing["scale_pt"]
    from tokenmoulds.design.layout_defaults import DEFAULT_BASELINE_EMU

    baseline_emu = int(typography.get("baseline_emu", DEFAULT_BASELINE_EMU))
    baseline_twips = _baseline_twips_from_emu(baseline_emu)

    # When we have a resolved design, use baseline-snapped role typography
    rd = resolved_design
    roles = rd.roles if rd else {}

    # Spacing from resolved design or fallback
    if rd:
        space_paragraph_twips = rd.spacing.get_twips("paragraph")
        space_section_twips = rd.spacing.get_twips("section")
        space_small_twips = rd.spacing.get_twips("small")
        space_indent_twips = rd.spacing.get_twips("indent")
        space_hanging_twips = rd.spacing.get_twips("hanging")
        space_micro_twips = rd.spacing.get_twips("micro")
        space_tiny_twips = rd.spacing.get_twips("tiny")
    else:
        space_micro_twips = _snap_pt_to_twips(
            spacing_pt.get("space_1", 0), baseline_twips
        )
        space_tiny_twips = _snap_pt_to_twips(
            spacing_pt.get("space_2", 0), baseline_twips
        )
        space_small_twips = _snap_pt_to_twips(
            spacing_pt.get("space_3", 0), baseline_twips
        )
        space_paragraph_twips = _snap_pt_to_twips(
            spacing_pt.get("space_4", 0), baseline_twips
        )
        space_section_twips = _snap_pt_to_twips(
            spacing_pt.get("space_5", 0), baseline_twips
        )
        space_indent_twips = space_paragraph_twips
        space_hanging_twips = space_tiny_twips

    # --- Heading styles from resolved roles ---
    display_role = roles.get("display.section")
    heading_font = fonts.get("major", fonts.get("serif", "Inter"))
    if display_role:
        heading_size_pt = display_role.font_size_pt
        heading_lh_multiplier = display_role.line_height_multiplier
        heading_lh_twips = display_role.line_height_twips
    else:
        heading_size_pt = scale_pt.get("h5", scale_pt.get("size_6", 16.5))
        heading_lh_multiplier = 1.15
        heading_lh_twips = _line_height_twips(heading_size_pt, 1.15, baseline_twips)

    heading_base = {
        "font_family": heading_font,
        "font_size_pt": heading_size_pt,
        "font_weight": 700,
        "color": theme["accent1"],
        "kerning_em": round(0.03 + (brand_tone * 0.01), 3),
        "line_height_multiplier": round(heading_lh_multiplier, 3),
        "line_height_twips": heading_lh_twips,
        "paragraph_spacing": {
            "before_twips": space_section_twips,
            "after_twips": space_paragraph_twips,
        },
        "character_link": {
            "font_family": heading_font,
            "font_weight": 700,
            "color": theme["accent1"],
        },
    }

    # Add OpenType and tracking if resolved
    if display_role:
        heading_base["opentype"] = {
            "ligatures": display_role.opentype.ligatures,
            "number_form": display_role.opentype.number_form,
            "contextual_alternates": display_role.opentype.contextual_alternates,
        }
        heading_base["tracking_twips"] = display_role.tracking.tracking_twips
        heading_base["caps_tracking_twips"] = display_role.tracking.caps_tracking_twips

    # Build heading levels from type scale (h1=largest to h9=smallest)
    # Map heading levels to type scale step names
    _HEADING_SCALE_NAMES = [
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "large",
        "body",
        "caption",
    ]
    heading_levels: Dict[str, Dict[str, Any]] = {}

    for idx in range(1, 10):
        scale_name = (
            _HEADING_SCALE_NAMES[idx - 1]
            if idx - 1 < len(_HEADING_SCALE_NAMES)
            else None
        )

        # Try to get size from resolved type scale
        resolved_size: float | None = None

        if rd and scale_name:
            step = rd.get_type_step(scale_name)
            if step:
                resolved_size = step.size_pt

        if resolved_size is None:
            # Fallback to numbered scale
            fallback_keys = [
                "size_10",
                "size_9",
                "size_8",
                "size_7",
                "size_6",
                "size_5",
                "size_4",
                "size_3",
                "size_2",
            ]
            if idx - 1 < len(fallback_keys):
                resolved_size = scale_pt.get(
                    fallback_keys[idx - 1], scale_pt.get("size_1", 8.0)
                )
            else:
                resolved_size = scale_pt.get("size_1", 8.0)

        heading_size: float = resolved_size if resolved_size is not None else 8.0

        # Calculate baseline-snapped line height for this heading size
        if rd:
            line_height_twips_val = int(
                round(rd.baseline.snap_up_pt(heading_size * heading_lh_multiplier) * 20)
            )
        else:
            line_height_twips_val = _line_height_twips(
                heading_size, heading_lh_multiplier, baseline_twips
            )

        heading_levels[f"h{idx}"] = {
            "font_size_pt": heading_size,
            "font_size_twips": _pt_to_twips(heading_size),
            "line_height_twips": line_height_twips_val,
            "outline_level": idx - 1,
            "paragraph_spacing": {
                "before_twips": space_section_twips
                if idx == 1
                else space_paragraph_twips,
                "after_twips": space_paragraph_twips,
            },
        }

    # --- Body styles from resolved roles ---
    body_role = roles.get("text.body.primary")
    body_font = fonts.get("minor", "Inter")
    if body_role:
        body_size_pt = body_role.font_size_pt
        body_lh = body_role.line_height_multiplier
        body_lh_twips = body_role.line_height_twips
        body_size_twips = body_role.font_size_twips
    else:
        body_size_pt = scale_pt.get("body", scale_pt.get("size_4", 11.0))
        body_lh = typography["line_height"]
        body_lh_twips = _line_height_twips(body_size_pt, body_lh, baseline_twips)
        body_size_twips = _pt_to_twips(body_size_pt)

    body_base = {
        "font_family": body_font,
        "font_size_pt": body_size_pt,
        "font_size_twips": body_size_twips,
        "line_height_multiplier": round(body_lh, 3),
        "line_height_twips": body_lh_twips,
        "color": colors["text"]["primary"],
        "paragraph_spacing": {
            "before_twips": space_tiny_twips,
            "after_twips": space_paragraph_twips,
        },
    }

    if body_role:
        body_base["opentype"] = {
            "ligatures": body_role.opentype.ligatures,
            "number_form": body_role.opentype.number_form,
            "number_spacing": body_role.opentype.number_spacing,
            "contextual_alternates": body_role.opentype.contextual_alternates,
        }
        body_base["tracking_twips"] = body_role.tracking.tracking_twips

    # --- List styles ---
    list_base = {
        "font_family": body_font,
        "font_size_pt": body_size_pt,
        "line_height_multiplier": round(body_lh, 3),
        "line_height_twips": body_lh_twips,
        "indent_twips": space_indent_twips,
        "hanging_twips": space_hanging_twips,
        "tab_stop_twips": space_indent_twips,
        "color": colors["text"]["primary"],
    }

    list_levels: Dict[str, Dict[str, Any]] = {}
    for level in range(1, 10):
        indent = _snap_to_baseline_twips(space_indent_twips * level, baseline_twips)
        list_levels[f"level_{level}"] = {
            "indent_twips": indent,
            "hanging_twips": space_hanging_twips,
            "tab_stop_twips": indent,
        }

    # --- Quote styles ---
    quote_border_width_twips = 15
    quote_indent_twips = _snap_to_baseline_twips(
        space_small_twips + quote_border_width_twips, baseline_twips
    )

    quote_base = {
        "font_family": fonts.get("serif", heading_font),
        "font_size_pt": body_size_pt,
        "line_height_multiplier": round(body_lh, 3),
        "line_height_twips": body_lh_twips,
        "italic": True,
        "color": palette["neutral"]["dark"],
        "border_color": palette["neutral"]["light"],
        "border_width_twips": quote_border_width_twips,
        "indent_twips": quote_indent_twips,
        "paragraph_spacing": {
            "before_twips": quote_indent_twips,
            "after_twips": quote_indent_twips,
        },
    }

    # --- Table styles from resolved roles ---
    table_role = roles.get("text.table.body")
    table_size_pt = (
        table_role.font_size_pt
        if table_role
        else scale_pt.get("caption", scale_pt.get("size_3", 9.0))
    )

    table_border_color = palette["neutral"]["medium"]
    table_border_width_twips = 12
    table_header_fill = palette["neutral"]["light"]
    table_body_fill = palette["neutral"]["white"]
    table_base = {
        "font_family": body_font,
        "font_size_pt": table_size_pt,
        "cell_padding_twips": {
            "x": space_tiny_twips,
            "y": space_micro_twips,
        },
        "border_color": table_border_color,
        "border_width_twips": table_border_width_twips,
        "border": f"{table_border_width_twips / 20:.2f}pt solid {table_border_color}",
        "header_fill": table_header_fill,
        "header_text_color": contrast_color(table_header_fill, 4.5),
        "body_fill": table_body_fill,
        "body_text_color": colors["text"]["primary"],
    }

    if table_role:
        table_base["opentype"] = {
            "ligatures": table_role.opentype.ligatures,
            "number_form": table_role.opentype.number_form,
            "number_spacing": table_role.opentype.number_spacing,
        }

    table_accents: Dict[str, Dict[str, Any]] = {}
    for accent_key, color in theme.items():
        if accent_key.startswith("accent"):
            table_accents[accent_key] = {
                "fill": color,
                "text": (
                    colors["text"]["inverse"]
                    if accent_key in {"accent2", "accent4"}
                    else colors["text"]["primary"]
                ),
                "border": (
                    palette["neutral"]["white"]
                    if accent_key in {"accent2", "accent4"}
                    else palette["neutral"]["light"]
                ),
            }

    character_base = {
        "font_family": body_font,
        "color": colors["text"]["primary"],
    }

    hyperlink_base = {
        "link": {
            "color": theme["hlink"],
            "underline": True,
        },
        "visited": {
            "color": theme["folHlink"],
            "underline": True,
        },
    }

    toc_base = {
        "font_family": body_font,
        "tab_leader": ".",
        "color": colors["text"]["primary"],
        "page_number_color": colors["text"]["secondary"],
    }

    return {
        "heading": {
            "base": heading_base,
            "levels": heading_levels,
        },
        "body": {"base": body_base},
        "list": {
            "base": list_base,
            "levels": list_levels,
        },
        "quote": {"base": quote_base},
        "table": {
            "base": table_base,
            "accents": table_accents,
        },
        "character": {"base": character_base},
        "hyperlink": hyperlink_base,
        "toc": {"base": toc_base},
    }
