"""Event types for session compression.

Events replace LogEntry for session logging. Key difference: tool events
store only metadata (input, status, error), not raw output, reducing
token cost by ~89%.

Reference: RFC-002-R1 §4 Interface Specification
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal

import deal

from lattice.core.types.enums import EventType

if TYPE_CHECKING:
    from lattice.core.types.config import Layer1Config


@dataclass(frozen=True)
class Event:
    """A single event in a session.

    Events are lightweight log entries designed for efficient session
    compilation. Tool events store only metadata, not raw output.

    type=user/assistant/reasoning:
        content contains the full message content.
        tool_* fields are ignored.

    type=tool:
        content is the tool name (e.g., "read", "edit", "bash").
        tool_input is a brief summary of tool input.
        tool_status is "success" or "error".
        tool_error is error message (max 500 chars) if status="error".

    >>> event = Event(type=EventType.USER, content="Hello")
    >>> event.type
    <EventType.USER: 'user'>
    >>> event.content
    'Hello'

    >>> tool_event = Event(
    ...     type=EventType.TOOL,
    ...     content="read",
    ...     tool_input="src/auth.py",
    ...     tool_status="success",
    ... )
    >>> tool_event.tool_input
    'src/auth.py'

    >>> error_event = Event(
    ...     type=EventType.TOOL,
    ...     content="edit",
    ...     tool_input="src/auth.py",
    ...     tool_status="error",
    ...     tool_error="SyntaxError: invalid syntax",
    ... )
    >>> error_event.tool_status
    'error'
    """

    type: EventType
    content: str
    tool_input: str = ""
    tool_status: str = ""
    tool_error: str = ""


@deal.post(lambda result: result in (True, False))
def is_high_signal(event: Event) -> bool:
    """Check if event is high-signal (should be preserved in full).

    High-signal events: user, assistant, reasoning.
    Low-signal events: tool (only metadata preserved).

    >>> is_high_signal(Event(type=EventType.USER, content="Hi"))
    True
    >>> is_high_signal(Event(type=EventType.TOOL, content="read"))
    False
    >>> is_high_signal(Event(type=EventType.REASONING, content="Analyzing..."))
    True
    """
    return event.type in (EventType.USER, EventType.ASSISTANT, EventType.REASONING)


@deal.post(lambda result: len(result) <= 100)  # Brief summary
def summarize_tool_input(tool_name: str, raw_input: dict | str | None) -> str:
    """Summarize tool input for storage.

    Extracts file paths, commands, or other key identifiers.
    Output should be brief (target: <100 chars).

    >>> summarize_tool_input("read", {"file_path": "src/auth.py"})
    'src/auth.py'
    >>> summarize_tool_input("bash", {"command": "npm test -- --coverage"})
    'npm test -- --coverage'
    >>> summarize_tool_input("unknown_tool", None)
    ''
    >>> summarize_tool_input("edit", {"file_path": "src/auth.py", "old_string": "def auth(): pass"})
    'src/auth.py'
    """
    if raw_input is None:
        return ""
    if isinstance(raw_input, str):
        return raw_input[:100]

    # Known tool input fields
    if tool_name == "read":
        return raw_input.get("file_path", "")[:100]
    if tool_name == "bash":
        return raw_input.get("command", "")[:100]
    if tool_name == "edit":
        return raw_input.get("file_path", "")[:100]
    if tool_name in ("glob", "grep"):
        return raw_input.get("pattern", "")[:100]
    if tool_name == "write":
        return raw_input.get("file_path", "")[:100]

    # Generic fallback: try common fields
    for key in ("file_path", "path", "command", "query", "pattern", "url"):
        if key in raw_input:
            return str(raw_input[key])[:100]

    return ""


@deal.pre(lambda error, max_len=500: isinstance(error, str) and max_len > 0)
@deal.post(lambda result: isinstance(result, str))
def truncate_error(error: str, max_len: int = 500) -> str:
    """Truncate error message to max_len characters.

    Empty string is valid (represents "no error message").

    >>> truncate_error("Short error")
    'Short error'
    >>> truncate_error("x" * 1000, max_len=500)
    'xxx...(truncated, 1000 chars total)'
    >>> truncate_error("")
    ''
    """
    if len(error) <= max_len:
        return error
    return f"{error[:max_len]}...(truncated, {len(error)} chars total)"


@deal.post(lambda result: isinstance(result, str))
def summarize_error_output(raw_output: str, max_len: int = 500) -> str:
    """Summarize tool error output for storage.

    For error outputs, keep first max_len chars.

    >>> summarize_error_output("ImportError: No module named 'jwt'")
    "ImportError: No module named 'jwt'"
    >>> summarize_error_output("x" * 1000, max_len=100)
    'xxx...(truncated, 1000 chars total)'
    """
    if len(raw_output) <= max_len:
        return raw_output
    return f"{raw_output[:max_len]}...(truncated, {len(raw_output)} chars total)"


@deal.post(lambda result: isinstance(result, str) and len(result) <= 50)
def summarize_success_output(raw_output: str | bytes) -> str:
    """Summarize tool success output for storage.

    For success outputs, only keep size metadata.

    >>> summarize_success_output("hello world")
    '[11 chars]'
    >>> summarize_success_output(b"hello world")
    '[11 bytes]'
    >>> summarize_success_output("x" * 10000)
    '[10000 chars, ~2500 tokens]'
    """
    if isinstance(raw_output, bytes):
        return f"[{len(raw_output)} bytes]"

    char_count = len(raw_output)
    token_estimate = char_count // 4

    if token_estimate >= 1000:
        return f"[{char_count} chars, ~{token_estimate} tokens]"
    return f"[{char_count} chars]"


@deal.post(lambda result: isinstance(result, int) and result >= 0)
def estimate_tokens(events: list[Event]) -> int:
    """Estimate token count for events.

    Uses simple heuristic: 1 token ≈ 4 characters.
    Pure function suitable for Core layer.

    Args:
        events: List of events (can be empty).

    Returns:
        Estimated token count (non-negative integer).

    >>> from lattice.core.types.event import Event, EventType
    >>> events = [Event(type=EventType.USER, content="Hello world")]
    >>> estimate_tokens(events)
    7
    >>> estimate_tokens([])
    0
    """
    total_chars = 0
    for e in events:
        total_chars += len(e.content)
        total_chars += len(e.tool_input)
        total_chars += len(e.tool_error)
        # Add overhead for metadata
        total_chars += 20  # type, status, etc.

    return total_chars // 4


@deal.post(lambda result: isinstance(result, bool))
def should_summarize(
    events: list[Event],
    config: Layer1Config,
) -> bool:
    """Check if events should be summarized.

    Pure function suitable for Core layer.
    Returns True if:
    - Layer1 is enabled
    - Token count exceeds min_tokens threshold

    Args:
        events: List of events.
        config: Layer1 configuration.

    Returns:
        True if summarization should be performed.

    >>> from lattice.core.types.config import Layer1Config
    >>> from lattice.core.types.event import Event, EventType
    >>> config = Layer1Config(enabled=True, min_tokens=10)
    >>> events = [Event(type=EventType.USER, content="x" * 100)]
    >>> should_summarize(events, config)
    True
    >>> should_summarize(events, Layer1Config(enabled=False))
    False
    """
    if not config.enabled:
        return False

    token_count = estimate_tokens(events)
    return token_count >= config.min_tokens
