"""
Style resolver — ``(css :flex :gap-4 :hover:bg-sky-200)`` → StyleValue.

Resolves a tuple of atom strings into a ``StyleValue`` with:
- A content-addressed class name (``sx-{hash[:6]}``)
- Base CSS declarations
- Pseudo-class rules (hover, focus, etc.)
- Media-query rules (responsive breakpoints)
- Referenced @keyframes definitions

Resolution order per atom:
  1. Dictionary lookup in ``STYLE_ATOMS``
  2. Arbitrary value pattern match (``w-[347px]`` → ``width:347px``)
  3. Ignored (unknown atoms are silently skipped)

Results are memoized by input tuple for zero-cost repeat calls.
"""
from __future__ import annotations

import hashlib
import re
from functools import lru_cache
from typing import Sequence

from .style_dict import (
    ARBITRARY_PATTERNS,
    CHILD_SELECTOR_ATOMS,
    KEYFRAMES,
    PSEUDO_VARIANTS,
    RESPONSIVE_BREAKPOINTS,
    STYLE_ATOMS,
)
from .types import StyleValue


# ---------------------------------------------------------------------------
# Compiled arbitrary-value patterns
# ---------------------------------------------------------------------------

_COMPILED_PATTERNS: list[tuple[re.Pattern, str]] = [
    (re.compile(f"^{pat}$"), tmpl)
    for pat, tmpl in ARBITRARY_PATTERNS
]


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def resolve_style(atoms: tuple[str, ...]) -> StyleValue:
    """Resolve a tuple of keyword atoms into a StyleValue.

    Each atom is a Tailwind-compatible keyword (``flex``, ``gap-4``,
    ``hover:bg-sky-200``, ``sm:flex-row``, etc.).  Both keywords
    (without leading colon) and runtime strings are accepted.
    """
    return _resolve_cached(atoms)


def merge_styles(styles: Sequence[StyleValue]) -> StyleValue:
    """Merge multiple StyleValues into one.

    Later declarations win for the same CSS property.  Class name is
    recomputed from the merged declarations.
    """
    if len(styles) == 1:
        return styles[0]

    all_decls: list[str] = []
    all_media: list[tuple[str, str]] = []
    all_pseudo: list[tuple[str, str]] = []
    all_kf: list[tuple[str, str]] = []

    for sv in styles:
        if sv.declarations:
            all_decls.append(sv.declarations)
        all_media.extend(sv.media_rules)
        all_pseudo.extend(sv.pseudo_rules)
        all_kf.extend(sv.keyframes)

    merged_decls = ";".join(all_decls)
    return _build_style_value(
        merged_decls,
        tuple(all_media),
        tuple(all_pseudo),
        tuple(dict(all_kf).items()),  # dedupe keyframes by name
    )


# ---------------------------------------------------------------------------
# Internal resolution
# ---------------------------------------------------------------------------

@lru_cache(maxsize=4096)
def _resolve_cached(atoms: tuple[str, ...]) -> StyleValue:
    """Memoized resolver."""
    base_decls: list[str] = []
    media_rules: list[tuple[str, str]] = []   # (query, decls)
    pseudo_rules: list[tuple[str, str]] = []  # (selector_suffix, decls)
    keyframes_needed: list[tuple[str, str]] = []

    for atom in atoms:
        if not atom:
            continue
        # Strip leading colon if keyword form (":flex" → "flex")
        a = atom.lstrip(":")

        # Split variant prefix(es): "hover:bg-sky-200" → ["hover", "bg-sky-200"]
        # "sm:hover:bg-sky-200" → ["sm", "hover", "bg-sky-200"]
        variant, base = _split_variant(a)

        # Resolve the base atom to CSS declarations
        decls = _resolve_atom(base)
        if not decls:
            continue

        # Check if this atom references a keyframe
        _check_keyframes(base, keyframes_needed)

        # Route to the appropriate bucket
        if variant is None:
            base_decls.append(decls)
        elif variant in RESPONSIVE_BREAKPOINTS:
            query = RESPONSIVE_BREAKPOINTS[variant]
            media_rules.append((query, decls))
        elif variant in PSEUDO_VARIANTS:
            pseudo_sel = PSEUDO_VARIANTS[variant]
            pseudo_rules.append((pseudo_sel, decls))
        else:
            # Compound variant: "sm:hover:..." → media + pseudo
            parts = variant.split(":")
            media_part = None
            pseudo_part = None
            for p in parts:
                if p in RESPONSIVE_BREAKPOINTS:
                    media_part = RESPONSIVE_BREAKPOINTS[p]
                elif p in PSEUDO_VARIANTS:
                    pseudo_part = PSEUDO_VARIANTS[p]
            if media_part and pseudo_part:
                # Both media and pseudo — store as pseudo within media
                # For now, put in pseudo_rules with media annotation
                pseudo_rules.append((pseudo_part, decls))
                media_rules.append((media_part, decls))
            elif media_part:
                media_rules.append((media_part, decls))
            elif pseudo_part:
                pseudo_rules.append((pseudo_part, decls))
            else:
                # Unknown variant — treat as base
                base_decls.append(decls)

    return _build_style_value(
        ";".join(base_decls),
        tuple(media_rules),
        tuple(pseudo_rules),
        tuple(keyframes_needed),
    )


def _split_variant(atom: str) -> tuple[str | None, str]:
    """Split a potentially variant-prefixed atom.

    Returns (variant, base) where variant is None for non-prefixed atoms.
    Examples:
        "flex" → (None, "flex")
        "hover:bg-sky-200" → ("hover", "bg-sky-200")
        "sm:flex-row" → ("sm", "flex-row")
        "sm:hover:bg-sky-200" → ("sm:hover", "bg-sky-200")
    """
    # Check for responsive prefix first (always outermost)
    for bp in RESPONSIVE_BREAKPOINTS:
        prefix = bp + ":"
        if atom.startswith(prefix):
            rest = atom[len(prefix):]
            # Check for nested pseudo variant
            for pv in PSEUDO_VARIANTS:
                inner_prefix = pv + ":"
                if rest.startswith(inner_prefix):
                    return (bp + ":" + pv, rest[len(inner_prefix):])
            return (bp, rest)

    # Check for pseudo variant
    for pv in PSEUDO_VARIANTS:
        prefix = pv + ":"
        if atom.startswith(prefix):
            return (pv, atom[len(prefix):])

    return (None, atom)


def _resolve_atom(atom: str) -> str | None:
    """Look up CSS declarations for a single base atom.

    Returns None if the atom is unknown.
    """
    # 1. Dictionary lookup
    decls = STYLE_ATOMS.get(atom)
    if decls is not None:
        return decls

    # 2. Dynamic keyframes: animate-{name} → animation-name:{name}
    if atom.startswith("animate-"):
        name = atom[len("animate-"):]
        if name in KEYFRAMES:
            return f"animation-name:{name}"

    # 3. Arbitrary value pattern match
    for pattern, template in _COMPILED_PATTERNS:
        m = pattern.match(atom)
        if m:
            groups = m.groups()
            result = template
            for i, g in enumerate(groups):
                result = result.replace(f"{{{i}}}", g)
            return result

    # 4. Unknown atom — silently skip
    return None


def _check_keyframes(atom: str, kf_list: list[tuple[str, str]]) -> None:
    """If the atom references a built-in animation, add its @keyframes."""
    if atom.startswith("animate-"):
        name = atom[len("animate-"):]
        if name in KEYFRAMES:
            kf_list.append((name, KEYFRAMES[name]))


def _build_style_value(
    declarations: str,
    media_rules: tuple,
    pseudo_rules: tuple,
    keyframes: tuple,
) -> StyleValue:
    """Build a StyleValue with a content-addressed class name."""
    # Build hash from all rules for deterministic class name
    hash_input = declarations
    for query, decls in media_rules:
        hash_input += f"@{query}{{{decls}}}"
    for sel, decls in pseudo_rules:
        hash_input += f"{sel}{{{decls}}}"
    for name, rule in keyframes:
        hash_input += rule

    h = hashlib.sha256(hash_input.encode()).hexdigest()[:6]
    class_name = f"sx-{h}"

    return StyleValue(
        class_name=class_name,
        declarations=declarations,
        media_rules=media_rules,
        pseudo_rules=pseudo_rules,
        keyframes=keyframes,
    )
