"""µFrame Keyword Registry — single source of truth for all DSL keywords.

Each keyword is registered with its parse, measure, layout, paint, and
codegen functions plus frontend metadata (section, detail, snippet).
Adding a new keyword requires only one registration in keywords.py.

Usage:
    from uframe.registry import register, KEYWORD_REGISTRY, NODE_REGISTRY

    @register("gauge", section="Data", detail="gauge label val max width",
              snippet='gauge "${label}" ${value} ${max:100} ${width:28}')
    def _def_gauge():
        return KeywordDef(
            parse=parse_gauge,
            measure=measure_gauge,
            paint=paint_gauge,
            ...
        )
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Callable, Any

from uframe.ir import IRNode


@dataclass
class KeywordDef:
    """Complete definition of a µFrame DSL keyword."""
    name: str = ""
    section: str = ""                  # "Layout", "Content", "Data", "Style", "Theme", "Form", "Dynamic"
    detail: str = ""                   # slash command detail text
    snippet: str = ""                  # slash command snippet (CodeMirror format)
    highlight_values: list[str] = field(default_factory=list)  # values to highlight as atoms

    # Pipeline functions — all optional, falling back to generic behavior
    parse: Callable[..., IRNode] | None = None       # (args, line_num) → IRNode
    measure: Callable[..., None] | None = None       # (node, available_width) → None
    layout: Callable[..., int] | None = None         # (node, x, y, w, h) → height
    paint: Callable[..., None] | None = None         # (node, grid, theme) → None
    codegen: Callable[..., list[str]] | None = None  # (node, indent_level) → [str]

    # Flags
    is_container: bool = False        # has children (affects layout: vertical stack)
    is_leaf: bool = False             # simple leaf node (layout: return pref_height)
    is_metadata: bool = False         # zero-height metadata (let, source, cache)
    is_style_directive: bool = False  # modifies parent's style (align, color, bold)


# ---------------------------------------------------------------------------
# Registries
# ---------------------------------------------------------------------------

# Keyword name → KeywordDef
KEYWORD_REGISTRY: dict[str, KeywordDef] = {}

# IR node class → KeywordDef (for measure/layout/paint/codegen dispatch)
NODE_REGISTRY: dict[type, KeywordDef] = {}

# All highlight values (populated during registration)
ALL_HIGHLIGHT_VALUES: set[str] = set()

# All theme names
ALL_THEME_NAMES: list[str] = []


def register(name: str, node_class: type | None = None, **kwargs: Any) -> Callable:
    """Decorator to register a keyword definition.

    Usage:
        @register("gauge", node_class=Gauge, section="Data",
                  detail="gauge label val max width",
                  snippet='gauge "${label}" ${value} ...')
        def def_gauge():
            return KeywordDef(parse=..., measure=..., paint=..., ...)

    Or simpler — pass all fields directly:
        register_keyword("gauge", node_class=Gauge, section="Data", ...)
    """
    def decorator(func: Callable) -> Callable:
        kw_def = func()
        if isinstance(kw_def, KeywordDef):
            kw_def.name = name
            for k, v in kwargs.items():
                if hasattr(kw_def, k):
                    setattr(kw_def, k, v)
        else:
            kw_def = KeywordDef(name=name, **kwargs)

        KEYWORD_REGISTRY[name] = kw_def
        if node_class is not None:
            NODE_REGISTRY[node_class] = kw_def

        ALL_HIGHLIGHT_VALUES.update(kw_def.highlight_values)
        return func
    return decorator


def register_keyword(name: str, node_class: type | None = None, **kwargs: Any) -> KeywordDef:
    """Direct registration (non-decorator form)."""
    kw_def = KeywordDef(name=name, **kwargs)
    KEYWORD_REGISTRY[name] = kw_def
    if node_class is not None:
        NODE_REGISTRY[node_class] = kw_def
    ALL_HIGHLIGHT_VALUES.update(kw_def.highlight_values)
    return kw_def


def get_dsl_meta() -> dict:
    """Return DSL metadata for the frontend (keywords, values, commands, themes)."""
    keywords = sorted(KEYWORD_REGISTRY.keys())
    values = sorted(ALL_HIGHLIGHT_VALUES)
    commands = []
    for kw in KEYWORD_REGISTRY.values():
        if kw.detail and kw.snippet:
            commands.append({
                "label": kw.name,
                "detail": kw.detail,
                "section": kw.section,
                "snippet": kw.snippet,
            })
    themes = ALL_THEME_NAMES or []
    return {
        "keywords": keywords,
        "values": values,
        "commands": commands,
        "themes": themes,
    }
