"""Shared token extraction and normalization utilities.

Centralizes the token payload extraction logic used across MCP tools
(template_generator, document_creator, document).
"""

from __future__ import annotations

from typing import Any, Dict


def escape_xml(text: str) -> str:
    """Escape XML special characters in text content."""
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&apos;")
    )


def extract_token_payload(tokens: Dict[str, Any]) -> Dict[str, Any]:
    """Extract token payload -- tokens are complete from build_recipe().

    No normalization or fallbacks.  If tokens are incomplete, that is a
    pipeline bug to fix upstream, not paper over here.

    Callers that need DTCG-to-internal conversion or normalization should
    apply :func:`convert_dtcg_to_internal` or :func:`normalize_tokens`
    explicitly after calling this function.
    """
    # DTCG format -- pass through (emitters handle DTCG directly)
    if any(key.startswith("$") for key in tokens.keys()) or "inputs" in tokens:
        return tokens

    # Legacy format with nested "tokens" key
    if "tokens" in tokens:
        return tokens["tokens"]

    # Already in internal format
    return tokens


def convert_dtcg_to_internal(tokens: Dict[str, Any]) -> Dict[str, Any]:
    """Convert DTCG format to internal format for emitters.

    Ported from template_generator.py -- handles the full DTCG structure
    including ``color``, ``typography``, and ``inputs`` groups.
    """
    result: Dict[str, Any] = {}

    # Extract from inputs group if present
    inputs = tokens.get("inputs", {})

    # Colors
    if "color" in tokens:
        colors_raw = tokens["color"]
        palette = {}
        theme_mapping = {}

        # Extract palette colors
        if "palette" in colors_raw:
            for key, value in colors_raw["palette"].items():
                if isinstance(value, dict):
                    if "$value" in value:
                        palette[key] = {"base": value["$value"]}
                    else:
                        # Nested structure
                        for sub_key, sub_value in value.items():
                            if isinstance(sub_value, dict) and "$value" in sub_value:
                                palette[f"{key}_{sub_key}"] = {
                                    "base": sub_value["$value"]
                                }

        # Extract theme mapping
        if "theme" in colors_raw:
            for slot, value in colors_raw["theme"].items():
                if isinstance(value, dict) and "$value" in value:
                    theme_mapping[slot] = value["$value"]

        result["colors"] = {"palette": palette, "theme_mapping": theme_mapping}

    # Typography
    if "typography" in tokens:
        typo = tokens["typography"]
        fonts = {}

        if "font" in typo:
            font_group = typo["font"]
            if "sans" in font_group:
                sans = font_group["sans"]
                fonts["minor"] = (
                    sans.get("$value", sans) if isinstance(sans, dict) else sans
                )
                fonts["body"] = fonts["minor"]
            if "serif" in font_group:
                serif = font_group["serif"]
                fonts["major"] = (
                    serif.get("$value", serif) if isinstance(serif, dict) else serif
                )
                fonts["heading"] = fonts["major"]

        baseline = 11.0
        if "baseline" in typo:
            bl = typo["baseline"]
            if isinstance(bl, dict) and "$value" in bl:
                val = bl["$value"]
                if isinstance(val, dict):
                    baseline = val.get("value", 11.0)
                else:
                    baseline = float(val)

        result["typography"] = {
            "fonts": fonts if fonts else {"major": "Arial", "minor": "Arial"},
            "baseline_emu": int(baseline * 12700),  # pt to EMU
        }

    # From inputs
    if inputs:
        if "color" in inputs:
            color_inputs = inputs["color"]
            if "primary" in color_inputs:
                primary = color_inputs["primary"]
                primary_val = (
                    primary.get("$value", primary)
                    if isinstance(primary, dict)
                    else primary
                )
                result.setdefault("colors", {}).setdefault("palette", {})["primary"] = {
                    "base": primary_val
                }
            if "secondary" in color_inputs:
                secondary = color_inputs["secondary"]
                secondary_val = (
                    secondary.get("$value", secondary)
                    if isinstance(secondary, dict)
                    else secondary
                )
                result.setdefault("colors", {}).setdefault("palette", {})[
                    "secondary"
                ] = {"base": secondary_val}

        if "font" in inputs:
            font_inputs = inputs["font"]
            fonts = result.setdefault("typography", {}).setdefault("fonts", {})
            if "sans" in font_inputs:
                sans = font_inputs["sans"]
                fonts["minor"] = (
                    sans.get("$value", sans) if isinstance(sans, dict) else sans
                )
            if "serif" in font_inputs:
                serif = font_inputs["serif"]
                fonts["major"] = (
                    serif.get("$value", serif) if isinstance(serif, dict) else serif
                )

    return result


def _get_color(palette: Dict[str, Any], key: str, default: str) -> str:
    """Get a color from palette, handling nested structures."""
    if key in palette:
        val = palette[key]
        if isinstance(val, str):
            return val
        if isinstance(val, dict):
            return val.get("base", val.get("$value", default))
    return default


def normalize_tokens(tokens: Dict[str, Any]) -> Dict[str, Any]:
    """Normalize tokens to ensure all required fields exist.

    Adds sensible defaults for colors, typography, styles, etc. when they
    are missing from the payload.
    """
    colors = tokens.get("colors", {})
    typography = tokens.get("typography", {})
    styles = tokens.get("styles", {})

    fonts = typography.get("fonts", {})
    body_font = fonts.get("minor", fonts.get("body", "Arial"))
    heading_font = fonts.get("major", fonts.get("heading", body_font))

    palette = colors.get("palette", {})
    from tokenmoulds.design.color_defaults import (
        DEFAULT_PRIMARY,
        DEFAULT_SECONDARY,
        SURFACE_DARKEST,
    )

    primary_color = _get_color(palette, "primary", DEFAULT_PRIMARY)
    secondary_color = _get_color(palette, "secondary", DEFAULT_SECONDARY)
    text_color = _get_color(palette, "text", SURFACE_DARKEST)

    if "theme_mapping" not in colors:
        from tokenmoulds.design.color_defaults import DEFAULT_THEME_MAPPING

        colors["theme_mapping"] = {
            "dk1": DEFAULT_THEME_MAPPING["dk1"],
            "lt1": DEFAULT_THEME_MAPPING["lt1"],
            "dk2": text_color,
            "lt2": DEFAULT_THEME_MAPPING["lt2"],
            "accent1": primary_color,
            "accent2": secondary_color,
            "accent3": DEFAULT_THEME_MAPPING["accent3"],
            "accent4": DEFAULT_THEME_MAPPING["accent4"],
            "accent5": DEFAULT_THEME_MAPPING["accent5"],
            "accent6": DEFAULT_THEME_MAPPING["accent6"],
            "hlink": primary_color,
            "folHlink": secondary_color,
        }

    if "scale_pt" not in typography:
        typography["scale_pt"] = {
            "body": 11.0,
            "small": 9.0,
            "h1": 24.0,
            "h2": 18.0,
            "h3": 14.0,
            "h4": 12.0,
        }

    if "body" not in styles:
        styles["body"] = {
            "base": {
                "font_family": body_font,
                "font_size_pt": typography["scale_pt"]["body"],
                "color": text_color,
                "paragraph_spacing": {"before_twips": 0, "after_twips": 200},
            }
        }

    if "heading" not in styles:
        scale = typography["scale_pt"]
        styles["heading"] = {
            "base": {"font_family": heading_font, "color": text_color},
            "levels": {
                "h1": {
                    "font_size_twips": int(scale.get("h1", 24) * 20),
                    "paragraph_spacing": {"before_twips": 480, "after_twips": 120},
                    "outline_level": 0,
                },
                "h2": {
                    "font_size_twips": int(scale.get("h2", 18) * 20),
                    "paragraph_spacing": {"before_twips": 360, "after_twips": 120},
                    "outline_level": 1,
                },
                "h3": {
                    "font_size_twips": int(scale.get("h3", 14) * 20),
                    "paragraph_spacing": {"before_twips": 240, "after_twips": 60},
                    "outline_level": 2,
                },
                "h4": {
                    "font_size_twips": int(scale.get("h4", 12) * 20),
                    "paragraph_spacing": {"before_twips": 240, "after_twips": 60},
                    "outline_level": 3,
                },
            },
        }

    if "list" not in styles:
        from tokenmoulds.design.layout_defaults import (
            list_hanging_twips,
            list_indent_twips,
        )

        styles["list"] = {
            "base": {
                "indent_twips": list_indent_twips(),
                "hanging_twips": list_hanging_twips(),
            }
        }

    if "quote" not in styles:
        from tokenmoulds.design.color_defaults import TEXT_MUTED
        from tokenmoulds.design.layout_defaults import (
            quote_border_width_twips,
            quote_indent_twips,
        )

        styles["quote"] = {
            "base": {
                "font_family": body_font,
                "font_size_pt": typography["scale_pt"]["body"],
                "color": TEXT_MUTED,
                "italic": True,
                "indent_twips": quote_indent_twips(),
                "border_color": primary_color,
                "border_width_twips": quote_border_width_twips(),
                "paragraph_spacing": {"before_twips": 200, "after_twips": 200},
            }
        }

    if "table" not in styles:
        from tokenmoulds.design.color_defaults import (
            BORDER_DEFAULT,
            SURFACE_LIGHT,
            SURFACE_WHITE,
        )
        from tokenmoulds.design.layout_defaults import table_cell_padding_twips

        styles["table"] = {
            "base": {
                "style_id": "LightGrid-Accent1",  # Theme-aware table style
                "font_family": body_font,
                "font_size_pt": typography["scale_pt"].get("small", 9),
                "border_width_twips": 15,
                "border_color": BORDER_DEFAULT,
                "header_fill": SURFACE_LIGHT,
                "body_fill": SURFACE_WHITE,
                "cell_padding_twips": table_cell_padding_twips(),
            }
        }
    elif "style_id" not in styles["table"].get("base", {}):
        # Ensure style_id exists even if table styles are partially defined
        styles["table"].setdefault("base", {})["style_id"] = "LightGrid-Accent1"

    return {
        "colors": colors,
        "typography": typography,
        "styles": styles,
        "spacing": tokens.get("spacing", {}),
        "grid": tokens.get("grid", {}),
    }
