"""Main list parsing mixin.

Provides the ListParsingMixin class that orchestrates list parsing
using the modular helper functions.
"""

from typing import TYPE_CHECKING

from patitas.lexer.modes import (
    HTML_BLOCK_TYPE1_TAGS,
    HTML_BLOCK_TYPE6_TAGS,
)
from patitas.nodes import (
    Block,
    Heading,
    HtmlBlock,
    IndentedCode,
    List,
    ListItem,
    Paragraph,
)
from patitas.parsing.blocks.list.blank_line import (
    ContinueList,
    EndItem,
    EndList,
    ParseBlock,
    ParseContinuation,
    handle_blank_line,
)
from patitas.parsing.blocks.list.fast_path import (
    is_simple_list,
    parse_simple_list,
)
from patitas.parsing.blocks.list.indent import (
    is_nested_list_indent,
)
from patitas.parsing.blocks.list.item_blocks import (
    handle_fenced_code_immediate,
    handle_thematic_break,
    parse_block_quote_from_indented_code,
    parse_fenced_code_from_indented_code,
    parse_indented_code_in_list,
)
from patitas.parsing.blocks.list.marker import (
    extract_marker_info,
    extract_task_marker,
    get_marker_indent,
    is_list_marker,
    is_same_list_type,
)
from patitas.parsing.blocks.list.nested import (
    detect_nested_block_in_content,
    parse_nested_list_from_indented_code,
    parse_nested_list_inline,
)
from patitas.parsing.containers import (
    ContainerFrame,
    ContainerType,
)
from patitas.tokens import TokenType

if TYPE_CHECKING:
    from patitas.parsing.containers import ContainerStack
    from patitas.tokens import Token


class ListParsingMixin:
    """Mixin for list parsing.

    Handles nested lists, task lists, continuation paragraphs, and loose/tight detection.

    Required Host Attributes:
        - _source: str
        - _tokens: list[Token]
        - _pos: int
        - _current: Token | None
        - _containers: ContainerStack (Phase 2 shadow stack)

    Required Host Methods:
        - _at_end() -> bool
        - _advance() -> Token | None
        - _parse_inline(text, location) -> tuple[Inline, ...]
        - _parse_block() -> Block | None
        - _get_line_at(offset) -> str
        - _strip_columns(text, count) -> str

    """

    _source: str
    _tokens: list
    _pos: int
    _current: Token | None
    _containers: ContainerStack

    def _parse_list(self, parent_indent: int = -1) -> List:
        """Parse list (unordered or ordered) with nested list support.

        Args:
            parent_indent: Indent level of parent list (-1 for top-level)

        Handles:
        - Nested lists via indentation tracking
        - Task lists with [ ] and [x] markers
        - Multi-line list items (continuation paragraphs)
        - Loose lists (blank lines between items)
        """
        start_token = self._current
        assert start_token is not None and start_token.type == TokenType.LIST_ITEM_MARKER

        # Fast path: simple lists at indent 0 with no nesting/block quotes
        # Bypasses ContainerStack overhead for ~5-8% performance gain
        if (
            parent_indent == -1
            and not self._containers._stack
            and is_simple_list(self._tokens, self._pos)
        ):
            list_node, new_pos = parse_simple_list(
                self._tokens,
                self._pos,
                self._parse_inline,
            )
            # Update parser position
            self._pos = new_pos
            self._current = self._tokens[new_pos] if new_pos < self._tokens_len else None
            return list_node

        # Extract marker info
        marker_info = extract_marker_info(start_token.value)
        start_indent = marker_info.indent
        ordered = marker_info.ordered
        bullet_char = marker_info.bullet_char
        ordered_marker_char = marker_info.ordered_marker_char
        start = marker_info.start

        # Calculate content indent
        content_indent = start_indent + marker_info.marker_length + 1

        # Track whether this list is nested inside a block quote
        inside_block_quote = any(
            frame.container_type == ContainerType.BLOCK_QUOTE for frame in self._containers._stack
        )

        # Phase 2 Shadow Stack: Push LIST container frame
        # This tracks the list's indent context for validation.
        # In Phase 3, this will become the source of truth.
        list_frame = ContainerFrame(
            container_type=ContainerType.LIST,
            start_indent=start_indent,
            content_indent=content_indent,
            marker_width=marker_info.marker_length,
            ordered=ordered,
            bullet_char=bullet_char,
            start_number=start,
        )
        self._containers.push(list_frame)

        items: list[ListItem] = []

        while not self._at_end():
            token = self._current
            assert token is not None

            # Handle blank lines between items (makes list loose)
            if token.type == TokenType.BLANK_LINE:
                # Phase 3.2: Use stack-based loose detection
                self._containers.mark_loose()
                while not self._at_end() and self._current.type == TokenType.BLANK_LINE:
                    self._advance()
                if self._at_end():
                    break
                token = self._current
                if token.type != TokenType.LIST_ITEM_MARKER:
                    break

            if token.type != TokenType.LIST_ITEM_MARKER:
                break

            current_indent = get_marker_indent(token.value)

            # If less indented than our list, we're done
            if current_indent < start_indent:
                break

            # If at or beyond content indent, it's nested
            if current_indent >= content_indent:
                break

            # Check if same list type
            if not is_same_list_type(token.value, ordered, bullet_char, ordered_marker_char):
                break

            self._advance()

            # Update content indent for this item
            current_marker = token.value.lstrip()
            current_marker_length = len(current_marker.split()[0]) if current_marker.split() else 1
            content_indent = current_indent + current_marker_length + 1

            # Parse item content
            item = self._parse_list_item(
                token,
                start_indent,
                content_indent,
                ordered,
                bullet_char,
                ordered_marker_char,
                current_marker,
            )
            items.append(item)

        # Phase 3.2: Read tight/loose from stack before popping
        # Looseness is now tracked in the stack and propagates from child
        # containers via pop(). If is_loose is True, the list is loose.
        tight = not self._containers.current().is_loose
        self._containers.pop()

        # Normalize misclassified indented code only when inside block quote
        if not inside_block_quote:
            final_items = items
        else:
            normalized_items: list[ListItem] = []
            for item in items:
                fixed_children = []
                for child in item.children:
                    if (
                        isinstance(child, IndentedCode)
                        and child.location.col_offset <= content_indent
                    ):
                        text = child.code.rstrip("\n")
                        inlines = self._parse_inline(text, child.location)
                        fixed_children.append(Paragraph(location=child.location, children=inlines))
                    else:
                        fixed_children.append(child)
                normalized_items.append(
                    ListItem(
                        location=item.location,
                        children=tuple(fixed_children),
                        checked=item.checked,
                    )
                )
            final_items = normalized_items

        return List(
            location=start_token.location,
            items=tuple(final_items),
            ordered=ordered,
            start=start,
            tight=tight,
        )

    def _parse_list_item(
        self,
        marker_token: Token,
        start_indent: int,
        content_indent: int,
        ordered: bool,
        bullet_char: str,
        ordered_marker_char: str,
        marker_stripped: str,
    ) -> ListItem:
        """Parse a single list item.

        Phase 3.2: Uses stack-based loose detection. Looseness is marked on
        the current frame via mark_loose() and propagates to the parent on pop().

        Returns:
            ListItem node
        """
        # Phase 2 Shadow Stack: Push LIST_ITEM container frame
        item_frame = ContainerFrame(
            container_type=ContainerType.LIST_ITEM,
            start_indent=start_indent,
            content_indent=content_indent,
        )
        self._containers.push(item_frame)

        item_children: list[Block] = []
        content_lines: list[str] = []
        checked: bool | None = None
        actual_content_indent: int | None = None
        saw_paragraph_content = False

        while not self._at_end():
            tok = self._current
            assert tok is not None

            # INDENTED_CODE at content indent inside block quotes should behave like
            # paragraph continuation, not code (CommonMark 259/260).
            if tok.type == TokenType.INDENTED_CODE and any(
                frame.container_type == ContainerType.BLOCK_QUOTE
                for frame in self._containers._stack
            ):
                indent_beyond = tok.line_indent - content_indent
                if indent_beyond < 4:
                    content_lines.append(tok.value.lstrip())
                    saw_paragraph_content = True
                    self._advance()
                    continue
                content_lines.append(tok.value.lstrip())
                saw_paragraph_content = True
                self._advance()
                continue

            # Handle thematic break
            if tok.type == TokenType.THEMATIC_BREAK:
                # Setext underline inside list item (CommonMark example 300)
                if (
                    saw_paragraph_content
                    and content_lines
                    and tok.line_indent >= content_indent
                    and tok.value.strip()
                    and all(c == "-" for c in tok.value.strip())
                ):
                    heading_text = "\n".join(content_lines).rstrip()
                    children = self._parse_inline(heading_text, marker_token.location)
                    item_children.append(
                        Heading(
                            location=marker_token.location,
                            level=2,
                            children=children,
                            style="setext",
                        )
                    )
                    content_lines = []
                    saw_paragraph_content = False
                    self._advance()
                    continue
                block, should_continue = handle_thematic_break(
                    tok, saw_paragraph_content, bool(content_lines), self
                )
                if block:
                    item_children.append(block)
                if not should_continue:
                    break
                continue

            # Handle fenced code immediately after marker (no prior content)
            if (
                tok.type == TokenType.FENCED_CODE_START
                and not saw_paragraph_content
                and not content_lines
            ):
                block, should_continue = handle_fenced_code_immediate(
                    tok, saw_paragraph_content, bool(content_lines), content_indent, self
                )
                if block:
                    item_children.append(block)
                if not should_continue:
                    break
                continue

            # Handle paragraph content
            if tok.type == TokenType.PARAGRAPH_LINE:
                # Treat single-line HTML tags as HTML blocks inside list items (CommonMark 175)
                stripped_line = tok.value.lstrip()
                if stripped_line.startswith("<"):
                    # Simple tag name extraction (letters/digits/hyphen)
                    tag = ""
                    idx = 1
                    if idx < len(stripped_line) and stripped_line[idx] == "/":
                        idx += 1
                    while idx < len(stripped_line) and (
                        stripped_line[idx].isalnum() or stripped_line[idx] == "-"
                    ):
                        tag += stripped_line[idx].lower()
                        idx += 1
                    if tag in HTML_BLOCK_TYPE1_TAGS or tag in HTML_BLOCK_TYPE6_TAGS:
                        html_content = tok.value
                        if not html_content.endswith("\n"):
                            html_content += "\n"
                        item_children.append(HtmlBlock(location=tok.location, html=html_content))
                        saw_paragraph_content = False
                        self._advance()
                        continue

                # CommonMark: If first content line has more than 4 spaces between
                # marker and content, treat it as indented code, not paragraph.
                # We count effective column width (with tabs expanded) from the
                # marker character (like "." or "-") to the first content character.
                if actual_content_indent is None and not content_lines:
                    # Find the original line containing this content
                    line_start = self._source.rfind("\n", 0, tok.location.offset) + 1
                    line_end = self._source.find("\n", tok.location.offset)
                    if line_end == -1:
                        line_end = len(self._source)
                    original_line = self._source[line_start:line_end]

                    # Get just the marker character (e.g., "-" or "1.")
                    marker_char = marker_stripped.rstrip()
                    marker_pos = original_line.find(marker_char)

                    if marker_pos != -1:
                        # Calculate effective column width with tabs expanded
                        after_marker = original_line[marker_pos + len(marker_char) :]
                        # Count effective spaces (expand tabs to tab stops)
                        spaces_after_marker = 0
                        col = marker_pos + len(marker_char)  # Column after marker (0-indexed)
                        for ch in after_marker:
                            if ch == " ":
                                spaces_after_marker += 1
                                col += 1
                            elif ch == "\t":
                                # Tab expands to next tab stop (tab stops every 4 columns)
                                tab_width = 4 - (col % 4)
                                spaces_after_marker += tab_width
                                col += tab_width
                            else:
                                break  # Non-whitespace character
                    else:
                        # Fallback: use token value leading spaces (with tab expansion)
                        spaces_after_marker = 0
                        col = 0
                        for ch in tok.value:
                            if ch == " ":
                                spaces_after_marker += 1
                                col += 1
                            elif ch == "\t":
                                tab_width = 4 - (col % 4)
                                spaces_after_marker += tab_width
                                col += tab_width
                            else:
                                break

                    in_block_quote = any(
                        frame.container_type == ContainerType.BLOCK_QUOTE
                        for frame in self._containers._stack
                    )

                    if spaces_after_marker > 4 and not in_block_quote:
                        # This is indented code - extract from original line,
                        # stripping marker and 4 column-widths of indentation
                        # Use after_marker content with proper tab handling
                        remaining_content = after_marker
                        cols_to_strip = 5  # 1 for marker space + 4 for indented code
                        col = marker_pos + len(marker_char)  # Start column after marker
                        pos = 0
                        while (
                            pos < len(remaining_content)
                            and col < marker_pos + len(marker_char) + cols_to_strip
                        ):
                            ch = remaining_content[pos]
                            if ch == " ":
                                col += 1
                                pos += 1
                            elif ch == "\t":
                                tab_width = 4 - (col % 4)
                                target_col = marker_pos + len(marker_char) + cols_to_strip
                                if col + tab_width <= target_col:
                                    col += tab_width
                                    pos += 1
                                else:
                                    # Tab would go past target - keep partial as spaces
                                    extra_spaces = target_col - col
                                    remaining_content = (
                                        " " * (tab_width - extra_spaces)
                                        + remaining_content[pos + 1 :]
                                    )
                                    pos = 0
                                    col = target_col
                            else:
                                break
                        code_content = remaining_content[pos:].rstrip() + "\n"
                        item_children.append(IndentedCode(location=tok.location, code=code_content))
                        # Set actual_content_indent to marker_end + 1 (CommonMark rule)
                        actual_content_indent = content_indent
                        self._containers.update_content_indent(actual_content_indent)
                        self._advance()
                        continue

                line = tok.value.lstrip()

                # Skip whitespace-only lines immediately after marker (test 279)
                # These are from trailing whitespace on the marker line
                if not line and not content_lines and not saw_paragraph_content:
                    self._advance()
                    continue

                # Check for nested list marker at start of content
                if not content_lines and not saw_paragraph_content and line:
                    check_indent = (
                        actual_content_indent
                        if actual_content_indent is not None
                        else content_indent
                    )
                    if detect_nested_block_in_content(line, tok.line_indent, check_indent):
                        # Config inherited automatically via ContextVar!
                        blocks = parse_nested_list_inline(
                            line + "\n",
                            tok.location,
                            self,
                        )
                        item_children.extend(blocks)
                        self._advance()
                        continue

                # Calculate actual content indent from first line
                if actual_content_indent is None:
                    actual_content_indent = self._calculate_actual_content_indent(
                        tok, marker_stripped
                    )
                    # Phase 3: Update the stack frame with actual content indent
                    # This enables find_owner() to use the correct value
                    self._containers.update_content_indent(actual_content_indent)

                # Check for task list marker
                if not content_lines and checked is None:
                    checked, line = extract_task_marker(line)

                content_lines.append(line)
                saw_paragraph_content = True
                self._advance()

            # Handle indented code
            elif tok.type == TokenType.INDENTED_CODE:
                # Phase 4: Simplified signature - stack provides indent context
                result = self._handle_indented_code_in_item(
                    tok,
                    marker_token,
                    content_lines,
                    item_children,
                )
                if result == "break":
                    break
                elif result == "continue":
                    continue
                elif isinstance(result, tuple):
                    content_lines, item_children = result

            # Handle blank line
            elif tok.type == TokenType.BLANK_LINE:
                # CommonMark test 280: Blank line immediately after empty marker
                # creates an empty list item and ends it
                if not content_lines and not item_children and not saw_paragraph_content:
                    # Empty item - don't consume the blank line, let parent handle it
                    break

                self._advance()
                # Consume consecutive blank lines
                while not self._at_end() and self._current.type == TokenType.BLANK_LINE:
                    self._advance()

                if self._at_end():
                    break

                # Phase 4: Use stack-based blank line handling
                result = handle_blank_line(
                    self._current,
                    self._containers,
                )

                if isinstance(result, EndList):
                    break
                elif isinstance(result, EndItem):
                    # Phase 3.2: Blank line before sibling item = parent list is loose
                    # We're in LIST_ITEM, so mark parent LIST as loose
                    self._containers.mark_parent_list_loose()
                    break
                elif isinstance(result, ContinueList):
                    if result.is_loose:
                        # Phase 3.2: Use stack-based loose detection
                        self._containers.mark_loose()
                    if result.save_paragraph and content_lines:
                        content = "\n".join(content_lines)
                        inlines = self._parse_inline(content, marker_token.location)
                        item_children.append(
                            Paragraph(location=marker_token.location, children=inlines)
                        )
                        content_lines = []
                    self._advance()
                    continue
                elif isinstance(result, ParseBlock):
                    # Phase 3.2: Use stack-based loose detection
                    self._containers.mark_loose()
                    if content_lines:
                        content = "\n".join(content_lines)
                        inlines = self._parse_inline(content, marker_token.location)
                        item_children.append(
                            Paragraph(location=marker_token.location, children=inlines)
                        )
                        content_lines = []

                    # Phase 5: Handle INDENTED_CODE specially within list context
                    # The lexer marks 4+ spaces as INDENTED_CODE, but within a list
                    # item, we need to re-interpret based on content_indent
                    next_tok = self._current
                    if next_tok and next_tok.type == TokenType.INDENTED_CODE:
                        check_indent = (
                            actual_content_indent
                            if actual_content_indent is not None
                            else content_indent
                        )
                        stripped = next_tok.value.lstrip().rstrip()
                        indent_beyond = next_tok.line_indent - check_indent

                        # Nested list marker at content indent
                        if is_list_marker(stripped):
                            nested_list = parse_nested_list_from_indented_code(
                                next_tok, next_tok.line_indent, check_indent, self
                            )
                            if nested_list:
                                item_children.append(nested_list)
                            continue

                        # Block quote at content indent
                        if stripped.startswith(">"):
                            bq = parse_block_quote_from_indented_code(next_tok, self, check_indent)
                            item_children.append(bq)
                            continue

                        # Fenced code at content indent
                        if stripped.startswith(("```", "~~~")):
                            fc = parse_fenced_code_from_indented_code(next_tok, self, check_indent)
                            item_children.append(fc)
                            continue

                        # Actual indented code (4+ beyond content_indent)
                        if indent_beyond >= 4:
                            ic = parse_indented_code_in_list(next_tok, self, check_indent)
                            item_children.append(ic)
                            continue

                    # Default: use standard block parsing
                    block = self._parse_block()
                    if block is not None:
                        item_children.append(block)
                    continue
                elif isinstance(result, ParseContinuation):
                    # Phase 3.2: Use stack-based loose detection
                    self._containers.mark_loose()
                    if result.save_paragraph and content_lines:
                        content = "\n".join(content_lines)
                        inlines = self._parse_inline(content, marker_token.location)
                        item_children.append(
                            Paragraph(location=marker_token.location, children=inlines)
                        )
                        content_lines = []

                    # Handle continuation content directly
                    # INDENTED_CODE at content indent is paragraph content, not code
                    next_tok = self._current
                    if next_tok and next_tok.type == TokenType.INDENTED_CODE:
                        check_indent = (
                            actual_content_indent
                            if actual_content_indent is not None
                            else content_indent
                        )
                        orig_indent = next_tok.line_indent
                        indent_beyond = orig_indent - check_indent
                        # At content indent (not 4+ beyond) = paragraph content
                        # Must be AT or BEYOND content_indent to be continuation
                        if orig_indent >= check_indent and indent_beyond < 4:
                            # Strip leading whitespace - lexer already removed 4 spaces,
                            # but content at content_indent should have no leading space
                            content_lines.append(next_tok.value.strip())
                            self._advance()
                            continue
                    elif next_tok and next_tok.type == TokenType.PARAGRAPH_LINE:
                        # Continuation paragraph
                        content_lines.append(next_tok.value.lstrip())
                        self._advance()
                        continue

                    continue

            # Handle nested list markers
            elif tok.type == TokenType.LIST_ITEM_MARKER:
                nested_indent = get_marker_indent(tok.value)
                check_content_indent = (
                    actual_content_indent if actual_content_indent is not None else content_indent
                )

                # Check if different marker at same indent (new list)
                if nested_indent == start_indent and not is_same_list_type(
                    tok.value, ordered, bullet_char, ordered_marker_char
                ):
                    break

                if is_nested_list_indent(nested_indent, check_content_indent):
                    # Save current paragraph
                    if content_lines:
                        content = "\n".join(content_lines)
                        inlines = self._parse_inline(content, marker_token.location)
                        item_children.append(
                            Paragraph(location=marker_token.location, children=inlines)
                        )
                        content_lines = []

                    # Parse nested list
                    nested_list = self._parse_list(parent_indent=start_indent)
                    item_children.append(nested_list)

                    # After nested list, check if there's a blank line before the next token
                    # This makes the outer list loose
                    if not self._at_end():
                        next_tok = self._current
                        # Check if there was a blank line before current token by looking at source
                        if next_tok.location.offset > 0:
                            # Find the line start
                            line_start = self._source.rfind("\n", 0, next_tok.location.offset) + 1
                            if line_start > 1:
                                prev_char = self._source[line_start - 2 : line_start]
                                if prev_char == "\n\n" or (
                                    line_start >= 2
                                    and self._source[line_start - 1] == "\n"
                                    and self._source[line_start - 2] == "\n"
                                ):
                                    # There was a blank line before this token
                                    # Phase 3.2: Use stack-based loose detection
                                    self._containers.mark_loose()

                        if next_tok.type == TokenType.PARAGRAPH_LINE:
                            next_indent = next_tok.line_indent
                            # Content at outer item's content indent = continuation
                            # Use content_indent (not check_content_indent) for comparison
                            if next_indent >= start_indent and next_indent <= content_indent:
                                # Blank line occurred (nested list ended), making list loose
                                # Phase 3.2: Use stack-based loose detection
                                self._containers.mark_loose()
                                content_lines.append(next_tok.value.lstrip())
                                self._advance()
                                continue
                elif nested_indent >= 4 and nested_indent < check_content_indent:
                    # Marker at 4+ spaces but not nested - literal content
                    marker_content = tok.value.lstrip()
                    self._advance()
                    if not self._at_end():
                        next_tok = self._current
                        if next_tok.type == TokenType.PARAGRAPH_LINE:
                            marker_content += " " + next_tok.value.lstrip()
                            self._advance()
                    content_lines.append(marker_content)
                else:
                    # Sibling item
                    break

            # Handle block-level elements at content indent
            elif tok.type in (
                TokenType.BLOCK_QUOTE_MARKER,
                TokenType.FENCED_CODE_START,
                TokenType.ATX_HEADING,
                TokenType.THEMATIC_BREAK,
            ):
                # Check if the block element is at content indent
                block_indent = tok.line_indent
                check_content_indent = (
                    actual_content_indent if actual_content_indent is not None else content_indent
                )
                if block_indent >= check_content_indent:
                    # Block element belongs to this item
                    if content_lines:
                        content = "\n".join(content_lines)
                        inlines = self._parse_inline(content, marker_token.location)
                        item_children.append(
                            Paragraph(location=marker_token.location, children=inlines)
                        )
                        content_lines = []
                    block = self._parse_block()
                    if block is not None:
                        item_children.append(block)
                    continue
                else:
                    # Block element is at list level - terminates item
                    break

            else:
                break

        # Finalize item content
        if content_lines:
            content = "\n".join(content_lines)
            inlines = self._parse_inline(content, marker_token.location)
            item_children.append(Paragraph(location=marker_token.location, children=inlines))

        # Phase 3.2: Pop the LIST_ITEM container frame
        # Looseness is tracked in the stack via mark_loose() calls.
        # The pop() will propagate looseness to the parent LIST frame.
        self._containers.pop()

        return ListItem(
            location=marker_token.location,
            children=tuple(item_children),
            checked=checked,
        )

    def _calculate_actual_content_indent(self, tok: Token, marker_stripped: str) -> int:
        """Calculate actual content indent from first content line.

        CommonMark: The content indent is the column position where the first
        non-space character appears after the marker. For continuation lines,
        content must be indented to at least this column.

        For example, in "1. a", the marker "1." ends at column 2, followed by
        a space, so content starts at column 3. Content indent = 3.
        """
        line_start = tok.location.offset
        line_start_pos = self._source.rfind("\n", 0, line_start) + 1
        if line_start_pos == 0:
            line_start_pos = 0
        original_line = self._source[line_start_pos:].split("\n")[0]

        marker_part = marker_stripped.split()[0] if marker_stripped.split() else marker_stripped
        marker_pos_in_line = original_line.find(marker_part)
        if marker_pos_in_line == -1:
            return get_marker_indent(tok.value) + len(marker_part) + 1

        # Calculate indent to start of marker (handles tabs correctly)
        marker_start_indent = get_marker_indent(original_line[:marker_pos_in_line])
        # Column position after marker = indent to marker + marker length
        marker_end_col = marker_start_indent + len(marker_part)

        rest_of_line = original_line[marker_pos_in_line + len(marker_part) :]
        if not rest_of_line or rest_of_line.isspace():
            return marker_end_col + 1

        spaces_after = len(rest_of_line) - len(rest_of_line.lstrip(" "))
        if spaces_after > 4:
            return marker_end_col + 1

        return marker_end_col + spaces_after

    def _handle_indented_code_in_item(
        self,
        tok: Token,
        marker_token: Token,
        content_lines: list[str],
        item_children: list[Block],
    ) -> str | tuple[list[str], list[Block]]:
        """Handle INDENTED_CODE token within a list item.

        Phase 4: Uses container stack as source of truth for indent context.
        The stack's content_indent is updated when actual_content_indent is
        determined, so current().content_indent reflects the correct value.

        Args:
            tok: The INDENTED_CODE token
            marker_token: The list item's marker token (for location info)
            content_lines: Current content lines being accumulated
            item_children: Current block children of the item

        Returns:
            "break" - break out of item loop
            "continue" - continue to next iteration
            (content_lines, item_children) - updated state
        """
        original_indent = tok.line_indent
        stripped_content = tok.value.lstrip()

        # Phase 4: Use container stack as source of truth
        current_frame = self._containers.current()
        check_indent = current_frame.content_indent

        if original_indent >= check_indent:
            # Check for nested list marker
            if is_list_marker(stripped_content):
                if content_lines:
                    content = "\n".join(content_lines)
                    inlines = self._parse_inline(content, marker_token.location)
                    item_children.append(
                        Paragraph(location=marker_token.location, children=inlines)
                    )
                    content_lines = []

                nested_list = parse_nested_list_from_indented_code(
                    tok, original_indent, check_indent, self
                )
                if nested_list:
                    item_children.append(nested_list)
                return (content_lines, item_children)

            # Check for block quote at content indent
            stripped = stripped_content.rstrip()
            if stripped.startswith(">"):
                if content_lines:
                    content = "\n".join(content_lines)
                    inlines = self._parse_inline(content, marker_token.location)
                    item_children.append(
                        Paragraph(location=marker_token.location, children=inlines)
                    )
                    content_lines = []
                bq = parse_block_quote_from_indented_code(tok, self, check_indent)
                item_children.append(bq)
                return (content_lines, item_children)

            # Check for fenced code at content indent
            if stripped.startswith(("```", "~~~")):
                if content_lines:
                    content = "\n".join(content_lines)
                    inlines = self._parse_inline(content, marker_token.location)
                    item_children.append(
                        Paragraph(location=marker_token.location, children=inlines)
                    )
                    content_lines = []
                fc = parse_fenced_code_from_indented_code(tok, self, check_indent)
                item_children.append(fc)
                return (content_lines, item_children)

            # Continuation at content indent
            if original_indent == check_indent and content_lines:
                content_lines.append(tok.value.rstrip())
                self._advance()
                return (content_lines, item_children)

            # More indented - actual indented code within the list item
            # CommonMark: 4+ spaces beyond content_indent = indented code
            indent_beyond = original_indent - check_indent
            if indent_beyond >= 4:
                if content_lines:
                    content = "\n".join(content_lines)
                    inlines = self._parse_inline(content, marker_token.location)
                    item_children.append(
                        Paragraph(location=marker_token.location, children=inlines)
                    )
                    content_lines = []
                # The lexer already stripped 4 spaces, strip remaining indent
                code_content = tok.value.strip() + "\n"
                item_children.append(IndentedCode(location=tok.location, code=code_content))
                self._advance()
                return (content_lines, item_children)

            # Not continuation and not code - break
            return "break"

        # original_indent < check_indent (guaranteed by control flow above)
        # Check if it's between start_indent and check_indent
        # In CommonMark, content at indentation between marker and content
        # column is literal content of the item (not a new marker or code)
        marker_indent = get_marker_indent(marker_token.value)
        if original_indent > marker_indent:
            # This is literal content of the item (e.g., "    - e" in example 312)
            content_lines.append(tok.value.rstrip())
            self._advance()
            return (content_lines, item_children)

        return "break"

    def _get_marker_indent(self, marker_value: str) -> int:
        """Extract indent level from list marker value.

        Marker values are prefixed with spaces by the lexer to encode indent.
        """
        return get_marker_indent(marker_value)

    def _parse_nested_list_from_indented_code(
        self, token: Token, original_indent: int, parent_content_indent: int
    ) -> List | None:
        """Parse a nested list from an INDENTED_CODE token containing a list marker."""
        return parse_nested_list_from_indented_code(
            token, original_indent, parent_content_indent, self
        )
