"""Control flow block parsing for Kida parser.

Provides mixin for parsing if/for control flow statements.

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 AsyncFor, Break, Continue, For, If, Match, UnaryOp, While

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

from kida.parser.blocks.core import BlockStackMixin


class ControlFlowBlockParsingMixin(BlockStackMixin):
    """Mixin for parsing control flow 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 _error(
            self,
            message: str,
            token: Token | None = None,
            suggestion: str | None = None,
            code: ErrorCode | None = None,
        ) -> ParseError: ...

        # From StatementParsingMixin
        def _parse_body(self, stop_on_continuation: bool = False) -> list[Node]: ...
        def _parse_for_target(self) -> Expr: ...
        def _parse_tuple_or_expression(self) -> Expr: ...
        def _parse_tuple_or_null_coalesce_no_ternary(self) -> Expr: ...
        def _skip_comment(self) -> None: ...

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

    def _parse_unless(self) -> If:
        """Parse {% unless cond %} as {% if not cond %}.

        Part of RFC: kida-modern-syntax-features.
        Supports {% end %}, {% endif %}, and {% endunless %}.
        """
        start = self._advance()  # consume 'unless'
        self._push_block("unless", start)  # Track as 'unless' for endunless support

        # Parse condition
        condition = self._parse_expression()
        self._expect(TokenType.BLOCK_END)

        # Parse body, stopping on continuation (else) or end keywords
        body = self._parse_body(stop_on_continuation=True)

        else_: list[Node] = []

        # Handle optional else clause
        while self._current.type == TokenType.BLOCK_BEGIN:
            next_tok = self._peek(1)
            if next_tok.type != TokenType.NAME:
                break

            keyword = next_tok.value

            if keyword == "else":
                self._advance()  # consume {%
                self._advance()  # consume 'else'
                self._expect(TokenType.BLOCK_END)
                else_ = self._parse_body(stop_on_continuation=False)
            elif keyword in ("end", "endif", "endunless"):
                self._consume_end_tag("unless")
                break
            else:
                break

        # Create If with negated condition: unless x == if not x
        return If(
            lineno=start.lineno,
            col_offset=start.col_offset,
            test=UnaryOp(
                lineno=condition.lineno,
                col_offset=condition.col_offset,
                op="not",
                operand=condition,
            ),
            body=tuple(body),
            elif_=(),
            else_=tuple(else_),
        )

    def _parse_break(self) -> Break:
        """Parse {% break %} loop control.

        Part of RFC: kida-modern-syntax-features.
        """
        start = self._advance()  # consume 'break'

        if not self._in_loop():
            raise self._error(
                "'break' outside loop",
                suggestion="Use 'break' only inside {% for %} or {% while %} loops",
            )

        self._expect(TokenType.BLOCK_END)
        return Break(lineno=start.lineno, col_offset=start.col_offset)

    def _parse_continue(self) -> Continue:
        """Parse {% continue %} loop control.

        Part of RFC: kida-modern-syntax-features.
        """
        start = self._advance()  # consume 'continue'

        if not self._in_loop():
            raise self._error(
                "'continue' outside loop",
                suggestion="Use 'continue' only inside {% for %} or {% while %} loops",
            )

        self._expect(TokenType.BLOCK_END)
        return Continue(lineno=start.lineno, col_offset=start.col_offset)

    def _parse_while(self) -> While:
        """Parse {% while cond %}...{% end %} or {% endwhile %}.

        Kida-native while loop for condition-based iteration.

        Syntax:
            {% while items | length > 0 %}
                {{ items | pop }}
            {% end %}

        Part of RFC: kida-2.0-moonshot (While Loops).
        """
        start = self._advance()  # consume 'while'
        self._push_block("while", start)

        # Parse condition
        condition = self._parse_expression()
        self._expect(TokenType.BLOCK_END)

        # Parse body
        body = self._parse_body(stop_on_continuation=False)

        # Consume end tag
        self._consume_end_tag("while")

        return While(
            lineno=start.lineno,
            col_offset=start.col_offset,
            test=condition,
            body=tuple(body),
        )

    def _parse_if(self) -> If:
        """Parse {% if %} ... {% end %} or {% endif %}.

        Supports unified {% end %} as well as explicit {% endif %}.
        Also handles {% elif %} and {% else %} clauses.
        """
        start = self._advance()  # consume 'if'
        self._push_block("if", start)

        test = self._parse_expression()
        self._expect(TokenType.BLOCK_END)

        # Parse body, stopping on continuation (elif/else) or end keywords
        body = self._parse_body(stop_on_continuation=True)

        elif_: list[tuple[Expr, Sequence[Node]]] = []
        else_: list[Node] = []

        # Now we're at {% elif/else/end/endif
        while self._current.type == TokenType.BLOCK_BEGIN:
            next_tok = self._peek(1)
            if next_tok.type != TokenType.NAME:
                break

            keyword = next_tok.value

            if keyword == "elif":
                self._advance()  # consume {%
                self._advance()  # consume 'elif'
                elif_test = self._parse_expression()
                self._expect(TokenType.BLOCK_END)
                elif_body = self._parse_body(stop_on_continuation=True)
                elif_.append((elif_test, tuple(elif_body)))
            elif keyword == "else":
                self._advance()  # consume {%
                self._advance()  # consume 'else'
                self._expect(TokenType.BLOCK_END)
                # After else, only stop on end keywords (no more elif)
                else_ = self._parse_body(stop_on_continuation=False)
            elif keyword in ("end", "endif"):
                # Consume the end tag and pop from stack
                self._consume_end_tag("if")
                break
            else:
                # Unknown keyword - let parent handle it
                break

        return If(
            lineno=start.lineno,
            col_offset=start.col_offset,
            test=test,
            body=tuple(body),
            elif_=tuple(elif_),
            else_=tuple(else_),
        )

    def _parse_async(self) -> AsyncFor:
        """Parse {% async for %} — dispatcher for async constructs.

        Two-token lookahead: consumes 'async', expects 'for' next.
        Part of RFC: rfc-async-rendering.
        """
        start = self._advance()  # consume 'async'

        if self._current.type == TokenType.NAME and self._current.value == "for":
            return self._parse_async_for(start)

        raise self._error(
            "Expected 'for' after 'async'",
            suggestion="Async blocks use: {% async for item in items %}...{% end %}",
        )

    def _parse_async_for(self, start: Token) -> AsyncFor:
        """Parse {% async for %} ... {% end %} or {% endfor %}.

        Mirrors _parse_for() but produces an AsyncFor node for native async
        iteration. Supports inline if filter and {% empty %} clause.
        Does not support recursive loops.

        Part of RFC: rfc-async-rendering.
        """
        self._advance()  # consume 'for'
        self._push_block("for", start)  # reuse 'for' block for end-tag matching

        # Parse target (loop variable or tuple for unpacking)
        target = self._parse_for_target()

        # Expect 'in'
        if self._current.type != TokenType.IN:
            raise self._error(
                "Expected 'in' in async for loop",
                suggestion=(
                    "Async for loops use: {% async for item in items %}"
                    " or {% async for a, b in items %}"
                ),
            )
        self._advance()

        # Parse iterable - use _parse_null_coalesce_no_ternary() to support ??
        # but avoid parsing 'if' as ternary.
        iter_expr = self._parse_null_coalesce_no_ternary()

        # Check for inline filter: {% async for x in items if condition %}
        test = None
        if self._current.type == TokenType.NAME and self._current.value == "if":
            self._advance()  # consume 'if'
            test = self._parse_or()

        self._expect(TokenType.BLOCK_END)

        # Parse body - stop at continuation (else/empty) or end keywords
        body = self._parse_body(stop_on_continuation=True)

        empty: list[Node] = []

        # Handle {% else %}, {% empty %}, {% end %}, {% endfor %}
        while self._current.type == TokenType.BLOCK_BEGIN:
            next_tok = self._peek(1)
            if next_tok.type != TokenType.NAME:
                break

            keyword = next_tok.value

            if keyword in ("else", "empty"):
                self._advance()  # consume {%
                self._advance()  # consume 'else' or 'empty'
                self._expect(TokenType.BLOCK_END)
                empty = self._parse_body(stop_on_continuation=False)
            elif keyword in ("end", "endfor"):
                self._consume_end_tag("for")
                break
            else:
                break

        return AsyncFor(
            lineno=start.lineno,
            col_offset=start.col_offset,
            target=target,
            iter=iter_expr,
            body=tuple(body),
            empty=tuple(empty),
            test=test,
        )

    def _parse_for(self) -> For:
        """Parse {% for %} ... {% end %} or {% endfor %.

        Supports unified {% end %} as well as explicit {% endfor %}.
        Also handles {% else %} and {% empty %} clauses.
        Supports inline if filter: {% for x in items if x.visible %}
        Part of RFC: kida-modern-syntax-features (inline if).
        """
        start = self._advance()  # consume 'for'
        self._push_block("for", start)

        # Parse target (loop variable or tuple for unpacking)
        # Can be: item, (a, b), or a, b, c
        target = self._parse_for_target()

        # Expect 'in'
        if self._current.type != TokenType.IN:
            raise self._error(
                "Expected 'in' in for loop",
                suggestion="For loops use: {% for item in items %} or {% for a, b in items %}",
            )
        self._advance()

        # Parse iterable - use _parse_null_coalesce_no_ternary() to support ??
        # but avoid parsing 'if' as ternary. This allows both:
        #   {% for x in items ?? [] %}           ← null coalescing
        #   {% for x in items if x.visible %}    ← inline filter
        # Part of RFC: kida-modern-syntax-features
        iter_expr = self._parse_null_coalesce_no_ternary()

        # Check for inline filter: {% for x in items if condition %}
        # Part of RFC: kida-modern-syntax-features
        test = None
        if self._current.type == TokenType.NAME and self._current.value == "if":
            self._advance()  # consume 'if'
            test = self._parse_or()  # Parse the condition (also without ternary)

        self._expect(TokenType.BLOCK_END)

        # Parse body - stop at continuation (else/empty) or end keywords
        body = self._parse_body(stop_on_continuation=True)

        empty: list[Node] = []

        # Now at {% else, {% empty, {% end, or {% endfor
        while self._current.type == TokenType.BLOCK_BEGIN:
            next_tok = self._peek(1)
            if next_tok.type != TokenType.NAME:
                break

            keyword = next_tok.value

            if keyword in ("else", "empty"):
                self._advance()  # consume {%
                self._advance()  # consume 'else' or 'empty'
                self._expect(TokenType.BLOCK_END)
                # After else/empty, only stop on end keywords
                empty = self._parse_body(stop_on_continuation=False)
            elif keyword in ("end", "endfor"):
                # Consume the end tag and pop from stack
                self._consume_end_tag("for")
                break
            else:
                # Unknown keyword - let parent handle it
                break

        return For(
            lineno=start.lineno,
            col_offset=start.col_offset,
            target=target,
            iter=iter_expr,
            body=tuple(body),
            empty=tuple(empty),
            test=test,
        )

    def _parse_match(self) -> Match:
        """Parse {% match expr %}{% case pattern %}...{% end %}.

        Pattern matching for cleaner branching than if/elif chains.
        Reuses the existing _parse_body infrastructure for case bodies.

        Syntax:
            {% match page.type %}
                {% case "post" %}...
                {% case "gallery" %}...
                {% case _ %}...
            {% end %}

        Valueless match (switch-true):
            {% match %}
                {% case _ if user.is_admin %}Admin
                {% case _ %}Member
            {% end %}

        The underscore (_) is the wildcard/default case.
        """
        start = self._advance()  # consume 'match'
        self._push_block("match", start)

        # Valueless match: {% match %} with no subject expression
        subject: Expr | None
        if self._current.type == TokenType.BLOCK_END:
            subject = None
        else:
            # Parse subject expression (supports implicit tuples: {% match a, b %})
            subject = self._parse_tuple_or_expression()
        self._expect(TokenType.BLOCK_END)

        cases: list[tuple[Expr, Expr | None, Sequence[Node]]] = []

        # Parse case clauses
        # Skip DATA tokens (whitespace between {% match %} and {% case %})
        while self._current.type != TokenType.EOF:
            # Skip DATA tokens (whitespace/text between blocks)
            if self._current.type == TokenType.DATA:
                self._advance()
                continue

            # Skip comments
            if self._current.type == TokenType.COMMENT_BEGIN:
                self._skip_comment()
                continue

            if self._current.type != TokenType.BLOCK_BEGIN:
                break

            next_tok = self._peek(1)
            if next_tok.type != TokenType.NAME:
                break

            keyword = next_tok.value

            if keyword == "case":
                self._advance()  # consume {%
                self._advance()  # consume 'case'

                # Parse pattern expression - use _parse_tuple_or_null_coalesce_no_ternary
                # to avoid consuming 'if' as a ternary conditional and to support
                # implicit tuples: {% case 1, 2 if guard %}
                pattern = self._parse_tuple_or_null_coalesce_no_ternary()

                # Check for optional guard clause: {% case pattern if guard %}
                guard: Expr | None = None
                if self._current.type == TokenType.NAME and self._current.value == "if":
                    self._advance()  # consume 'if'
                    guard = self._parse_expression()

                self._expect(TokenType.BLOCK_END)

                # Parse case body - reuse existing _parse_body
                # stop_on_continuation=True stops at next "case" or end keywords
                body = self._parse_body(stop_on_continuation=True)
                cases.append((pattern, guard, tuple(body)))

            elif keyword in ("end", "endmatch"):
                self._consume_end_tag("match")
                break
            else:
                # Unknown keyword - error
                raise self._error(
                    f"Expected 'case' or 'end' in match block, got '{keyword}'",
                    suggestion="Match blocks contain {% case pattern %} clauses",
                )

        if not cases:
            raise self._error(
                "Match block must have at least one {% case %} clause",
                suggestion="Add {% case value %}...{% end %} inside the match block",
            )

        return Match(
            lineno=start.lineno,
            col_offset=start.col_offset,
            subject=subject,
            cases=tuple(cases),
        )
