"""
Card Structure DSL (Domain Specific Language)

This module provides a compact symbol-based language for describing card structures.
Symbols are embedded alongside component names during ingestion, creating strong
associations that make structure matching more precise and token-efficient.

Usage:
    "§[đ, Ƀ[ᵬ×2], ℊ[ǵ×4]]"

    Means: Section containing:
        - DecoratedText
        - ButtonList with 2 Buttons
        - Grid with 4 GridItems

The symbol table is provided to LLMs via MCP tool instructions.
During execution, symbols are expanded and validated against the relationship hierarchy.

Multi-Module Support:
    Symbols can be prefixed with a module identifier for disambiguation:
    "g:§[g:đ, g:ᵬ×2]"  # gchat-specific Section with DecoratedText and Buttons

Symbol Generation:
    Symbols can be auto-generated via ModuleWrapper.generate_component_symbols()
    or use the hardcoded defaults below for backward compatibility.
"""

import logging
import re
from collections import Counter
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

# =============================================================================
# SYMBOL TABLE (Initialized from SymbolGenerator - SSoT)
# =============================================================================
# NO HARDCODED DEFAULTS - symbols must be generated by SymbolGenerator
# based on LETTER_SYMBOLS pools in adapters/symbol_generator.py
#
# These tables are populated by calling configure_from_generator() or
# get_card_framework_wrapper() which triggers auto-configuration.

# Active symbol table - populated at runtime from SymbolGenerator
SYMBOL_TO_COMPONENT: Dict[str, str] = {}

# Reverse mapping - populated at runtime
COMPONENT_TO_SYMBOL: Dict[str, str] = {}

# All symbols as a set for quick lookup - populated at runtime
ALL_SYMBOLS: set = set()

# Flag to track initialization
_initialized = False


# =============================================================================
# DYNAMIC SYMBOL TABLE MANAGEMENT
# =============================================================================


def configure_symbols_from_generator(generator: "SymbolGenerator") -> None:
    """
    Configure the symbol table from a SymbolGenerator instance.

    This allows runtime configuration of symbols, including multi-module
    support via prefixed symbols.

    Args:
        generator: Configured SymbolGenerator instance
    """
    global SYMBOL_TO_COMPONENT, COMPONENT_TO_SYMBOL, ALL_SYMBOLS, _initialized

    new_symbols = generator.get_reverse_mapping()
    SYMBOL_TO_COMPONENT.clear()
    SYMBOL_TO_COMPONENT.update(new_symbols)

    COMPONENT_TO_SYMBOL.clear()
    COMPONENT_TO_SYMBOL.update({v: k for k, v in SYMBOL_TO_COMPONENT.items()})

    ALL_SYMBOLS.clear()
    ALL_SYMBOLS.update(SYMBOL_TO_COMPONENT.keys())

    _initialized = True
    logger.info(f"🔣 Configured {len(SYMBOL_TO_COMPONENT)} symbols from generator")


def configure_symbols_from_wrapper(
    wrapper: "ModuleWrapper",
    module_prefix: Optional[str] = None,
) -> Dict[str, str]:
    """
    Configure symbols from a ModuleWrapper instance.

    Convenience function that creates a generator from wrapper components
    and configures the symbol table.

    Args:
        wrapper: Initialized ModuleWrapper instance
        module_prefix: Optional module prefix for multi-module support

    Returns:
        Dict mapping component names to symbols
    """
    from adapters.symbol_generator import (
        SymbolGenerator,
        extract_component_names_from_wrapper,
    )

    component_names = extract_component_names_from_wrapper(wrapper)
    generator = SymbolGenerator(module_prefix=module_prefix)
    symbols = generator.generate_symbols(component_names)
    configure_symbols_from_generator(generator)

    return symbols


def clear_symbols() -> None:
    """Clear all symbols (requires re-initialization via configure_symbols_*)."""
    global SYMBOL_TO_COMPONENT, COMPONENT_TO_SYMBOL, ALL_SYMBOLS, _initialized

    SYMBOL_TO_COMPONENT.clear()
    COMPONENT_TO_SYMBOL.clear()
    ALL_SYMBOLS.clear()
    _initialized = False

    logger.info("🔄 Cleared symbol tables (re-initialization required)")


def ensure_initialized() -> bool:
    """
    Ensure symbol tables are initialized from the card framework wrapper.

    Auto-initializes from get_card_framework_wrapper() if not yet configured.
    This allows lazy initialization - symbols are loaded on first use.

    Returns:
        True if initialized, False if initialization failed
    """
    global _initialized

    if _initialized and SYMBOL_TO_COMPONENT:
        return True

    try:
        # Import here to avoid circular imports
        from gchat.card_framework_wrapper import (
            configure_structure_dsl_symbols,
            get_card_framework_wrapper,
        )

        # This will configure the symbol tables
        configure_structure_dsl_symbols()
        _initialized = True
        return True

    except Exception as e:
        logger.warning(f"⚠️ Failed to auto-initialize symbols: {e}")
        return False


# =============================================================================
# EMBEDDING TEXT BUILDERS
# =============================================================================


def build_symbol_embedding_text(component_name: str) -> str:
    """
    Build text for embedding that creates strong symbol-component association.

    The symbol appears multiple times in different contexts to strengthen
    the bidirectional embedding association.

    Args:
        component_name: Name of the component (e.g., "Grid")

    Returns:
        Text suitable for embedding, e.g., "ℊ Grid ℊ | Grid widget ℊ"
    """
    ensure_initialized()
    symbol = COMPONENT_TO_SYMBOL.get(component_name)
    if not symbol:
        return f"{component_name} widget component"

    # Multiple mentions create stronger association
    return f"{symbol} {component_name} {symbol} | {component_name} widget {symbol}"


def build_component_identity_with_symbol(
    component_name: str,
    component_type: str,
    full_path: str,
    docstring: Optional[str] = None,
) -> str:
    """
    Build full component identity text including symbol for embedding.

    This is used during ingestion to create the 'components' vector.

    Args:
        component_name: Name of the component
        component_type: Type (class, function, etc.)
        full_path: Full module path
        docstring: Optional docstring

    Returns:
        Identity text with symbol associations
    """
    ensure_initialized()
    symbol = COMPONENT_TO_SYMBOL.get(component_name, "")

    parts = [
        f"Name: {component_name}",
        f"Symbol: {symbol}" if symbol else "",
        f"Type: {component_type}",
        f"Path: {full_path}",
    ]

    if symbol:
        # Add symbol associations
        parts.append(f"Shortcut: {symbol}={component_name}")

    if docstring:
        parts.append(f"Documentation: {docstring[:500]}")

    return "\n".join(p for p in parts if p)


# =============================================================================
# STRUCTURE PARSING
# =============================================================================


@dataclass
class ParsedNode:
    """A node in the parsed structure tree."""

    symbol: str
    component: str
    multiplier: int = 1
    children: List["ParsedNode"] = field(default_factory=list)

    def to_compact(self) -> str:
        """Convert back to compact notation."""
        result = self.symbol
        if self.multiplier > 1:
            result = f"{self.symbol}×{self.multiplier}"
        if self.children:
            children_str = ", ".join(c.to_compact() for c in self.children)
            result = f"{result}[{children_str}]"
        return result

    def to_expanded(self) -> str:
        """Convert to expanded component names."""
        result = self.component
        if self.multiplier > 1:
            result = f"{self.component}×{self.multiplier}"
        if self.children:
            children_str = ", ".join(c.to_expanded() for c in self.children)
            result = f"{result}[{children_str}]"
        return result


def parse_structure(structure_str: str) -> List[ParsedNode]:
    """
    Parse a structure string into a tree of ParsedNodes.

    Syntax:
        §[đ, ᵬ×2]     → Section with DecoratedText and 2 Buttons
        ℊ[ǵ×4]        → Grid with 4 GridItems
        ¶[ǀ[đ], ǀ[ɨ]] → 2 Columns with different content

    Args:
        structure_str: Compact structure notation

    Returns:
        List of root ParsedNodes
    """
    # Tokenize
    tokens = _tokenize_structure(structure_str)

    # Parse into tree
    nodes, _ = _parse_tokens(tokens, 0)
    return nodes


def _tokenize_structure(s: str) -> List[str]:
    """Tokenize structure string into symbols, brackets, and multipliers."""
    # Ensure symbols are initialized before tokenizing
    ensure_initialized()

    tokens = []
    i = 0
    while i < len(s):
        char = s[i]

        # Skip whitespace and commas
        if char in " ,\n\t":
            i += 1
            continue

        # Brackets
        if char in "[]":
            tokens.append(char)
            i += 1
            continue

        # Multiplier (×N or *N or xN)
        if char in "×*x" and i + 1 < len(s) and s[i + 1].isdigit():
            # Read the number
            j = i + 1
            while j < len(s) and s[j].isdigit():
                j += 1
            tokens.append(f"×{s[i + 1 : j]}")
            i = j
            continue

        # Symbol (single character from our table)
        if char in ALL_SYMBOLS:
            tokens.append(char)
            i += 1
            continue

        # Component name (for mixed mode: "Grid" or "ℊ")
        if char.isalpha():
            j = i
            while j < len(s) and (s[j].isalnum() or s[j] == "_"):
                j += 1
            word = s[i:j]
            # Convert to symbol if it's a known component
            if word in COMPONENT_TO_SYMBOL:
                tokens.append(COMPONENT_TO_SYMBOL[word])
            else:
                tokens.append(word)  # Keep as-is for unknown
            i = j
            continue

        # Skip unknown characters
        i += 1

    return tokens


def _parse_tokens(tokens: List[str], pos: int) -> Tuple[List[ParsedNode], int]:
    """Parse tokens into ParsedNode tree recursively."""
    nodes = []

    while pos < len(tokens):
        token = tokens[pos]

        # End of current level
        if token == "]":
            return nodes, pos + 1

        # Start of children
        if token == "[":
            if nodes:
                # Children belong to previous node
                children, pos = _parse_tokens(tokens, pos + 1)
                nodes[-1].children = children
            else:
                pos += 1
            continue

        # Multiplier
        if token.startswith("×"):
            if nodes:
                nodes[-1].multiplier = int(token[1:])
            pos += 1
            continue

        # Symbol or component name
        if token in SYMBOL_TO_COMPONENT:
            nodes.append(
                ParsedNode(
                    symbol=token,
                    component=SYMBOL_TO_COMPONENT[token],
                )
            )
        elif token in COMPONENT_TO_SYMBOL:
            # It's a full component name
            symbol = COMPONENT_TO_SYMBOL[token]
            nodes.append(
                ParsedNode(
                    symbol=symbol,
                    component=token,
                )
            )
        else:
            # Unknown - treat as literal
            nodes.append(
                ParsedNode(
                    symbol=token,
                    component=token,
                )
            )

        pos += 1

    return nodes, pos


# =============================================================================
# STRUCTURE VALIDATION
# =============================================================================


def validate_structure(
    nodes: List[ParsedNode],
    relationships: Dict[str, List[str]],
) -> Tuple[bool, List[str]]:
    """
    Validate a parsed structure against the relationship hierarchy.

    Args:
        nodes: Parsed structure nodes
        relationships: Dict mapping parent components to valid children

    Returns:
        Tuple of (is_valid, list of error messages)
    """
    errors = []

    def validate_node(node: ParsedNode, parent: Optional[str] = None) -> None:
        # Check if this component can be a child of the parent
        if parent:
            valid_children = relationships.get(parent, [])
            if node.component not in valid_children and valid_children:
                errors.append(
                    f"Invalid: {node.component} cannot be child of {parent}. "
                    f"Valid children: {valid_children}"
                )

        # Validate children recursively
        for child in node.children:
            validate_node(child, node.component)

    for node in nodes:
        validate_node(node)

    return len(errors) == 0, errors


# =============================================================================
# SKELETON BUILDING
# =============================================================================


@dataclass
class SkeletonSlot:
    """A slot in the card skeleton waiting for input."""

    path: str  # e.g., "Section[0].DecoratedText[0].text"
    component: str  # e.g., "DecoratedText"
    field_name: str  # e.g., "text"
    field_type: str  # e.g., "str", "Optional[str]", "List[Button]"
    required: bool = False
    value: Any = None  # Filled during input matching


def build_skeleton(
    nodes: List[ParsedNode],
    component_fields: Dict[str, List[Dict[str, Any]]],
) -> List[SkeletonSlot]:
    """
    Build a skeleton with empty slots from parsed structure.

    Args:
        nodes: Parsed structure nodes
        component_fields: Dict mapping component names to their fields
                         [{name: "text", type: "str", required: False}, ...]

    Returns:
        List of SkeletonSlots to be filled with inputs
    """
    slots = []

    def process_node(node: ParsedNode, path_prefix: str = "") -> None:
        component = node.component

        for i in range(node.multiplier):
            # Build path for this instance
            if node.multiplier > 1:
                instance_path = f"{path_prefix}{component}[{i}]"
            else:
                instance_path = f"{path_prefix}{component}"

            # Get fields for this component
            fields = component_fields.get(component, [])

            for field_info in fields:
                field_name = field_info.get("name", "")
                field_type = field_info.get("type", "Any")
                required = field_info.get("required", False)

                # Skip fields that are other components (handled by children)
                if field_info.get("is_component", False):
                    continue

                slots.append(
                    SkeletonSlot(
                        path=f"{instance_path}.{field_name}",
                        component=component,
                        field_name=field_name,
                        field_type=field_type,
                        required=required,
                    )
                )

            # Process children
            for child in node.children:
                process_node(child, f"{instance_path}.")

    for node in nodes:
        process_node(node)

    return slots


# =============================================================================
# MCP TOOL INSTRUCTIONS
# =============================================================================


def get_structure_instructions() -> str:
    """
    Get instructions for LLMs on how to use the structure DSL.

    This is included in MCP tool descriptions.
    Symbols are dynamically generated from COMPONENT_TO_SYMBOL (SSoT).
    """
    ensure_initialized()

    # Group components by category, then look up their symbols from SSoT
    categories = {
        "Layout": [
            "Card",
            "Section",
            "Header",
            "CardFixedFooter",
            "Columns",
            "Column",
            "Divider",
        ],
        "Text": ["DecoratedText", "TextParagraph", "TextInput"],
        "Buttons": ["Button", "ButtonList", "ChipList", "Chip"],
        "Media": ["Grid", "GridItem", "Image", "Icon"],
        "Inputs": [
            "SelectionInput",
            "SelectionItem",
            "DateTimePicker",
            "SwitchControl",
        ],
        "Actions": ["OnClick", "OpenLink", "Action"],
        "Menus": ["OverflowMenu", "OverflowMenuItem"],
    }

    # Build symbol mappings from SSoT
    category_lines = []
    for cat_name, components in categories.items():
        mappings = []
        for comp in components:
            symbol = COMPONENT_TO_SYMBOL.get(comp)
            if symbol:
                mappings.append(f"{symbol}={comp}")
        if mappings:
            category_lines.append(f"**{cat_name}:** {', '.join(mappings)}")

    # Get example symbols from SSoT - no fallbacks, symbols come ONLY from ModuleWrapper
    section_sym = COMPONENT_TO_SYMBOL.get("Section", "Section")
    dt_sym = COMPONENT_TO_SYMBOL.get("DecoratedText", "DecoratedText")
    btn_sym = COMPONENT_TO_SYMBOL.get("Button", "Button")
    btnlist_sym = COMPONENT_TO_SYMBOL.get("ButtonList", "ButtonList")
    grid_sym = COMPONENT_TO_SYMBOL.get("Grid", "Grid")
    griditem_sym = COMPONENT_TO_SYMBOL.get("GridItem", "GridItem")
    cols_sym = COMPONENT_TO_SYMBOL.get("Columns", "Columns")
    col_sym = COMPONENT_TO_SYMBOL.get("Column", "Column")
    img_sym = COMPONENT_TO_SYMBOL.get("Image", "Image")
    header_sym = COMPONENT_TO_SYMBOL.get("Header", "Header")
    textinput_sym = COMPONENT_TO_SYMBOL.get("TextInput", "TextInput")
    datepicker_sym = COMPONENT_TO_SYMBOL.get("DateTimePicker", "DateTimePicker")

    categories_text = "\n".join(category_lines)

    return f"""
## Card Structure Symbols

Use these shortcuts to describe card structure compactly:

{categories_text}

## Syntax

- `{section_sym}[{dt_sym}, {btn_sym}]` → Section with DecoratedText and Button
- `{btn_sym}×2` → 2 Buttons
- `{grid_sym}[{griditem_sym}×4]` → Grid with 4 GridItems
- `{cols_sym}[{col_sym}[{dt_sym}], {col_sym}[{img_sym}]]` → 2 Columns (left: text, right: image)

## Examples

Simple card: `{section_sym}[{dt_sym}, {btnlist_sym}[{btn_sym}×2]]`
Product grid: `{section_sym}[{header_sym}, {grid_sym}[{griditem_sym}×6], {btnlist_sym}[{btn_sym}]]`
Form: `{section_sym}[{textinput_sym}×3, {datepicker_sym}, {btnlist_sym}[{btn_sym}]]`
"""


# =============================================================================
# EXPANSION (Symbol → Full Structure)
# =============================================================================


def expand_to_full_notation(structure_str: str) -> str:
    """
    Expand a compact symbol structure to full component names.

    Args:
        structure_str: Compact notation, e.g., "§[đ, ᵬ×2]"

    Returns:
        Expanded notation, e.g., "Section[DecoratedText, Button×2]"
    """
    nodes = parse_structure(structure_str)
    return ", ".join(n.to_expanded() for n in nodes)


def compact_to_symbol_notation(structure_str: str) -> str:
    """
    Convert full component names to compact symbol notation.

    Args:
        structure_str: Full notation, e.g., "Section[DecoratedText, Button×2]"

    Returns:
        Compact notation, e.g., "§[đ, ᵬ×2]"
    """
    nodes = parse_structure(structure_str)
    return ", ".join(n.to_compact() for n in nodes)
