"""Tool output truncation utilities."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

DEFAULT_MAX_LINES = 2000
DEFAULT_MAX_BYTES = 50 * 1024  # 50 KB
GREP_MAX_LINE_LENGTH = 500


@dataclass
class TruncationResult:
    """Result of truncation operation."""

    content: str
    truncated: bool
    truncated_by: str | None = None  # "lines" | "bytes"
    total_lines: int = 0
    total_bytes: int = 0
    output_lines: int = 0
    output_bytes: int = 0
    last_line_partial: bool = False
    first_line_exceeds_limit: bool = False
    max_lines: int = DEFAULT_MAX_LINES
    max_bytes: int = DEFAULT_MAX_BYTES


def truncate_head(
    content: str,
    max_lines: int = DEFAULT_MAX_LINES,
    max_bytes: int = DEFAULT_MAX_BYTES,
) -> TruncationResult:
    """Keep first N lines/bytes. Used for file reads."""
    total_bytes = len(content.encode("utf-8"))
    lines = content.split("\n")
    total_lines = len(lines)

    # Check if first line exceeds byte limit
    if lines and len(lines[0].encode("utf-8")) > max_bytes:
        return TruncationResult(
            content="",
            truncated=True,
            truncated_by="bytes",
            total_lines=total_lines,
            total_bytes=total_bytes,
            output_lines=0,
            output_bytes=0,
            first_line_exceeds_limit=True,
            max_lines=max_lines,
            max_bytes=max_bytes,
        )

    # Truncate by lines
    if total_lines > max_lines:
        kept = lines[:max_lines]
        result = "\n".join(kept)
        result_bytes = len(result.encode("utf-8"))
        return TruncationResult(
            content=result,
            truncated=True,
            truncated_by="lines",
            total_lines=total_lines,
            total_bytes=total_bytes,
            output_lines=max_lines,
            output_bytes=result_bytes,
            max_lines=max_lines,
            max_bytes=max_bytes,
        )

    # Truncate by bytes
    if total_bytes > max_bytes:
        encoded = content.encode("utf-8")[:max_bytes]
        # Decode back, dropping incomplete chars
        result = encoded.decode("utf-8", errors="ignore")
        # Don't return partial lines — drop the last incomplete line
        last_newline = result.rfind("\n")
        if last_newline >= 0:
            result = result[:last_newline]
        result_lines = result.count("\n") + 1 if result else 0
        return TruncationResult(
            content=result,
            truncated=True,
            truncated_by="bytes",
            total_lines=total_lines,
            total_bytes=total_bytes,
            output_lines=result_lines,
            output_bytes=len(result.encode("utf-8")),
            max_lines=max_lines,
            max_bytes=max_bytes,
        )

    return TruncationResult(
        content=content,
        truncated=False,
        total_lines=total_lines,
        total_bytes=total_bytes,
        output_lines=total_lines,
        output_bytes=total_bytes,
        max_lines=max_lines,
        max_bytes=max_bytes,
    )


def truncate_tail(
    content: str,
    max_lines: int = DEFAULT_MAX_LINES,
    max_bytes: int = DEFAULT_MAX_BYTES,
) -> TruncationResult:
    """Keep last N lines/bytes. Used for bash output (want to see errors)."""
    total_bytes = len(content.encode("utf-8"))
    lines = content.split("\n")
    total_lines = len(lines)

    # Truncate by lines
    if total_lines > max_lines:
        kept = lines[-max_lines:]
        result = "\n".join(kept)
        result_bytes = len(result.encode("utf-8"))
        return TruncationResult(
            content=result,
            truncated=True,
            truncated_by="lines",
            total_lines=total_lines,
            total_bytes=total_bytes,
            output_lines=max_lines,
            output_bytes=result_bytes,
            max_lines=max_lines,
            max_bytes=max_bytes,
        )

    # Truncate by bytes
    if total_bytes > max_bytes:
        encoded = content.encode("utf-8")
        tail = encoded[-max_bytes:]
        result = tail.decode("utf-8", errors="ignore")
        result_lines = result.count("\n") + 1 if result else 0
        return TruncationResult(
            content=result,
            truncated=True,
            truncated_by="bytes",
            total_lines=total_lines,
            total_bytes=total_bytes,
            output_lines=result_lines,
            output_bytes=len(result.encode("utf-8")),
            last_line_partial=True,
            max_lines=max_lines,
            max_bytes=max_bytes,
        )

    return TruncationResult(
        content=content,
        truncated=False,
        total_lines=total_lines,
        total_bytes=total_bytes,
        output_lines=total_lines,
        output_bytes=total_bytes,
        max_lines=max_lines,
        max_bytes=max_bytes,
    )


def truncate_line(line: str, max_chars: int = GREP_MAX_LINE_LENGTH) -> tuple[str, bool]:
    """Truncate a single line, appending [truncated] if needed."""
    if len(line) <= max_chars:
        return line, False
    return line[:max_chars] + " [truncated]", True


def format_size(num_bytes: int) -> str:
    """Return human-readable size string."""
    if num_bytes < 1024:
        return f"{num_bytes}B"
    elif num_bytes < 1024 * 1024:
        return f"{num_bytes / 1024:.1f}KB"
    else:
        return f"{num_bytes / (1024 * 1024):.1f}MB"
