"""Enhanced diff widget for displaying unified diffs."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any

from textual.containers import Vertical
from textual.content import Content
from textual.widgets import Static

from deepagents_code import theme
from deepagents_code.config import get_glyphs, is_ascii_mode

if TYPE_CHECKING:
    from textual.app import ComposeResult

_HUNK_RE = re.compile(r"@@ -(\d+)(?:,\d+)? \+(\d+)")
"""Matches a unified-diff hunk header, capturing the old and new start lines."""


def compose_diff_lines(
    diff: str,
    max_lines: int | None = 100,
) -> ComposeResult:
    """Yield per-line Static widgets for a unified diff.

    Each added/removed line gets a CSS class (`.diff-line-added`,
    `.diff-line-removed`) so background colors are driven by CSS variables
    and update automatically on theme change.

    Args:
        diff: Unified diff string.
        max_lines: Maximum number of diff lines to show (None for unlimited).

    Yields:
        Static widgets — one per diff line — with appropriate CSS classes.
    """
    if not diff:
        yield Static(Content.styled("No changes detected", "dim"))
    else:
        yield from _compose_diff_content(diff, max_lines)


def _compose_diff_content(
    diff: str,
    max_lines: int | None,
) -> ComposeResult:
    """Yield styled diff line widgets for non-empty diff content.

    Args:
        diff: Non-empty unified diff string.
        max_lines: Maximum number of diff lines to show (None for unlimited).

    Yields:
        Static widgets for stats header and individual diff lines.
    """
    colors = theme.get_theme_colors()
    glyphs = get_glyphs()
    lines = diff.splitlines()

    # Single pass for stats and max line number (width calculation).
    additions = 0
    deletions = 0
    max_line = 0
    for ln in lines:
        if ln.startswith("+"):
            if not ln.startswith("+++"):
                additions += 1
        elif ln.startswith("-"):
            if not ln.startswith("---"):
                deletions += 1
        elif m := _HUNK_RE.match(ln):
            max_line = max(max_line, int(m.group(1)), int(m.group(2)))

    # Stats header
    stats_parts: list[str | tuple[str, str] | Content] = []
    if additions:
        stats_parts.append((f"+{additions}", colors.success))
    if deletions:
        if stats_parts:
            stats_parts.append(" ")
        stats_parts.append((f"-{deletions}", colors.error))
    if stats_parts:
        yield Static(Content.assemble(*stats_parts))

    width = max(3, len(str(max_line + len(lines))))

    old_num = new_num = 0
    line_count = 0

    for line in lines:
        if max_lines and line_count >= max_lines:
            yield Static(
                Content.styled(f"\n... ({len(lines) - line_count} more lines)", "dim")
            )
            break

        # Skip file headers (--- and +++)
        if line.startswith(("---", "+++")):
            continue

        # Handle hunk headers - just update line numbers, don't display
        if m := _HUNK_RE.match(line):
            old_num, new_num = int(m.group(1)), int(m.group(2))
            continue

        # Handle diff lines - use gutter bar instead of +/- prefix
        content = line[1:] if line else ""

        if line.startswith("-"):
            # Deletion — red gutter bar, background via CSS
            yield Static(
                Content.assemble(
                    (f"{glyphs.gutter_bar}", f"{colors.error} bold"),
                    (f"{old_num:>{width}}", "dim"),
                    f" {content}",
                ),
                classes="diff-line-removed",
            )
            old_num += 1
            line_count += 1
        elif line.startswith("+"):
            # Addition — green gutter bar, background via CSS
            yield Static(
                Content.assemble(
                    (f"{glyphs.gutter_bar}", f"{colors.success} bold"),
                    (f"{new_num:>{width}}", "dim"),
                    f" {content}",
                ),
                classes="diff-line-added",
            )
            new_num += 1
            line_count += 1
        elif line.startswith(" "):
            # Context line — dim gutter
            yield Static(
                Content.assemble(
                    (f"{glyphs.box_vertical}{old_num:>{width}}", "dim"),
                    f"  {content}",
                ),
            )
            old_num += 1
            new_num += 1
            line_count += 1
        elif line.strip() == "...":
            # Truncation marker
            yield Static(Content.styled("...", "dim"))
            line_count += 1
        else:
            # Unrecognized diff line (e.g., "\ No newline at end of file")
            yield Static(Content.styled(line, "dim"))
            line_count += 1


class EnhancedDiff(Vertical):
    """Widget for displaying a unified diff with syntax highlighting."""

    DEFAULT_CSS = """
    EnhancedDiff {
        height: auto;
        padding: 1;
        background: $surface-darken-1;
        border: round $primary;
    }

    EnhancedDiff .diff-title {
        color: $primary;
        text-style: bold;
        margin-bottom: 1;
    }

    EnhancedDiff .diff-content {
        height: auto;
    }

    EnhancedDiff .diff-stats {
        color: $text-muted;
        margin-top: 1;
    }
    """

    def __init__(
        self,
        diff: str,
        title: str = "Diff",
        max_lines: int | None = 100,
        **kwargs: Any,
    ) -> None:
        """Initialize the diff widget.

        Args:
            diff: Unified diff string
            title: Title to display above the diff
            max_lines: Maximum number of diff lines to show
            **kwargs: Additional arguments passed to parent
        """
        super().__init__(**kwargs)
        self._diff = diff
        self._title = title
        self._max_lines = max_lines
        self._stats = self._compute_stats()

    def _compute_stats(self) -> tuple[int, int]:
        """Compute additions and deletions count.

        Returns:
            Tuple of (additions count, deletions count).
        """
        additions = 0
        deletions = 0
        for line in self._diff.splitlines():
            if line.startswith("+") and not line.startswith("+++"):
                additions += 1
            elif line.startswith("-") and not line.startswith("---"):
                deletions += 1
        return additions, deletions

    def on_mount(self) -> None:
        """Set border style based on charset mode."""
        if is_ascii_mode():
            colors = theme.get_theme_colors(self)
            self.styles.border = ("ascii", colors.primary)

    def compose(self) -> ComposeResult:
        """Compose the diff widget layout.

        Yields:
            Widgets for title, formatted diff content, and stats.
        """
        colors = theme.get_theme_colors(self)
        glyphs = get_glyphs()
        h = glyphs.box_double_horizontal
        yield Static(
            Content.styled(
                f"{h}{h}{h} {self._title} {h}{h}{h}", f"bold {colors.primary}"
            ),
            classes="diff-title",
        )

        yield from compose_diff_lines(self._diff, self._max_lines)

        additions, deletions = self._stats
        if additions or deletions:
            content_parts: list[str | tuple[str, str]] = []
            if additions:
                content_parts.append((f"+{additions}", colors.success))
            if deletions:
                if content_parts:
                    content_parts.append(" ")
                content_parts.append((f"-{deletions}", colors.error))
            yield Static(Content.assemble(*content_parts), classes="diff-stats")
