"""DTCG Recipe Builder - creates self-contained token files with derivation formulas.

A "recipe" is a DTCG file that contains:
1. Input tokens (the few values a designer provides)
2. Derived tokens with formulas (how everything else is calculated)
3. Resolved values (for tools that can't evaluate formulas)

This enables round-trip workflows:
- Designer changes primary color in Figma
- TokenMoulds re-evaluates all formulas
- All derived colors, theme slots, etc. update automatically
"""

from __future__ import annotations

from dataclasses import dataclass

from tokenmoulds.dtcg.types import (
    ColorValue,
    DimensionValue,
    Token,
    TokenExtensions,
    TokenFile,
    TokenGroup,
    TokenType,
)
from tokenmoulds.dtcg.formula import (
    lighten,
    darken,
    contrast_color,
    hex_to_hsl,
    hsl_to_hex,
    desaturate,
)


@dataclass
class RecipeInputs:
    """The minimal inputs needed to generate a full token system."""

    primary_color: str
    secondary_color: str
    font_sans: str
    font_serif: str
    baseline_pt: float = 11.0
    brand_tone: float = 0.5
    content_density: float = 0.4
    locale: str = "US"
    type_scale_ratio: float = 1.25
    kerning_threshold_pt: float = 18.0


def build_recipe(inputs: RecipeInputs) -> TokenFile:
    """Build a DTCG token file with full derivation formulas.

    The resulting file is a self-contained recipe - given the inputs,
    any DTCG-compatible tool can understand how every token is derived.

    Args:
        inputs: The minimal design inputs

    Returns:
        TokenFile with resolved values AND derivation formulas
    """
    token_file = TokenFile()

    # 1. Add inputs group - these are the "source of truth"
    token_file.groups["inputs"] = _build_inputs_group(inputs)

    # 2. Add color group with derivations
    token_file.groups["color"] = _build_color_group(inputs)

    # 3. Add typography group with derivations
    token_file.groups["typography"] = _build_typography_group(inputs)

    # 4. Add spacing group with derivations
    token_file.groups["spacing"] = _build_spacing_group(inputs)

    return token_file


def _build_inputs_group(inputs: RecipeInputs) -> TokenGroup:
    """Build the inputs group - the source values everything derives from."""
    group = TokenGroup(
        name="inputs",
        description="Source inputs - change these to regenerate all tokens",
    )

    # Color inputs
    color_group = TokenGroup(name="color", type=TokenType.COLOR)
    color_group.tokens["primary"] = Token(
        name="primary",
        value=ColorValue.from_hex(inputs.primary_color),
        type=TokenType.COLOR,
        description="Primary brand color - the main color of your brand",
    )
    color_group.tokens["secondary"] = Token(
        name="secondary",
        value=ColorValue.from_hex(inputs.secondary_color),
        type=TokenType.COLOR,
        description="Secondary brand color - accent and call-to-action",
    )
    group.groups["color"] = color_group

    # Font inputs
    font_group = TokenGroup(name="font", type=TokenType.FONT_FAMILY)
    font_group.tokens["sans"] = Token(
        name="sans",
        value=inputs.font_sans,
        type=TokenType.FONT_FAMILY,
        description="Sans-serif font for body text and UI",
    )
    font_group.tokens["serif"] = Token(
        name="serif",
        value=inputs.font_serif,
        type=TokenType.FONT_FAMILY,
        description="Serif font for headings and emphasis",
    )
    group.groups["font"] = font_group

    # Dimension inputs
    dimension_group = TokenGroup(name="dimension", type=TokenType.DIMENSION)
    dimension_group.tokens["baseline"] = Token(
        name="baseline",
        value=DimensionValue(value=inputs.baseline_pt, unit="pt"),
        type=TokenType.DIMENSION,
        description="Base grid unit - all spacing derives from this",
    )
    group.groups["dimension"] = dimension_group

    # Number inputs
    number_group = TokenGroup(name="number", type=TokenType.NUMBER)
    number_group.tokens["brandTone"] = Token(
        name="brandTone",
        value=inputs.brand_tone,
        type=TokenType.NUMBER,
        description="Brand tone: 0=formal, 1=casual",
    )
    number_group.tokens["contentDensity"] = Token(
        name="contentDensity",
        value=inputs.content_density,
        type=TokenType.NUMBER,
        description="Content density: 0=sparse, 1=dense",
    )
    group.groups["number"] = number_group

    return group


def _build_color_group(inputs: RecipeInputs) -> TokenGroup:
    """Build color tokens with derivation formulas."""
    group = TokenGroup(name="color", type=TokenType.COLOR)

    # Palette - derived from inputs
    palette = TokenGroup(name="palette", type=TokenType.COLOR)

    # Primary colors
    primary = TokenGroup(name="primary", type=TokenType.COLOR)
    primary.tokens["base"] = Token(
        name="base",
        value="{inputs.color.primary}",  # Reference to input
        type=TokenType.COLOR,
        description="Primary base color",
    )

    # Derived: lighten primary by 20%
    primary_light = lighten(inputs.primary_color, 0.2)
    primary.tokens["light"] = Token(
        name="light",
        value=ColorValue.from_hex(primary_light),
        type=TokenType.COLOR,
        description="Lightened primary for backgrounds",
        extensions=TokenExtensions(
            derivation_formula="lighten({inputs.color.primary}, 0.2)",
            derivation_source="formula",
        ),
    )

    # Derived: darken primary by 20%
    primary_dark = darken(inputs.primary_color, 0.2)
    primary.tokens["dark"] = Token(
        name="dark",
        value=ColorValue.from_hex(primary_dark),
        type=TokenType.COLOR,
        description="Darkened primary for emphasis",
        extensions=TokenExtensions(
            derivation_formula="darken({inputs.color.primary}, 0.2)",
            derivation_source="formula",
        ),
    )

    # Derived: contrast color for accessibility
    primary_contrast = contrast_color(inputs.primary_color, 4.5)
    primary.tokens["contrast"] = Token(
        name="contrast",
        value=ColorValue.from_hex(primary_contrast),
        type=TokenType.COLOR,
        description="Accessible text color on primary background",
        extensions=TokenExtensions(
            derivation_formula="contrast_color({inputs.color.primary}, 4.5)",
            derivation_source="formula",
        ),
    )
    palette.groups["primary"] = primary

    # Secondary colors (same pattern)
    secondary = TokenGroup(name="secondary", type=TokenType.COLOR)
    secondary.tokens["base"] = Token(
        name="base",
        value="{inputs.color.secondary}",
        type=TokenType.COLOR,
    )

    secondary_light = lighten(inputs.secondary_color, 0.2)
    secondary.tokens["light"] = Token(
        name="light",
        value=ColorValue.from_hex(secondary_light),
        type=TokenType.COLOR,
        extensions=TokenExtensions(
            derivation_formula="lighten({inputs.color.secondary}, 0.2)",
            derivation_source="formula",
        ),
    )

    secondary_dark = darken(inputs.secondary_color, 0.2)
    secondary.tokens["dark"] = Token(
        name="dark",
        value=ColorValue.from_hex(secondary_dark),
        type=TokenType.COLOR,
        extensions=TokenExtensions(
            derivation_formula="darken({inputs.color.secondary}, 0.2)",
            derivation_source="formula",
        ),
    )

    secondary_contrast = contrast_color(inputs.secondary_color, 4.5)
    secondary.tokens["contrast"] = Token(
        name="contrast",
        value=ColorValue.from_hex(secondary_contrast),
        type=TokenType.COLOR,
        extensions=TokenExtensions(
            derivation_formula="contrast_color({inputs.color.secondary}, 4.5)",
            derivation_source="formula",
        ),
    )
    palette.groups["secondary"] = secondary

    # Neutral colors - derived from desaturated primary
    neutral = TokenGroup(name="neutral", type=TokenType.COLOR)
    primary_hsl = hex_to_hsl(inputs.primary_color)
    neutral_hsl = desaturate(primary_hsl, 0.9)

    # Generate neutral scale
    neutral_shades = [
        ("white", 98, "lighten(desaturate({inputs.color.primary}, 0.9), 0.48)"),
        ("light", 85, "lighten(desaturate({inputs.color.primary}, 0.9), 0.35)"),
        ("medium", 50, "desaturate({inputs.color.primary}, 0.9)"),
        ("dark", 25, "darken(desaturate({inputs.color.primary}, 0.9), 0.25)"),
        ("black", 5, "darken(desaturate({inputs.color.primary}, 0.9), 0.45)"),
    ]

    for shade_name, lightness, formula in neutral_shades:
        shade_hsl = {"h": neutral_hsl["h"], "s": neutral_hsl["s"], "l": lightness}
        shade_hex = hsl_to_hex(shade_hsl)
        neutral.tokens[shade_name] = Token(
            name=shade_name,
            value=ColorValue.from_hex(shade_hex),
            type=TokenType.COLOR,
            extensions=TokenExtensions(
                derivation_formula=formula,
                derivation_source="formula",
            ),
        )

    palette.groups["neutral"] = neutral
    group.groups["palette"] = palette

    # Theme mapping - OOXML theme slots
    theme = TokenGroup(
        name="theme",
        type=TokenType.COLOR,
        description="Office theme color slots (dk1, lt1, accent1-6, etc.)",
    )

    theme_mapping = [
        ("dk1", "{color.palette.neutral.black}", "Dark 1 - document text"),
        ("lt1", "{color.palette.neutral.white}", "Light 1 - document background"),
        ("dk2", "{color.palette.neutral.dark}", "Dark 2 - secondary dark"),
        ("lt2", "{color.palette.neutral.light}", "Light 2 - secondary light"),
        ("accent1", "{inputs.color.primary}", "Accent 1 - primary brand"),
        ("accent2", "{inputs.color.secondary}", "Accent 2 - secondary brand"),
        ("accent3", "{color.palette.primary.light}", "Accent 3 - primary tint"),
        ("accent4", "{color.palette.secondary.light}", "Accent 4 - secondary tint"),
        ("accent5", "{color.palette.primary.dark}", "Accent 5 - primary shade"),
        ("accent6", "{color.palette.secondary.dark}", "Accent 6 - secondary shade"),
        ("hlink", "darken({inputs.color.primary}, 0.1)", "Hyperlink"),
        ("folHlink", "darken({inputs.color.primary}, 0.2)", "Followed hyperlink"),
    ]

    for slot_name, formula, description in theme_mapping:
        # Resolve the value for tools that can't evaluate formulas
        resolved = _resolve_theme_color(formula, inputs)

        theme.tokens[slot_name] = Token(
            name=slot_name,
            value=ColorValue.from_hex(resolved),
            type=TokenType.COLOR,
            description=description,
            extensions=TokenExtensions(
                ooxml_theme_slot=slot_name,
                derivation_formula=formula,
                derivation_source="formula"
                if not formula.startswith("{inputs.")
                else "input",
            ),
        )

    group.groups["theme"] = theme

    # Text colors
    from tokenmoulds.design.color_defaults import (
        TEXT_INVERSE,
        TEXT_MUTED,
        TEXT_PRIMARY,
        TEXT_SECONDARY,
    )

    text = TokenGroup(name="text", type=TokenType.COLOR)
    text.tokens["primary"] = Token(
        name="primary",
        value=ColorValue.from_hex(TEXT_PRIMARY),
        type=TokenType.COLOR,
        description="Primary text color",
    )
    text.tokens["secondary"] = Token(
        name="secondary",
        value=ColorValue.from_hex(TEXT_SECONDARY),
        type=TokenType.COLOR,
        extensions=TokenExtensions(
            derivation_formula=f"lighten({TEXT_PRIMARY}, 0.29)",
            derivation_source="formula",
        ),
    )
    text.tokens["inverse"] = Token(
        name="inverse",
        value=ColorValue.from_hex(TEXT_INVERSE),
        type=TokenType.COLOR,
        description="Text on dark backgrounds",
    )
    text.tokens["muted"] = Token(
        name="muted",
        value=ColorValue.from_hex(TEXT_MUTED),
        type=TokenType.COLOR,
        extensions=TokenExtensions(
            derivation_formula=f"lighten({TEXT_PRIMARY}, 0.54)",
            derivation_source="formula",
        ),
    )
    group.groups["text"] = text

    return group


def _resolve_theme_color(formula: str, inputs: RecipeInputs) -> str:
    """Resolve a theme color formula to a hex value."""
    # Handle direct references
    if formula == "{inputs.color.primary}":
        return inputs.primary_color
    if formula == "{inputs.color.secondary}":
        return inputs.secondary_color
    if formula == "{color.palette.neutral.black}":
        primary_hsl = hex_to_hsl(inputs.primary_color)
        neutral_hsl = desaturate(primary_hsl, 0.9)
        return hsl_to_hex({"h": neutral_hsl["h"], "s": neutral_hsl["s"], "l": 5})
    if formula == "{color.palette.neutral.white}":
        primary_hsl = hex_to_hsl(inputs.primary_color)
        neutral_hsl = desaturate(primary_hsl, 0.9)
        return hsl_to_hex({"h": neutral_hsl["h"], "s": neutral_hsl["s"], "l": 98})
    if formula == "{color.palette.neutral.dark}":
        primary_hsl = hex_to_hsl(inputs.primary_color)
        neutral_hsl = desaturate(primary_hsl, 0.9)
        return hsl_to_hex({"h": neutral_hsl["h"], "s": neutral_hsl["s"], "l": 25})
    if formula == "{color.palette.neutral.light}":
        primary_hsl = hex_to_hsl(inputs.primary_color)
        neutral_hsl = desaturate(primary_hsl, 0.9)
        return hsl_to_hex({"h": neutral_hsl["h"], "s": neutral_hsl["s"], "l": 85})
    if formula == "{color.palette.primary.light}":
        return lighten(inputs.primary_color, 0.2)
    if formula == "{color.palette.secondary.light}":
        return lighten(inputs.secondary_color, 0.2)
    if formula == "{color.palette.primary.dark}":
        return darken(inputs.primary_color, 0.2)
    if formula == "{color.palette.secondary.dark}":
        return darken(inputs.secondary_color, 0.2)

    # Handle function formulas
    if formula.startswith("darken({inputs.color.primary}"):
        amount = float(formula.split(",")[1].strip().rstrip(")"))
        return darken(inputs.primary_color, amount)
    if formula.startswith("darken({inputs.color.secondary}"):
        amount = float(formula.split(",")[1].strip().rstrip(")"))
        return darken(inputs.secondary_color, amount)

    return inputs.primary_color  # Fallback


def _build_typography_group(inputs: RecipeInputs) -> TokenGroup:
    """Build typography tokens with derivation formulas."""
    group = TokenGroup(name="typography")

    # Font families - reference inputs
    font = TokenGroup(name="font", type=TokenType.FONT_FAMILY)
    font.tokens["sans"] = Token(
        name="sans",
        value="{inputs.font.sans}",
        type=TokenType.FONT_FAMILY,
    )
    font.tokens["serif"] = Token(
        name="serif",
        value="{inputs.font.serif}",
        type=TokenType.FONT_FAMILY,
    )
    font.tokens["mono"] = Token(
        name="mono",
        value="SF Mono",  # Constant
        type=TokenType.FONT_FAMILY,
    )
    group.groups["font"] = font

    # Type scale - derived from baseline using modular scale (1.25 ratio)
    scale = TokenGroup(name="scale", type=TokenType.DIMENSION)

    ratio = inputs.type_scale_ratio
    scale_steps = [
        ("xs", -2, "Extra small - captions"),
        ("sm", -1, "Small - footnotes"),
        ("base", 0, "Base - body text"),
        ("md", 1, "Medium - subheadings"),
        ("lg", 2, "Large - section titles"),
        ("xl", 3, "Extra large - page titles"),
        ("2xl", 4, "Display - hero text"),
        ("3xl", 5, "Display large"),
    ]

    for name, step, description in scale_steps:
        size_pt = inputs.baseline_pt * (ratio**step)
        formula = f"{{inputs.dimension.baseline}} * {ratio}^{step}"

        scale.tokens[name] = Token(
            name=name,
            value=DimensionValue(value=round(size_pt, 2), unit="pt"),
            type=TokenType.DIMENSION,
            description=description,
            extensions=TokenExtensions(
                derivation_formula=formula,
                derivation_source="formula",
            ),
        )

    group.groups["scale"] = scale

    # Baseline token
    group.tokens["baseline"] = Token(
        name="baseline",
        value="{inputs.dimension.baseline}",
        type=TokenType.DIMENSION,
        description="Base grid unit for vertical rhythm",
    )

    return group


def _build_spacing_group(inputs: RecipeInputs) -> TokenGroup:
    """Build spacing tokens with derivation formulas."""
    group = TokenGroup(name="spacing", type=TokenType.DIMENSION)

    # Spacing scale - multiples of baseline
    spacing_scale = [
        ("xs", 0.25, "Extra small spacing"),
        ("sm", 0.5, "Small spacing"),
        ("md", 1, "Medium spacing (1 baseline)"),
        ("lg", 1.5, "Large spacing"),
        ("xl", 2, "Extra large spacing"),
        ("2xl", 3, "2x large spacing"),
        ("3xl", 4, "3x large spacing"),
        ("4xl", 6, "4x large spacing"),
    ]

    for name, multiplier, description in spacing_scale:
        size_pt = inputs.baseline_pt * multiplier
        formula = f"{{inputs.dimension.baseline}} * {multiplier}"

        group.tokens[name] = Token(
            name=name,
            value=DimensionValue(value=round(size_pt, 2), unit="pt"),
            type=TokenType.DIMENSION,
            description=description,
            extensions=TokenExtensions(
                derivation_formula=formula,
                derivation_source="formula",
            ),
        )

    return group
