"""Variable block parsing for Kida parser.

Provides mixin for parsing variable assignment statements (set, let, export).

Uses inline TYPE_CHECKING declarations for host attributes.
See: plan/rfc-mixin-protocol-typing.md
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING

from kida._types import Token, TokenType
from kida.environment.exceptions import ErrorCode
from kida.nodes import Export, Let, Set, Tuple

if TYPE_CHECKING:
    from kida.nodes import Expr
    from kida.parser.errors import ParseError

from kida.parser.blocks.core import BlockStackMixin


class VariableBlockParsingMixin(BlockStackMixin):
    """Mixin for parsing variable assignment blocks.

    Host attributes and cross-mixin dependencies are declared via inline
    TYPE_CHECKING blocks. Inherits block stack management from BlockStackMixin.

    """

    # ─────────────────────────────────────────────────────────────────────────
    # Host attributes and cross-mixin dependencies (type-check only)
    # ─────────────────────────────────────────────────────────────────────────
    if TYPE_CHECKING:
        # Host attributes (from Parser.__init__)
        _tokens: Sequence[Token]
        _pos: int
        _block_stack: list[tuple[str, int, int]]

        # From TokenNavigationMixin (ParserCoreProtocol members)
        @property
        def _current(self) -> Token: ...
        def _peek(self, offset: int = 0) -> Token: ...
        def _advance(self) -> Token: ...
        def _expect(self, token_type: TokenType) -> Token: ...
        def _match(self, *types: TokenType) -> bool: ...
        def _error(
            self,
            message: str,
            token: Token | None = None,
            suggestion: str | None = None,
            code: ErrorCode | None = None,
        ) -> ParseError: ...

        # From StatementParsingMixin
        def _parse_tuple_or_name(self) -> Expr: ...
        def _parse_tuple_or_expression(self) -> Expr: ...

        # From ExpressionParsingMixin
        def _parse_expression(self) -> Expr: ...

    def _parse_set(self) -> list[Set]:
        """Parse {% set x = expr %} or {% set x = 1, y = 2, z = 3 %}.

        Multi-set syntax allows comma-separated independent assignments:
            {% set a = 1, b = 2, c = 3 %}

        Tuple unpacking remains unchanged:
            {% set a, b = 1, 2 %}

        IMPORTANT: For multi-set, uses _parse_expression() (not _parse_tuple_or_expression())
        to prevent commas from being consumed as tuple elements.

        Returns:
            List of Set nodes (one per assignment in multi-set, or single for regular set).
        """
        start = self._advance()  # consume 'set'
        sets: list[Set] = []

        while True:
            # Parse target - can be single name or tuple for unpacking
            target = self._parse_tuple_or_name()

            # Check for tuple unpacking (target is a tuple with multiple items)
            is_tuple_unpack = isinstance(target, Tuple) and len(target.items) > 1

            self._expect(TokenType.ASSIGN)

            if is_tuple_unpack:
                # Tuple unpacking: use _parse_tuple_or_expression for RHS
                # This handles {% set a, b = 1, 2 %}
                value = self._parse_tuple_or_expression()
                sets.append(
                    Set(
                        lineno=start.lineno,
                        col_offset=start.col_offset,
                        target=target,
                        value=value,
                    )
                )
                # Tuple unpacking cannot be combined with multi-set
                break
            else:
                # Single target: use _parse_expression to preserve commas
                value = self._parse_expression()

                # Check for multi-set continuation or tuple value: , ...
                if self._current.type == TokenType.COMMA:
                    if self._is_multi_set_continuation():
                        # Multi-set: a = 1, b = 2, c = 3
                        sets.append(
                            Set(
                                lineno=start.lineno,
                                col_offset=start.col_offset,
                                target=target,
                                value=value,
                            )
                        )
                        self._advance()  # consume comma
                        continue
                    else:
                        # Could be tuple value or trailing comma
                        # Parse rest as tuple: a = 1, 2, 3 -> tuple value
                        items = [value]
                        while self._current.type == TokenType.COMMA:
                            self._advance()  # consume comma
                            if self._match(TokenType.BLOCK_END):
                                break  # trailing comma
                            items.append(self._parse_expression())

                        if len(items) > 1:
                            # Create tuple value
                            value = Tuple(
                                lineno=items[0].lineno,
                                col_offset=items[0].col_offset,
                                items=tuple(items),
                                ctx="load",
                            )

                sets.append(
                    Set(
                        lineno=start.lineno,
                        col_offset=start.col_offset,
                        target=target,
                        value=value,
                    )
                )
                break

        self._expect(TokenType.BLOCK_END)
        return sets

    def _is_multi_set_continuation(self) -> bool:
        """Check if comma is followed by 'NAME =' pattern (multi-set).

        Uses 2-token lookahead without consuming tokens.

        Returns True for: a = 1, b = 2
        Returns False for: a = 1, 2, 3 (tuple value)
        Returns False for: a = 1, (trailing comma)
        """
        # Current token is COMMA
        # Peek ahead: NAME ASSIGN?
        next_token = self._peek(1)
        if next_token.type != TokenType.NAME:
            return False

        next_next_token = self._peek(2)
        return bool(next_next_token.type == TokenType.ASSIGN)

    def _parse_let(self) -> Let | list[Let]:
        """Parse {% let x = expr %} or {% let x = 1, y = 2, z = 3 %}.

        Multi-let syntax allows comma-separated independent assignments:
            {% let a = 1, b = 2, c = 3 %}

        Supports tuple unpacking:
            {% let a, b = 1, 2 %}

        Returns:
            Single Let node or list of Let nodes for multi-let.
        """
        start = self._advance()  # consume 'let'
        lets: list[Let] = []

        while True:
            # Parse target - can be single name or tuple for unpacking
            target = self._parse_tuple_or_name()

            self._expect(TokenType.ASSIGN)

            # Check for tuple unpacking
            from kida.nodes import Tuple as KidaTuple

            is_tuple_unpack = isinstance(target, KidaTuple) and len(target.items) > 1

            if is_tuple_unpack:
                # Tuple unpacking: use _parse_tuple_or_expression for RHS
                value = self._parse_tuple_or_expression()
                lets.append(
                    Let(
                        lineno=start.lineno,
                        col_offset=start.col_offset,
                        name=target,  # node.name can now be an Expr (Name or Tuple)
                        value=value,
                    )
                )
                # Tuple unpacking cannot be combined with multi-let
                break
            else:
                # Single target: use _parse_expression to preserve commas
                value = self._parse_expression()

                # Check for multi-let continuation
                if self._current.type == TokenType.COMMA:
                    if self._is_multi_set_continuation():
                        lets.append(
                            Let(
                                lineno=start.lineno,
                                col_offset=start.col_offset,
                                name=target,
                                value=value,
                            )
                        )
                        self._advance()  # consume comma
                        continue
                    else:
                        # Handle tuple value assignment: let x = 1, 2, 3
                        items = [value]
                        while self._current.type == TokenType.COMMA:
                            self._advance()
                            if self._match(TokenType.BLOCK_END):
                                break
                            items.append(self._parse_expression())

                        if len(items) > 1:
                            value = KidaTuple(
                                lineno=items[0].lineno,
                                col_offset=items[0].col_offset,
                                items=tuple(items),
                                ctx="load",
                            )

                lets.append(
                    Let(
                        lineno=start.lineno,
                        col_offset=start.col_offset,
                        name=target,
                        value=value,
                    )
                )
                break

        self._expect(TokenType.BLOCK_END)

        # Return single node or list
        return lets[0] if len(lets) == 1 else lets

    def _parse_export(self) -> Export:
        """Parse {% export x = expr %}.

        Supports tuple unpacking:
            {% export a, b = 1, 2 %}
        """
        start = self._advance()  # consume 'export'

        # Parse target - can be single name or tuple for unpacking
        target = self._parse_tuple_or_name()

        self._expect(TokenType.ASSIGN)

        # Check for tuple unpacking
        from kida.nodes import Tuple as KidaTuple

        if isinstance(target, KidaTuple) and len(target.items) > 1:
            value = self._parse_tuple_or_expression()
        else:
            value = self._parse_expression()

        self._expect(TokenType.BLOCK_END)

        return Export(
            lineno=start.lineno,
            col_offset=start.col_offset,
            name=target,  # node.name can now be an Expr (Name or Tuple)
            value=value,
        )
