"""
Tool Call (TC) Compression - compresses tool results before they enter context.

When an agent makes a tool call, the result can be arbitrarily large. These
compressors marshal results into a more digestible format for the agent.

Available compressors:
- NoOpTCCompressor: Pass-through, no compression (default for now)
- NaiveSizeTCCompressor: Truncate to last N characters
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any


class TCCompressor(ABC):
    """
    Abstract base class for tool call result compression.

    Subclasses implement different compression strategies to prevent
    tool results from exploding the agent's context window.
    """

    name: str = "base"

    @abstractmethod
    def compress(self, tool_name: str, result: Any) -> Any:
        """
        Compress a tool call result.

        Args:
            tool_name: Name of the tool that was called
            result: The raw result from the tool

        Returns:
            Compressed result (same type or string)
        """
        pass

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}()"


class NoOpTCCompressor(TCCompressor):
    """
    No-op compressor - passes results through unchanged.

    Use this when tool results are already well-bounded or when you want
    to disable compression entirely.
    """

    name = "noop"

    def compress(self, tool_name: str, result: Any) -> Any:
        """Pass through unchanged."""
        return result


class NaiveSizeTCCompressor(TCCompressor):
    """
    Naive size-based compressor - truncates results to last N characters.

    Simple but effective: keeps the most recent output which is usually
    the most relevant (e.g., last N chars of a log file).

    Args:
        max_chars: Maximum characters to keep (default: 25000)
        truncation_marker: String to prepend when truncated
    """

    name = "naive_size"

    def __init__(
        self,
        max_chars: int = 25000,
        truncation_marker: str = "... [truncated, showing last {n} chars] ...\n",
    ):
        self.max_chars = max_chars
        self.truncation_marker = truncation_marker

    def compress(self, tool_name: str, result: Any) -> Any:
        """Truncate to last max_chars characters if needed."""
        # Handle dict results (common for our tools)
        if isinstance(result, dict):
            return self._compress_dict(result)

        # Handle string results
        if isinstance(result, str):
            return self._truncate_string(result)

        # Handle list results
        if isinstance(result, list):
            return self._compress_list(result)

        # For other types, convert to string and truncate
        result_str = str(result)
        return self._truncate_string(result_str)

    def _truncate_string(self, s: str) -> str:
        """Truncate string to last max_chars."""
        if len(s) <= self.max_chars:
            return s

        # Keep last N chars with marker
        marker = self.truncation_marker.format(n=self.max_chars)
        keep_chars = self.max_chars - len(marker)
        return marker + s[-keep_chars:]

    def _compress_dict(self, d: dict) -> dict:
        """Recursively compress dict values."""
        compressed = {}
        for key, value in d.items():
            if isinstance(value, str):
                compressed[key] = self._truncate_string(value)
            elif isinstance(value, dict):
                compressed[key] = self._compress_dict(value)
            elif isinstance(value, list):
                compressed[key] = self._compress_list(value)
            else:
                compressed[key] = value
        return compressed

    def _compress_list(self, lst: list) -> list:
        """Compress list items."""
        return [
            self._truncate_string(item) if isinstance(item, str)
            else self._compress_dict(item) if isinstance(item, dict)
            else item
            for item in lst
        ]

    def __repr__(self) -> str:
        return f"NaiveSizeTCCompressor(max_chars={self.max_chars})"


# =============================================================================
# Factory
# =============================================================================


def get_tc_compressor(
    name: str = "noop",
    **kwargs,
) -> TCCompressor:
    """
    Get a TC compressor by name.

    Args:
        name: Compressor name ("noop", "naive_size")
        **kwargs: Passed to compressor constructor

    Returns:
        Configured TCCompressor instance
    """
    compressors = {
        "noop": NoOpTCCompressor,
        "naive_size": NaiveSizeTCCompressor,
    }

    if name not in compressors:
        raise ValueError(f"Unknown TC compressor: {name}. Available: {list(compressors.keys())}")

    return compressors[name](**kwargs)
