"""Parser for macro injection syntax in chat messages.

Parses injection patterns from user messages using the same syntax as templates:
- @fragment(name)              - Include a fragment template
- @text(name)                  - Insert text from a content part
- @json(name)                  - Insert JSON from a content part
- @schema(name)                - Reference a schema definition

Note: Unlike template macros which use Jinja2 rendering, these are resolved
at message send time by fetching the referenced content from the API.
"""

import re
from dataclasses import dataclass


@dataclass
class FragmentReference:
    """A reference to a fragment template found in user input."""

    identifier: str  # Fragment name or UUID
    raw_match: str = ""  # Original matched text
    start: int = 0  # Start position in message
    end: int = 0  # End position in message


@dataclass
class ContentReference:
    """A reference to content found in user input."""

    identifier: str  # Content name/UUID
    is_json: bool = False  # True for @json, False for @text
    raw_match: str = ""  # Original matched text
    start: int = 0  # Start position in message
    end: int = 0  # End position in message


@dataclass
class SchemaReference:
    """A reference to a schema found in user input."""

    identifier: str  # Schema name or UUID
    raw_match: str = ""  # Original matched text
    start: int = 0  # Start position in message
    end: int = 0  # End position in message


# Pattern for @fragment(identifier)
# Supports identifiers with dots, dashes, underscores, alphanumeric, and UUIDs
# No quotes required (matches template macro syntax)
FRAGMENT_PATTERN = re.compile(
    r"@fragment\(\s*([a-zA-Z0-9._-]+)\s*\)",  # @fragment(identifier)
)

# Pattern for @text(identifier) - text content
TEXT_PATTERN = re.compile(
    r"@text\(\s*([a-zA-Z0-9._-]+)\s*\)",  # @text(identifier)
)

# Pattern for @json(identifier) - JSON content
JSON_PATTERN = re.compile(
    r"@json\(\s*([a-zA-Z0-9._-]+)\s*\)",  # @json(identifier)
)

# Pattern for @schema(identifier) - schema definition
SCHEMA_PATTERN = re.compile(
    r"@schema\(\s*([a-zA-Z0-9._-]+)\s*\)",  # @schema(identifier)
)

# Pattern for detecting partial injection triggers (for autocomplete)
# Matches: @fragment, @fragment(, @fragment(partial
PARTIAL_FRAGMENT_PATTERN = re.compile(r"@fragment(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
PARTIAL_TEXT_PATTERN = re.compile(r"@text(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
PARTIAL_JSON_PATTERN = re.compile(r"@json(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
PARTIAL_SCHEMA_PATTERN = re.compile(r"@schema(?:\(\s*([a-zA-Z0-9._-]*)?)?$")


def parse_fragments(message: str) -> list[FragmentReference]:
    """Parse all @fragment references from a message.

    Args:
        message: User input message

    Returns:
        List of FragmentReference objects found in the message
    """
    references: list[FragmentReference] = []

    for match in FRAGMENT_PATTERN.finditer(message):
        references.append(
            FragmentReference(
                identifier=match.group(1),
                raw_match=match.group(0),
                start=match.start(),
                end=match.end(),
            )
        )

    return references


def parse_content(message: str) -> list[ContentReference]:
    """Parse all @text and @json references from a message.

    Args:
        message: User input message

    Returns:
        List of ContentReference objects found in the message
    """
    references: list[ContentReference] = []

    # Parse @text(identifier)
    for match in TEXT_PATTERN.finditer(message):
        references.append(
            ContentReference(
                identifier=match.group(1),
                is_json=False,
                raw_match=match.group(0),
                start=match.start(),
                end=match.end(),
            )
        )

    # Parse @json(identifier)
    for match in JSON_PATTERN.finditer(message):
        references.append(
            ContentReference(
                identifier=match.group(1),
                is_json=True,
                raw_match=match.group(0),
                start=match.start(),
                end=match.end(),
            )
        )

    return references


def parse_schemas(message: str) -> list[SchemaReference]:
    """Parse all @schema references from a message.

    Args:
        message: User input message

    Returns:
        List of SchemaReference objects found in the message
    """
    references: list[SchemaReference] = []

    for match in SCHEMA_PATTERN.finditer(message):
        references.append(
            SchemaReference(
                identifier=match.group(1),
                raw_match=match.group(0),
                start=match.start(),
                end=match.end(),
            )
        )

    return references


def parse_injections(
    message: str,
) -> tuple[list[FragmentReference], list[ContentReference], list[SchemaReference]]:
    """Parse all injection patterns from a message.

    Args:
        message: User input message

    Returns:
        Tuple of (fragment_references, content_references, schema_references)
    """
    return parse_fragments(message), parse_content(message), parse_schemas(message)


def has_injections(message: str) -> bool:
    """Check if a message contains any injection patterns.

    This is a quick check that doesn't do full parsing.

    Args:
        message: User input message

    Returns:
        True if message contains @fragment, @text, @json, or @schema patterns
    """
    return (
        "@fragment(" in message
        or "@text(" in message
        or "@json(" in message
        or "@schema(" in message
    )


@dataclass
class PartialInjection:
    """A partial injection pattern detected for autocomplete."""

    injection_type: str  # "fragment", "text", "json", or "schema"
    partial_identifier: str  # Partially typed identifier (may be empty)
    start: int  # Start position of the @ in the message
    end: int  # Current end position (cursor)


def detect_partial_injection(
    message: str, cursor_pos: int | None = None
) -> PartialInjection | None:
    """Detect if the cursor is in a partial injection pattern.

    Used for autocomplete - detects when user is typing an injection.

    Args:
        message: User input message
        cursor_pos: Cursor position (defaults to end of message)

    Returns:
        PartialInjection if cursor is in a partial pattern, None otherwise

    Examples:
        >>> detect_partial_injection("Hello @fragment")
        PartialInjection(injection_type='fragment', partial_identifier='', ...)
        >>> detect_partial_injection("Hello @fragment(my-te")
        PartialInjection(injection_type='fragment', partial_identifier='my-te', ...)
    """
    if cursor_pos is None:
        cursor_pos = len(message)

    # Get text up to cursor
    text_to_cursor = message[:cursor_pos]

    # Find the last @ before cursor
    last_at = text_to_cursor.rfind("@")
    if last_at == -1:
        return None

    # Get the text from @ to cursor
    partial_text = text_to_cursor[last_at:]

    # Try to match each partial pattern
    patterns = [
        (PARTIAL_FRAGMENT_PATTERN, "fragment"),
        (PARTIAL_TEXT_PATTERN, "text"),
        (PARTIAL_JSON_PATTERN, "json"),
        (PARTIAL_SCHEMA_PATTERN, "schema"),
    ]

    for pattern, injection_type in patterns:
        match = pattern.match(partial_text)
        if match:
            # Extract partial identifier if present
            partial_identifier = (
                match.group(1) if match.lastindex and match.group(1) else ""
            )
            return PartialInjection(
                injection_type=injection_type,
                partial_identifier=partial_identifier,
                start=last_at,
                end=cursor_pos,
            )

    return None


def format_fragment_injection(identifier: str) -> str:
    """Format a fragment injection string.

    Args:
        identifier: Fragment name or UUID

    Returns:
        Formatted injection string like @fragment(name)
    """
    return f"@fragment({identifier})"


def format_content_injection(identifier: str, is_json: bool = False) -> str:
    """Format a content injection string.

    Args:
        identifier: Content identifier (name or UUID)
        is_json: Whether to use @json

    Returns:
        Formatted injection string
    """
    if is_json:
        return f"@json({identifier})"
    return f"@text({identifier})"


def format_schema_injection(identifier: str) -> str:
    """Format a schema injection string.

    Args:
        identifier: Schema name or UUID

    Returns:
        Formatted injection string
    """
    return f"@schema({identifier})"
