"""CSS to DTCG Token Converter.

Parses a CSS file and extracts design tokens into W3C DTCG format.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any, Dict, List, Tuple

from tokenmoulds.dtcg.types import (
    ColorValue,
    DimensionValue,
    Token,
    TokenExtensions,
    TokenFile,
    TokenGroup,
    TokenType,
)


@dataclass
class CSSVariable:
    """Represents a CSS custom property."""

    name: str
    value: str


@dataclass
class CSSRule:
    """Represents a CSS rule with selector and declarations."""

    selector: str
    declarations: Dict[str, str]


def parse_css(css_content: str) -> Tuple[List[CSSVariable], List[CSSRule]]:
    """Parse CSS content into variables and rules."""
    variables: List[CSSVariable] = []
    rules: List[CSSRule] = []

    # Remove comments
    css_content = re.sub(r"/\*.*?\*/", "", css_content, flags=re.DOTALL)

    # Extract CSS variables from :root
    root_match = re.search(r":root\s*\{([^}]+)\}", css_content)
    if root_match:
        var_content = root_match.group(1)
        var_pattern = re.compile(r"--([a-zA-Z0-9-]+)\s*:\s*([^;]+);")
        for match in var_pattern.finditer(var_content):
            variables.append(
                CSSVariable(name=match.group(1), value=match.group(2).strip())
            )

    # Extract rules
    rule_pattern = re.compile(r"([^{]+)\{([^}]+)\}")
    for match in rule_pattern.finditer(css_content):
        selector = match.group(1).strip()
        if selector.startswith("@") or selector == ":root":
            continue

        declarations = {}
        decl_content = match.group(2)
        decl_pattern = re.compile(r"([a-zA-Z-]+)\s*:\s*([^;]+);")
        for decl_match in decl_pattern.finditer(decl_content):
            prop = decl_match.group(1).strip()
            val = decl_match.group(2).strip()
            declarations[prop] = val

        if declarations:
            rules.append(CSSRule(selector=selector, declarations=declarations))

    return variables, rules


def extract_colors(
    rules: List[CSSRule], variables: List[CSSVariable]
) -> Dict[str, str]:
    """Extract unique colors from CSS."""
    colors = {}

    # From variables
    for var in variables:
        if is_color(var.value):
            colors[var.name] = normalize_color(var.value)

    # Color properties to check
    color_props = [
        "color",
        "background-color",
        "background",
        "border-color",
        "fill",
        "stroke",
    ]

    for rule in rules:
        for prop in color_props:
            if prop in rule.declarations:
                value = rule.declarations[prop]
                # Handle gradients
                gradient_colors = extract_gradient_colors(value)
                if gradient_colors:
                    for i, c in enumerate(gradient_colors):
                        name = f"{rule.selector.replace('.', '').replace('#', '').replace(' ', '-')}-{prop}-{i}"
                        name = re.sub(r"[^a-zA-Z0-9-]", "", name)
                        colors[name] = c
                elif is_color(value):
                    name = f"{rule.selector.replace('.', '').replace('#', '').replace(' ', '-')}-{prop}"
                    name = re.sub(r"[^a-zA-Z0-9-]", "", name)
                    colors[name] = normalize_color(value)

    return colors


def is_color(value: str) -> bool:
    """Check if a value is a color (not a CSS keyword)."""
    value = value.strip().lower()

    # Skip CSS keywords that aren't actual colors
    css_keywords = {
        "inherit",
        "initial",
        "unset",
        "revert",
        "currentcolor",
        "none",
        "auto",
    }
    if value in css_keywords:
        return False

    if value.startswith("#"):
        return True
    if value.startswith("rgb") or value.startswith("hsl"):
        return True
    # Named colors (actual color values)
    named_colors = [
        "white",
        "black",
        "red",
        "blue",
        "green",
        "yellow",
        "orange",
        "purple",
        "pink",
        "gray",
        "grey",
        "navy",
        "teal",
        "maroon",
        "olive",
        "aqua",
        "fuchsia",
        "silver",
        "lime",
        "transparent",
    ]
    if value in named_colors:
        return True
    return False


def normalize_color(value: str) -> str:
    """Normalize color to hex format."""
    value = value.strip().lower()

    if value.startswith("#"):
        # Expand shorthand
        if len(value) == 4:
            return f"#{value[1] * 2}{value[2] * 2}{value[3] * 2}"
        return value.upper()

    if value.startswith("rgb"):
        # Parse rgb(r, g, b) or rgba(r, g, b, a)
        match = re.search(r"rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", value)
        if match:
            r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
            return f"#{r:02X}{g:02X}{b:02X}"

    # Named color mappings (CSS3 color names)
    named = {
        "white": "#FFFFFF",
        "black": "#000000",
        "red": "#FF0000",
        "blue": "#0000FF",
        "green": "#008000",
        "yellow": "#FFFF00",
        "orange": "#FFA500",
        "purple": "#800080",
        "pink": "#FFC0CB",
        "gray": "#808080",
        "grey": "#808080",
        "navy": "#000080",
        "teal": "#008080",
        "maroon": "#800000",
        "olive": "#808000",
        "aqua": "#00FFFF",
        "fuchsia": "#FF00FF",
        "silver": "#C0C0C0",
        "lime": "#00FF00",
        "transparent": "#00000000",
    }
    return named.get(value, value)


def extract_gradient_colors(value: str) -> List[str]:
    """Extract colors from gradient definitions."""
    colors = []
    if "gradient" not in value.lower():
        return colors

    # Find hex colors in gradient
    hex_pattern = re.compile(r"#[0-9a-fA-F]{3,8}")
    for match in hex_pattern.finditer(value):
        colors.append(normalize_color(match.group()))

    return colors


def extract_typography(rules: List[CSSRule]) -> Dict[str, Dict[str, Any]]:
    """Extract typography tokens from CSS rules."""
    typography = {}

    # Target heading and body selectors
    heading_selectors = ["h1", "h2", "h3", "h4", "h5", "h6"]
    body_selectors = ["body", "p", ".container"]

    for rule in rules:
        selector = rule.selector.strip()
        decls = rule.declarations

        # Check if typography-related
        if not any(
            p in decls
            for p in [
                "font-family",
                "font-size",
                "font-weight",
                "line-height",
                "letter-spacing",
            ]
        ):
            continue

        name = selector
        for prefix in heading_selectors:
            if selector.startswith(prefix):
                name = prefix
                break

        if selector in body_selectors or selector == "body":
            name = "body"

        name = re.sub(r"[^a-zA-Z0-9]", "", name)
        if not name:
            continue

        if name not in typography:
            typography[name] = {}

        if "font-family" in decls:
            typography[name]["fontFamily"] = (
                decls["font-family"].split(",")[0].strip().strip("'\"")
            )
        if "font-size" in decls:
            typography[name]["fontSize"] = parse_dimension(decls["font-size"])
        if "font-weight" in decls:
            typography[name]["fontWeight"] = parse_font_weight(decls["font-weight"])
        if "line-height" in decls:
            typography[name]["lineHeight"] = decls["line-height"]
        if "letter-spacing" in decls:
            typography[name]["letterSpacing"] = decls["letter-spacing"]

    return typography


def parse_dimension(value: str) -> Dict[str, Any]:
    """Parse a CSS dimension value."""
    value = value.strip()

    # Handle clamp()
    if value.startswith("clamp("):
        # Extract the preferred value (middle)
        match = re.search(r"clamp\([^,]+,\s*([^,]+),", value)
        if match:
            value = match.group(1).strip()

    # Handle calc()
    if value.startswith("calc("):
        return {"value": value, "unit": "calc"}

    # Parse number + unit
    match = re.match(r"([\d.]+)(px|rem|em|pt|%|vw|vh)?", value)
    if match:
        num = float(match.group(1))
        unit = match.group(2) or "px"
        return {"value": num, "unit": unit}

    return {"value": value, "unit": "unknown"}


def parse_font_weight(value: str) -> int:
    """Parse font weight to numeric value."""
    value = value.strip().lower()
    weights = {
        "thin": 100,
        "extralight": 200,
        "light": 300,
        "normal": 400,
        "regular": 400,
        "medium": 500,
        "semibold": 600,
        "bold": 700,
        "extrabold": 800,
        "black": 900,
    }
    if value in weights:
        return weights[value]
    try:
        return int(value)
    except ValueError:
        return 400


def extract_spacing(rules: List[CSSRule]) -> Dict[str, Dict[str, Any]]:
    """Extract spacing tokens from CSS."""
    spacing = {}
    spacing_props = ["padding", "margin", "gap"]

    for rule in rules:
        for prop in spacing_props:
            if prop in rule.declarations:
                value = rule.declarations[prop]
                dim = parse_dimension(value)
                if dim["unit"] != "unknown":
                    name = f"{rule.selector.replace('.', '').replace('#', '')}-{prop}"
                    name = re.sub(r"[^a-zA-Z0-9-]", "", name)
                    spacing[name] = dim

    return spacing


def extract_shadows(rules: List[CSSRule]) -> Dict[str, str]:
    """Extract shadow tokens."""
    shadows = {}

    for rule in rules:
        if "box-shadow" in rule.declarations:
            value = rule.declarations["box-shadow"]
            name = f"{rule.selector.replace('.', '').replace('#', '')}-shadow"
            name = re.sub(r"[^a-zA-Z0-9-]", "", name)
            shadows[name] = value

    return shadows


def extract_borders(rules: List[CSSRule]) -> Dict[str, Dict[str, Any]]:
    """Extract border tokens."""
    borders = {}

    for rule in rules:
        if "border-radius" in rule.declarations:
            value = rule.declarations["border-radius"]
            dim = parse_dimension(value)
            name = f"{rule.selector.replace('.', '').replace('#', '')}-radius"
            name = re.sub(r"[^a-zA-Z0-9-]", "", name)
            borders[name] = dim

    return borders


def css_to_dtcg(css_content: str) -> TokenFile:
    """Convert CSS content to DTCG TokenFile."""
    variables, rules = parse_css(css_content)

    token_file = TokenFile()

    # Extract and organize colors
    colors = extract_colors(rules, variables)
    if colors:
        color_group = TokenGroup(name="color", type=TokenType.COLOR)

        # Group by purpose
        palette = TokenGroup(name="palette", type=TokenType.COLOR)
        brand = TokenGroup(name="brand", type=TokenType.COLOR)
        text = TokenGroup(name="text", type=TokenType.COLOR)
        background = TokenGroup(name="background", type=TokenType.COLOR)

        for name, hex_val in colors.items():
            token = Token(
                name=name,
                value=ColorValue.from_hex(hex_val),
                type=TokenType.COLOR,
                extensions=TokenExtensions(
                    derivation_source="css-import",
                ),
            )

            # Categorize
            name_lower = name.lower()
            if "background" in name_lower or "bg" in name_lower:
                background.tokens[name] = token
            elif "text" in name_lower or "color" in name_lower:
                text.tokens[name] = token
            elif "hero" in name_lower or "header" in name_lower or "cta" in name_lower:
                brand.tokens[name] = token
            else:
                palette.tokens[name] = token

        if palette.tokens:
            color_group.groups["palette"] = palette
        if brand.tokens:
            color_group.groups["brand"] = brand
        if text.tokens:
            color_group.groups["text"] = text
        if background.tokens:
            color_group.groups["background"] = background

        # Also add CSS variables directly
        if variables:
            vars_group = TokenGroup(name="variables", type=TokenType.COLOR)
            for var in variables:
                if is_color(var.value):
                    vars_group.tokens[var.name] = Token(
                        name=var.name,
                        value=ColorValue.from_hex(normalize_color(var.value)),
                        type=TokenType.COLOR,
                        description=f"CSS variable --{var.name}",
                    )
            if vars_group.tokens:
                color_group.groups["variables"] = vars_group

        token_file.groups["color"] = color_group

    # Extract typography
    typography = extract_typography(rules)
    if typography:
        typo_group = TokenGroup(name="typography")

        # Font families
        fonts = TokenGroup(name="font", type=TokenType.FONT_FAMILY)
        seen_fonts = set()
        for name, props in typography.items():
            if "fontFamily" in props and props["fontFamily"] not in seen_fonts:
                font_name = props["fontFamily"]
                seen_fonts.add(font_name)
                # Determine role
                role = (
                    "body"
                    if name == "body"
                    else "heading"
                    if name.startswith("h")
                    else name
                )
                fonts.tokens[role] = Token(
                    name=role,
                    value=font_name,
                    type=TokenType.FONT_FAMILY,
                )
        if fonts.tokens:
            typo_group.groups["font"] = fonts

        # Type scale
        scale = TokenGroup(name="scale", type=TokenType.DIMENSION)
        for name, props in typography.items():
            if "fontSize" in props:
                dim = props["fontSize"]
                if isinstance(dim, dict) and "value" in dim:
                    scale.tokens[name] = Token(
                        name=name,
                        value=DimensionValue(
                            value=dim["value"], unit=dim.get("unit", "px")
                        ),
                        type=TokenType.DIMENSION,
                    )
        if scale.tokens:
            typo_group.groups["scale"] = scale

        # Weights
        weights = TokenGroup(name="weight", type=TokenType.FONT_WEIGHT)
        seen_weights = {}
        for name, props in typography.items():
            if "fontWeight" in props:
                w = props["fontWeight"]
                if w not in seen_weights.values():
                    weights.tokens[name] = Token(
                        name=name,
                        value=w,
                        type=TokenType.FONT_WEIGHT,
                    )
                    seen_weights[name] = w
        if weights.tokens:
            typo_group.groups["weight"] = weights

        # Line heights
        line_heights = TokenGroup(name="lineHeight", type=TokenType.NUMBER)
        for name, props in typography.items():
            if "lineHeight" in props:
                lh = props["lineHeight"]
                try:
                    lh_val = float(lh)
                    line_heights.tokens[name] = Token(
                        name=name,
                        value=lh_val,
                        type=TokenType.NUMBER,
                    )
                except ValueError:
                    pass
        if line_heights.tokens:
            typo_group.groups["lineHeight"] = line_heights

        token_file.groups["typography"] = typo_group

    # Spacing
    spacing_data = extract_spacing(rules)
    if spacing_data:
        spacing_group = TokenGroup(name="spacing", type=TokenType.DIMENSION)
        for name, dim in spacing_data.items():
            spacing_group.tokens[name] = Token(
                name=name,
                value=DimensionValue(value=dim["value"], unit=dim.get("unit", "px")),
                type=TokenType.DIMENSION,
            )
        token_file.groups["spacing"] = spacing_group

    # Shadows
    shadows = extract_shadows(rules)
    if shadows:
        shadow_group = TokenGroup(name="shadow", type=TokenType.SHADOW)
        for name, value in shadows.items():
            shadow_group.tokens[name] = Token(
                name=name,
                value=value,  # Keep raw for now
                type=TokenType.SHADOW,
            )
        token_file.groups["shadow"] = shadow_group

    # Borders/radii
    borders = extract_borders(rules)
    if borders:
        border_group = TokenGroup(name="border", type=TokenType.DIMENSION)
        for name, dim in borders.items():
            border_group.tokens[name] = Token(
                name=name,
                value=DimensionValue(value=dim["value"], unit=dim.get("unit", "px")),
                type=TokenType.DIMENSION,
            )
        token_file.groups["border"] = border_group

    return token_file
