"""Der einzige Codex-Ergebnisparser (JSONL von `codex exec --json`).

Streng: turn.started und turn.completed sind Pflicht, turn.failed/error
führen zu MODEL_FAILED, jedes Item außer agent_message/reasoning zählt als
Tool-Ereignis (TOOL_EVENT_SEEN). Größen- und UTF-8-Prüfung vor dem Parsen.
Nur mit allow_tools=True (Käfig-Profil tools, E2b) sind Tool-Ereignisse
erlaubt; sie werden dann gezählt statt abgelehnt.

Hinweis: Die Tool-Erkennung ist eine nachträgliche Prüfung, KEINE
Verhinderung. Die Prozess-Isolation muss das Betriebssystem leisten
(runner.py + Wrapper-Präfix in der Konfiguration).
"""
from __future__ import annotations

import json
from dataclasses import dataclass

MAX_OUTPUT_BYTES = 2 * 1024 * 1024


@dataclass(frozen=True)
class ProcessOutcome:
    success: bool
    output: str = ""
    error_code: str | None = None
    exit_code: int | None = None
    tool_seen: bool = False
    tool_events: int = 0
    last_message: str = ""          # Text der LETZTEN agent_message (fuer den last.txt-Quercheck, #14)


def decode_events(raw: bytes, *, allow_tools: bool = False) -> ProcessOutcome:
    if len(raw) > MAX_OUTPUT_BYTES:
        return ProcessOutcome(False, error_code="OUTPUT_LIMIT")
    try:
        text = raw.decode("utf-8", errors="strict")
    except UnicodeDecodeError:
        return ProcessOutcome(False, error_code="INVALID_UTF8")
    messages = []
    completed = False
    tool_seen = False
    tool_events = 0
    started = False
    for line in text.splitlines():
        if not line.strip():
            continue
        try:
            event = json.loads(line)
        except (ValueError, TypeError):
            return ProcessOutcome(False, error_code="INVALID_JSONL")
        if not isinstance(event, dict) or not isinstance(event.get("type"), str):
            return ProcessOutcome(False, error_code="INVALID_EVENT")
        kind = event["type"]
        if kind == "turn.started":
            started = True
        if kind in {"turn.failed", "error"}:
            return ProcessOutcome(False, error_code="MODEL_FAILED")
        if kind == "item.started" or kind == "item.updated" or kind == "item.completed":
            item = event.get("item", {})
            if not isinstance(item, dict):
                return ProcessOutcome(False, error_code="INVALID_EVENT")
            item_type = item.get("type")
            if item_type not in {"agent_message", "reasoning"}:
                tool_seen = True
                if kind == "item.completed":
                    tool_events += 1
            if kind == "item.completed" and item_type == "agent_message":
                message = item.get("text")
                if not isinstance(message, str):
                    return ProcessOutcome(False, error_code="INVALID_EVENT")
                messages.append(message)
        if kind == "turn.completed":
            completed = True
    if tool_seen and not allow_tools:
        return ProcessOutcome(False, error_code="TOOL_EVENT_SEEN", tool_seen=True)
    if not started or not completed:
        return ProcessOutcome(False, error_code="INCOMPLETE_TURN")
    if not messages or not "\n".join(messages).strip():
        return ProcessOutcome(False, error_code="EMPTY_RESPONSE")
    return ProcessOutcome(True, output="\n".join(messages), tool_seen=tool_seen, tool_events=tool_events,
                          last_message=messages[-1])
