"""
Token Resolver

Resolves tokens between source formats and DSS canonical structure.
Supports bidirectional translation.
"""

from typing import Any, Dict, List, Optional, Union

from .canonical import DSS_CANONICAL_TOKENS
from .models import ResolvedToken, TranslationRegistry, TranslationSource


class TokenResolver:
    """
    Resolves tokens between source and DSS canonical formats.

    Supports:
    - Source -> DSS translation (forward)
    - DSS -> Source translation (reverse)
    - Token path resolution with aliasing
    - Reference chain resolution

    Usage:
        resolver = TokenResolver(registry)

        # Forward translation
        dss_token = resolver.resolve_to_dss("--brand-blue")
        # -> "color.primary.500"

        # Reverse translation
        source_token = resolver.resolve_to_source("color.primary.500", "css")
        # -> "--brand-blue"
    """

    def __init__(self, registry: TranslationRegistry):
        """
        Initialize resolver with translation registry.

        Args:
            registry: Loaded TranslationRegistry with mappings
        """
        self.registry = registry
        self._reverse_map: Dict[str, Dict[str, str]] = {}
        self._build_reverse_maps()

    def _build_reverse_maps(self) -> None:
        """Build reverse lookup maps (DSS -> source) for each source type."""
        for source_type, dictionary in self.registry.dictionaries.items():
            self._reverse_map[source_type] = {
                dss: source for source, dss in dictionary.mappings.tokens.items()
            }

    def resolve_to_dss(
        self, source_token: str, source_type: Optional[Union[str, TranslationSource]] = None
    ) -> Optional[str]:
        """
        Resolve source token to DSS canonical path.

        Args:
            source_token: Source token (e.g., "--brand-blue", "$primary")
            source_type: Optional source type hint (searches all if not provided)

        Returns:
            DSS canonical path or None if not found
        """
        # Direct lookup in combined map
        if source_token in self.registry.combined_token_map:
            return self.registry.combined_token_map[source_token]

        # If source type specified, look only there
        if source_type:
            if isinstance(source_type, str):
                source_type = TranslationSource(source_type)
            dictionary = self.registry.dictionaries.get(source_type.value)
            if dictionary:
                return dictionary.mappings.tokens.get(source_token)

        # Try normalization patterns
        normalized = self._normalize_token_name(source_token)
        return self.registry.combined_token_map.get(normalized)

    def resolve_to_source(self, dss_token: str, source_type: Union[str, TranslationSource]) -> Optional[str]:
        """
        Resolve DSS token to source format (reverse translation).

        Args:
            dss_token: DSS canonical path (e.g., "color.primary.500")
            source_type: Target source type

        Returns:
            Source token name or None if not mapped
        """
        if isinstance(source_type, str):
            source_type_str = source_type
        else:
            source_type_str = source_type.value

        reverse_map = self._reverse_map.get(source_type_str, {})
        return reverse_map.get(dss_token)

    def resolve_token_value(
        self,
        source_token: str,
        base_theme_tokens: Dict[str, Any],
        source_type: Optional[Union[str, TranslationSource]] = None,
    ) -> Optional[ResolvedToken]:
        """
        Fully resolve a source token to its DSS value.

        Args:
            source_token: Source token name
            base_theme_tokens: Base theme token values
            source_type: Optional source type hint

        Returns:
            ResolvedToken with full provenance or None
        """
        # Get DSS path
        dss_path = self.resolve_to_dss(source_token, source_type)
        if not dss_path:
            # Check if it's a custom prop
            if source_token in self.registry.all_custom_props:
                return ResolvedToken(
                    dss_path=source_token,
                    value=self.registry.all_custom_props[source_token],
                    source_token=source_token,
                    is_custom=True,
                    provenance=[f"custom_prop: {source_token}"],
                )
            return None

        # Resolve value from base theme
        value = self._get_token_value(dss_path, base_theme_tokens)

        # Determine source type if not provided
        resolved_source = source_type
        if resolved_source is None:
            for src_type, dictionary in self.registry.dictionaries.items():
                if source_token in dictionary.mappings.tokens:
                    resolved_source = TranslationSource(src_type)
                    break

        return ResolvedToken(
            dss_path=dss_path,
            value=value,
            source_token=source_token,
            source_type=resolved_source
            if isinstance(resolved_source, TranslationSource)
            else (TranslationSource(resolved_source) if resolved_source else None),
            is_custom=False,
            provenance=[
                f"source: {source_token}",
                f"mapped_to: {dss_path}",
                f"value: {value}",
            ],
        )

    def resolve_all_mappings(self, base_theme_tokens: Dict[str, Any]) -> Dict[str, ResolvedToken]:
        """
        Resolve all mapped tokens to their DSS values.

        Args:
            base_theme_tokens: Base theme token values

        Returns:
            Dict of DSS path -> ResolvedToken
        """
        resolved = {}

        # Resolve all mapped tokens
        for source_token, dss_path in self.registry.combined_token_map.items():
            value = self._get_token_value(dss_path, base_theme_tokens)

            # Find source type
            source_type = None
            for src_type, dictionary in self.registry.dictionaries.items():
                if source_token in dictionary.mappings.tokens:
                    source_type = TranslationSource(src_type)
                    break

            resolved[dss_path] = ResolvedToken(
                dss_path=dss_path,
                value=value,
                source_token=source_token,
                source_type=source_type,
                is_custom=False,
                provenance=[f"source: {source_token}", f"mapped_to: {dss_path}"],
            )

        # Add custom props
        for prop_name, prop_value in self.registry.all_custom_props.items():
            resolved[prop_name] = ResolvedToken(
                dss_path=prop_name,
                value=prop_value,
                is_custom=True,
                provenance=[f"custom_prop: {prop_name}"],
            )

        return resolved

    def _get_token_value(self, dss_path: str, base_tokens: Dict[str, Any]) -> Any:
        """Get token value from base theme using DSS path."""
        # Handle nested paths (e.g., "color.primary.500")
        parts = dss_path.split(".")
        current = base_tokens

        for part in parts:
            if isinstance(current, dict):
                current = current.get(part)
                if current is None:
                    break
            else:
                return None

        # If we got a DesignToken object, extract value
        if hasattr(current, "value"):
            return current.value

        return current

    def _normalize_token_name(self, token: str) -> str:
        """Normalize token name for lookup."""
        # Remove common prefixes
        normalized = token.lstrip("-$")

        # Convert various formats to dot notation
        normalized = normalized.replace("-", ".").replace("_", ".")

        # Handle var() references
        if normalized.startswith("var(") and normalized.endswith(")"):
            normalized = normalized[4:-1].lstrip("-")

        return normalized.lower()

    def get_unmapped_tokens(self) -> List[str]:
        """Get list of tokens that couldn't be mapped."""
        unmapped = []
        for dictionary in self.registry.dictionaries.values():
            unmapped.extend(dictionary.unmapped)
        return list(set(unmapped))

    def validate_dss_path(self, path: str) -> bool:
        """Validate that a path matches DSS canonical structure."""
        return path in DSS_CANONICAL_TOKENS or self._is_valid_custom_namespace(path)

    def _is_valid_custom_namespace(self, path: str) -> bool:
        """Check if path uses valid custom namespace."""
        parts = path.split(".")
        if len(parts) < 3:
            return False
        # Custom props should be like: color.brand.acme.primary
        return parts[1] in ("brand", "custom")
