"""Talk Markdown - Block detection for streaming markdown content.

This module provides utilities for detecting completed markdown blocks
in streaming content, handling code fences properly.
"""

from __future__ import annotations

from typing import Final

from rich.console import Console
from rich.markdown import Markdown

__all__ = ["detect_completed_blocks", "StreamMarkdownRenderer"]


# @invar:allow shell_result: Stream markdown block detection utility used by CLI renderer.
# @shell_complexity: Step mcp-conflict-detection.fix-shell-complexity-debt-gate-readiness-followup preserves fence-aware streaming boundary branches to avoid behavior changes in markdown rendering
def detect_completed_blocks(buffer: str, in_fence: bool) -> tuple[list[str], str, bool]:
    """Detect completed markdown blocks in a streaming buffer.

    Splits the buffer on double newlines only when outside code fences.
    Lines starting with ``` toggle the fence state.

    Args:
        buffer: The accumulated text buffer to process.
        in_fence: Whether we are currently inside a code fence.

    Returns:
        A tuple of (completed_blocks, remaining_buffer, new_fence_state).

    Examples:
        >>> detect_completed_blocks("", False)
        ([], '', False)

        >>> detect_completed_blocks("Hello", False)
        ([], 'Hello', False)

        >>> detect_completed_blocks("Hello\\n", False)
        ([], 'Hello\\n', False)

        >>> detect_completed_blocks("Hello\\n\\n", False)
        (['Hello'], '', False)

        >>> detect_completed_blocks("Hello\\n\\nWorld", False)
        (['Hello'], 'World', False)

        >>> detect_completed_blocks("Hello\\n\\nWorld\\n\\n", False)
        (['Hello', 'World'], '', False)

        >>> detect_completed_blocks("```python\\ncode\\n```", False)
        ([], '```python\\ncode\\n```', False)

        >>> detect_completed_blocks("```python\\ncode\\n```\\n\\n", False)
        (['```python\\ncode\\n```'], '', False)

        >>> detect_completed_blocks("```python\\ncode\\n```", True)
        ([], '```python\\ncode\\n```', True)

        >>> detect_completed_blocks("text\\n\\n```python\\ncode\\n```", False)
        (['text'], '```python\\ncode\\n```', False)

        >>> detect_completed_blocks("```python\\ncode\\n```\\n\\nregular", False)
        (['```python\\ncode\\n```'], 'regular', False)

        >>> detect_completed_blocks("text\\n\\n\\n", False)
        (['text'], '', False)

        >>> detect_completed_blocks("   \\n\\ntext", False)
        ([], '   \\n\\ntext', False)

        >>> detect_completed_blocks("text\\n\\n   \\n\\n", False)
        (['text'], '', False)

        >>> detect_completed_blocks("text\\n\\nmore text\\n\\n```python\\n", False)
        (['text', 'more text'], '```python\\n', True)

        >>> detect_completed_blocks("```\\ncode\\n```\\n\\n```\\nmore\\n```\\n\\nend", False)
        (['```\\ncode\\n```', '```\\nmore\\n```'], 'end', False)

        >>> detect_completed_blocks("a\\n\\nb\\n\\nc\\n\\n", False)
        (['a', 'b', 'c'], '', False)

        >>> detect_completed_blocks("a\\n\\nb\\n\\nc", False)
        (['a', 'b'], 'c', False)

        >>> detect_completed_blocks("text\\n\\n", False)
        (['text'], '', False)

        >>> detect_completed_blocks("text\\n\\n", True)
        ([], 'text\\n\\n', True)

        >>> detect_completed_blocks("Hello\\n\\n```python\\nprint('hi')\\n```", False)
        (['Hello'], "```python\\nprint('hi')\\n```", False)

        >>> detect_completed_blocks("```\\ncode\\n```\\n\\n", True)
        ([], '```\\ncode\\n```\\n\\n', True)
    """
    blocks: list[str] = []
    current_block: list[str] = []
    is_in_fence = in_fence
    i = 0
    n = len(buffer)
    found_boundary = False
    # Track if we've emitted ANY valid block so far
    emitted_valid_block = False

    while i < n:
        # Check if this line starts a fence (``` at position after newline or at start)
        if i == 0:
            line_start = 0
        else:
            # Find the start of the current line
            line_start = i
            while line_start > 0 and buffer[line_start - 1] != "\n":
                line_start -= 1

        # Get the full line
        line_end = i
        while line_end < n and buffer[line_end] != "\n":
            line_end += 1
        line = buffer[line_start:line_end]

        # Check for fence toggle
        if line.startswith("```"):
            is_in_fence = not is_in_fence

        current_block.append(buffer[i : line_end + 1])
        i = line_end + 1

        # Check for double newline boundary outside fence
        # Only consider boundary if we have content in current_block
        found_boundary = (
            current_block
            and i < n
            and buffer[i - 1] == "\n"
            and buffer[i] == "\n"
            and not is_in_fence
        )
        if found_boundary:
            # Join current block and check if it's not whitespace-only
            block_text = "".join(current_block)
            # Remove trailing single newline before checking (trailing single newline not boundary)
            block_text_stripped = block_text.rstrip("\n")
            # Discard whitespace-only blocks - if the block is only whitespace, don't split
            if block_text_stripped.strip():
                # Valid block - emit it
                blocks.append(block_text_stripped)
                current_block = []
                emitted_valid_block = True
                # Skip the second newline
                i += 1
            else:
                # Whitespace-only block - keep building. Don't skip the extra newline.
                # Let the loop continue - the extra newline will be processed as an empty line
                pass

    # Handle remaining content in current_block
    # If we emitted a valid block AND there's trailing whitespace content, discard it
    # (it was part of boundary markers)
    remaining = "".join(current_block)
    if emitted_valid_block and not is_in_fence:
        # FIX: Don't use .strip() which removes leading whitespace!
        # Instead, only remove trailing newlines and leading empty lines
        # that are just separators - preserve actual indentation
        lines = remaining.split("\n")
        # Remove leading empty or whitespace-only lines
        start_idx = 0
        for j, line in enumerate(lines):
            if line.strip() == "":  # empty or whitespace-only
                start_idx = j + 1
            else:
                break  # first non-empty line
        # Rejoin from first non-empty line, preserving its indentation
        remaining = "\n".join(lines[start_idx:])

    return (blocks, remaining, is_in_fence)


class StreamMarkdownRenderer:
    """Render streaming markdown with block-aware terminal updates.

    Completed markdown blocks are printed permanently as they are detected.
    The trailing incomplete block is buffered and rendered once in finish().
    No Rich Live region is used — this avoids transient cleanup issues with
    wide characters (CJK) where transient=True fails to erase the correct
    number of terminal lines on line-wrap boundaries.
    """

    _NOT_STARTED: Final[str] = "NOT_STARTED"
    _STREAMING: Final[str] = "STREAMING"
    _FINISHED: Final[str] = "FINISHED"

    def __init__(self, console: Console) -> None:
        self._console = console
        self._state = self._NOT_STARTED
        self._buffer = ""
        self._in_fence = False

    def start(self) -> None:
        """Start live rendering for the streaming session."""
        if self._state != self._NOT_STARTED:
            raise RuntimeError(f"Invalid state transition: {self._state} -> STREAMING")

        self._state = self._STREAMING

    def feed(self, chunk: str) -> None:
        """Feed a markdown chunk and update rendering state.

        Only completed blocks are rendered immediately. Incomplete trailing
        content accumulates in the buffer and is rendered once in finish().
        This avoids Rich Live transient cleanup issues with wide characters
        (CJK) where transient=True fails to erase the correct number of
        terminal lines on line-wrap boundaries.
        """
        if self._state != self._STREAMING:
            raise RuntimeError(f"Invalid state for feed: {self._state}")

        if not chunk:
            return

        self._buffer += chunk
        blocks, remaining, in_fence = detect_completed_blocks(
            self._buffer, self._in_fence
        )
        self._buffer = remaining
        self._in_fence = in_fence

        if blocks:
            for block in blocks:
                self._console.print(Markdown(block))

    def finish(self) -> None:
        """Flush final content and stop live rendering."""
        if self._state != self._STREAMING:
            raise RuntimeError(f"Invalid state for finish: {self._state}")

        if self._buffer.strip():
            self._console.print(Markdown(self._buffer.rstrip("\n")))

        self._state = self._FINISHED
