"""DTCG token file emitter.

Emits TokenFile objects to .tokens.json format.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict, Optional, Union

from tokenmoulds.dtcg.types import (
    BorderValue,
    ColorValue,
    DimensionValue,
    DurationValue,
    GradientStop,
    ShadowValue,
    StrokeStyleValue,
    Token,
    TokenExtensions,
    TokenFile,
    TokenGroup,
    TokenType,
    TransitionValue,
    TypographyValue,
)


def emit_tokens(token_file: TokenFile, path: Path, indent: int = 2) -> None:
    """Emit a TokenFile to a .tokens.json file.

    Args:
        token_file: The token file to emit
        path: Output path
        indent: JSON indentation (default 2)
    """
    content = emit_tokens_to_string(token_file, indent=indent)
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)


def emit_tokens_to_dict(token_file: TokenFile) -> Dict[str, Any]:
    """Emit a TokenFile to a plain dictionary.

    Args:
        token_file: The token file to emit

    Returns:
        Dictionary in DTCG format
    """
    return _emit_token_file(token_file)


def emit_tokens_to_string(token_file: TokenFile, indent: int = 2) -> str:
    """Emit a TokenFile to a JSON string.

    Args:
        token_file: The token file to emit
        indent: JSON indentation (default 2)

    Returns:
        JSON string in DTCG format
    """
    data = _emit_token_file(token_file)
    return json.dumps(data, indent=indent, ensure_ascii=False)


def _emit_token_file(token_file: TokenFile) -> Dict[str, Any]:
    """Convert TokenFile to dictionary."""
    result: Dict[str, Any] = {}

    # Emit root-level tokens (no inherited type)
    for name, token in token_file.tokens.items():
        result[name] = _emit_token(token, inherited_type=None)

    # Emit groups (no inherited type at root)
    for name, group in token_file.groups.items():
        result[name] = _emit_group(group, inherited_type=None)

    return result


def _emit_group(
    group: TokenGroup,
    inherited_type: Optional[TokenType],
) -> Dict[str, Any]:
    """Convert TokenGroup to dictionary.

    Args:
        group: The token group to emit
        inherited_type: The type inherited from parent group (if any)
    """
    result: Dict[str, Any] = {}

    # Only emit $type if it differs from inherited type
    if group.type is not None and group.type != inherited_type:
        result["$type"] = group.type.value

    if group.description is not None:
        result["$description"] = group.description

    if group.deprecated is not None:
        result["$deprecated"] = group.deprecated

    if group.extends is not None:
        result["$extends"] = group.extends

    if group.extensions is not None:
        result["$extensions"] = group.extensions

    # Determine effective type for children
    effective_type = group.type if group.type is not None else inherited_type

    # Emit tokens in this group
    for name, token in group.tokens.items():
        result[name] = _emit_token(token, inherited_type=effective_type)

    # Emit nested groups
    for name, nested_group in group.groups.items():
        result[name] = _emit_group(nested_group, inherited_type=effective_type)

    return result


def _emit_token(
    token: Token,
    inherited_type: Optional[TokenType],
) -> Dict[str, Any]:
    """Convert Token to dictionary.

    Args:
        token: The token to emit
        inherited_type: The type inherited from parent group (if any)
    """
    result: Dict[str, Any] = {}

    # Value is required
    result["$value"] = _emit_value(token.value, token.type)

    # Only emit $type if it differs from inherited type
    if token.type is not None and token.type != inherited_type:
        result["$type"] = token.type.value

    # Optional properties
    if token.description is not None:
        result["$description"] = token.description

    if token.deprecated is not None:
        result["$deprecated"] = token.deprecated

    # Extensions
    if token.extensions is not None:
        result["$extensions"] = _emit_extensions(token.extensions)

    return result


def _emit_value(value: Any, token_type: Optional[TokenType]) -> Any:
    """Convert a token value to JSON-serializable format."""
    # References are passed through as-is
    if isinstance(value, str):
        return value

    # Handle based on value type (dataclass instances)
    if isinstance(value, ColorValue):
        return _emit_color_value(value)

    if isinstance(value, DimensionValue):
        return _emit_dimension_value(value)

    if isinstance(value, DurationValue):
        return _emit_duration_value(value)

    if isinstance(value, StrokeStyleValue):
        return _emit_stroke_style_value(value)

    if isinstance(value, BorderValue):
        return _emit_border_value(value)

    if isinstance(value, TransitionValue):
        return _emit_transition_value(value)

    if isinstance(value, ShadowValue):
        return _emit_shadow_value(value)

    if isinstance(value, TypographyValue):
        return _emit_typography_value(value)

    if isinstance(value, GradientStop):
        return _emit_gradient_stop(value)

    # Handle lists (shadows, gradients, font families, cubic bezier)
    if isinstance(value, list):
        if value and isinstance(value[0], ShadowValue):
            return [_emit_shadow_value(v) for v in value]
        if value and isinstance(value[0], GradientStop):
            return [_emit_gradient_stop(v) for v in value]
        # Primitive list (cubic bezier, font families)
        return value

    # Primitive types (number, int, float, bool)
    return value


def _emit_color_value(color: ColorValue) -> Union[str, Dict[str, Any]]:
    """Convert ColorValue to JSON format.

    Prefers hex string for simplicity, uses object form if color space is specified.
    """
    # If we have full color space info, emit as object
    if color.color_space is not None and color.components is not None:
        result: Dict[str, Any] = {
            "colorSpace": color.color_space,
            "components": color.components,
        }
        if color.alpha is not None and color.alpha != 1.0:
            result["alpha"] = color.alpha
        # Also include hex for convenience
        if color.hex:
            result["hex"] = color.hex
        return result

    # Otherwise emit as simple hex string
    return color.to_hex()


def _emit_dimension_value(dim: DimensionValue) -> Dict[str, Any]:
    """Convert DimensionValue to JSON format."""
    return {
        "value": dim.value,
        "unit": dim.unit,
    }


def _emit_duration_value(dur: DurationValue) -> Dict[str, Any]:
    """Convert DurationValue to JSON format."""
    return {
        "value": dur.value,
        "unit": dur.unit,
    }


def _emit_stroke_style_value(style: StrokeStyleValue) -> Union[str, Dict[str, Any]]:
    """Convert StrokeStyleValue to JSON format."""
    # Keyword style
    if style.keyword is not None:
        return style.keyword

    # Object style with dash array
    result: Dict[str, Any] = {}
    if style.dash_array is not None:
        result["dashArray"] = [
            _emit_value(d, TokenType.DIMENSION) if isinstance(d, DimensionValue) else d
            for d in style.dash_array
        ]
    if style.line_cap is not None:
        result["lineCap"] = style.line_cap

    return result


def _emit_border_value(border: BorderValue) -> Dict[str, Any]:
    """Convert BorderValue to JSON format."""
    return {
        "color": _emit_value(border.color, TokenType.COLOR)
        if not isinstance(border.color, str)
        else border.color,
        "width": _emit_value(border.width, TokenType.DIMENSION)
        if not isinstance(border.width, str)
        else border.width,
        "style": _emit_value(border.style, TokenType.STROKE_STYLE)
        if not isinstance(border.style, str)
        else border.style,
    }


def _emit_transition_value(transition: TransitionValue) -> Dict[str, Any]:
    """Convert TransitionValue to JSON format."""
    return {
        "duration": _emit_value(transition.duration, TokenType.DURATION)
        if not isinstance(transition.duration, str)
        else transition.duration,
        "delay": _emit_value(transition.delay, TokenType.DURATION)
        if not isinstance(transition.delay, str)
        else transition.delay,
        "timingFunction": transition.timing_function
        if isinstance(transition.timing_function, (str, list))
        else transition.timing_function,
    }


def _emit_shadow_value(shadow: ShadowValue) -> Dict[str, Any]:
    """Convert ShadowValue to JSON format."""
    result: Dict[str, Any] = {
        "color": _emit_value(shadow.color, TokenType.COLOR)
        if not isinstance(shadow.color, str)
        else shadow.color,
        "offsetX": _emit_value(shadow.offset_x, TokenType.DIMENSION)
        if not isinstance(shadow.offset_x, str)
        else shadow.offset_x,
        "offsetY": _emit_value(shadow.offset_y, TokenType.DIMENSION)
        if not isinstance(shadow.offset_y, str)
        else shadow.offset_y,
        "blur": _emit_value(shadow.blur, TokenType.DIMENSION)
        if not isinstance(shadow.blur, str)
        else shadow.blur,
        "spread": _emit_value(shadow.spread, TokenType.DIMENSION)
        if not isinstance(shadow.spread, str)
        else shadow.spread,
    }
    if shadow.inset:
        result["inset"] = True
    return result


def _emit_gradient_stop(stop: GradientStop) -> Dict[str, Any]:
    """Convert GradientStop to JSON format."""
    return {
        "color": _emit_value(stop.color, TokenType.COLOR)
        if not isinstance(stop.color, str)
        else stop.color,
        "position": stop.position,
    }


def _emit_typography_value(typo: TypographyValue) -> Dict[str, Any]:
    """Convert TypographyValue to JSON format."""
    return {
        "fontFamily": typo.font_family,
        "fontSize": _emit_value(typo.font_size, TokenType.DIMENSION)
        if not isinstance(typo.font_size, str)
        else typo.font_size,
        "fontWeight": typo.font_weight,
        "letterSpacing": _emit_value(typo.letter_spacing, TokenType.DIMENSION)
        if not isinstance(typo.letter_spacing, str)
        else typo.letter_spacing,
        "lineHeight": typo.line_height,
    }


def _emit_extensions(ext: TokenExtensions) -> Dict[str, Any]:
    """Convert TokenExtensions to JSON format."""
    result: Dict[str, Any] = {}

    # Build TokenMoulds extension
    tm_ext: Dict[str, Any] = {}

    # OOXML mapping
    ooxml: Dict[str, Any] = {}
    if ext.ooxml_theme_slot is not None:
        ooxml["themeSlot"] = ext.ooxml_theme_slot
    if ext.ooxml_tint is not None:
        ooxml["tint"] = ext.ooxml_tint
    if ext.ooxml_shade is not None:
        ooxml["shade"] = ext.ooxml_shade
    if ooxml:
        tm_ext["ooxml"] = ooxml

    # Derivation info
    derivation: Dict[str, Any] = {}
    if ext.derivation_source is not None:
        derivation["source"] = ext.derivation_source
    if ext.derivation_formula is not None:
        derivation["formula"] = ext.derivation_formula
    if derivation:
        tm_ext["derivation"] = derivation

    # Accessibility
    accessibility: Dict[str, Any] = {}
    if ext.wcag_contrast_ratio is not None:
        accessibility["contrastRatio"] = ext.wcag_contrast_ratio
    if ext.wcag_level is not None:
        accessibility["wcagLevel"] = ext.wcag_level
    if accessibility:
        tm_ext["accessibility"] = accessibility

    # Add TokenMoulds extension if it has content
    if tm_ext:
        result["com.tokenmoulds"] = tm_ext

    # Add other vendor extensions
    result.update(ext.other)

    return result
