"""Build a wrapper's final stdout status line.

The orchestrator parses stdout backwards and takes the first JSON object as the
agent's reply, and the review seat rejects any field outside
ok/clean/findings/thread_id. So when the agent emitted a structured reply it has
to BE the status line, and the continuity id has to ride as thread_id.

env: RAW_OUTPUT_FILE, OK ("1"/"0"), DETAIL, THREAD_ID, PROTOCOL ("codex" or
the default streamed-text chain used by cursor and grok)
"""
import json
import os
import re

REPLY_MARKERS = ("clean", "findings", "committed", "steer", "kill", "noop")

CODEX_MESSAGE_ITEM_TYPES = ("agent_message", "assistant_message", "message")


def is_reply(obj):
    return isinstance(obj, dict) and any(field in obj for field in REPLY_MARKERS)


def json_spans(text):
    """Offsets of balanced top-level {...} runs, ignoring braces inside strings."""
    spans = []
    depth = 0
    start = -1
    in_string = False
    escaped = False
    for index, char in enumerate(text):
        if in_string:
            if escaped:
                escaped = False
            elif char == "\\":
                escaped = True
            elif char == '"':
                in_string = False
            continue
        if char == '"':
            in_string = True
        elif char == "{":
            if depth == 0:
                start = index
            depth += 1
        elif char == "}":
            if depth == 0:
                continue
            depth -= 1
            if depth == 0:
                spans.append((start, index + 1))
    return spans


def extract_json(text):
    text = text or ""
    for start, end in reversed(json_spans(text)):
        try:
            obj = json.loads(text[start:end])
        except Exception:
            continue
        if is_reply(obj):
            return obj
    return None


def terminal_verdict(text):
    """The verdict must be the message's last line, so trailing prose cannot hide it."""
    if not isinstance(text, str):
        return None
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    if not lines:
        return None
    try:
        candidate = json.loads(lines[-1])
    except (TypeError, ValueError):
        return None
    return candidate if is_reply(candidate) else None


def plain_terminal_verdict(raw):
    """codex's non-json transcript repeats the final reply after a token count."""
    lines = [line.strip() for line in raw.splitlines() if line.strip()]
    if len(lines) < 5:
        return None
    for index in range(len(lines) - 5, -1, -1):
        if lines[index] != "codex":
            continue
        span = len(lines) - index
        if (span - 3) % 2 != 0:
            continue
        response_length = (span - 3) // 2
        if response_length < 1:
            continue
        tokens_index = index + 1 + response_length
        response = lines[index + 1:tokens_index]
        candidate = terminal_verdict("\n".join(response))
        if (
            candidate is not None
            and lines[tokens_index] == "tokens used"
            and re.fullmatch(r"[0-9][0-9,]*", lines[tokens_index + 1])
            and lines[tokens_index + 2:] == response
        ):
            return candidate
    return None


def iter_events(raw):
    for line in raw.splitlines():
        line = line.strip()
        if not line.startswith("{"):
            continue
        try:
            event = json.loads(line)
        except Exception:
            continue
        if isinstance(event, dict):
            yield event


def item_texts(item):
    texts = []
    if isinstance(item.get("text"), str):
        texts.append(item["text"])
    content = item.get("content")
    if isinstance(content, str):
        texts.append(content)
    elif isinstance(content, list):
        texts.extend(
            part["text"] for part in content
            if isinstance(part, dict) and isinstance(part.get("text"), str)
        )
    return texts


def codex_verdict(raw):
    verdict = None
    for event in iter_events(raw):
        if event.get("type") != "item.completed":
            continue
        item = event.get("item")
        if not isinstance(item, dict):
            continue
        item_type = item.get("type")
        if item_type is not None and item_type not in CODEX_MESSAGE_ITEM_TYPES:
            continue
        # A later agent message without a verdict retracts an earlier one.
        verdict = terminal_verdict("\n".join(item_texts(item)))
    if verdict is None:
        verdict = plain_terminal_verdict(raw)
    return verdict


def streamed_verdict(raw):
    verdict = None
    for event in iter_events(raw):
        texts = []
        # cursor: a trailing result event repeats the whole reply in one field.
        result = event.get("result")
        if is_reply(result):
            verdict = result
        elif isinstance(result, str):
            texts.append(result)
        item = event.get("item")
        if isinstance(item, dict):
            texts.extend(item_texts(item))
        for text in texts:
            candidate = extract_json(text)
            if candidate is not None:
                verdict = candidate
    if verdict is not None:
        return verdict
    verdict = extract_json(raw)
    if verdict is not None:
        return verdict
    # Providers that stream the reply token by token leave it only as escaped
    # text inside the transport frames.
    for match in re.finditer(r'"(?:result|text)":"((?:\\.|[^"])*)"', raw):
        try:
            decoded = json.loads(f'"{match.group(1)}"')
        except Exception:
            continue
        candidate = extract_json(decoded)
        if candidate is not None:
            verdict = candidate
    return verdict


def mine_thread_id(raw):
    for event in iter_events(raw):
        event_type = event.get("type") or event.get("event")
        if event_type == "thread.started":
            value = event.get("thread_id")
        elif event_type == "session.started":
            value = event.get("session_id")
        else:
            continue
        if isinstance(value, str) and value:
            return value
    return ""


def main():
    try:
        with open(os.environ["RAW_OUTPUT_FILE"], "r", encoding="utf-8") as handle:
            raw = handle.read()
    except Exception:
        raw = ""
    ok = os.environ.get("OK", "") == "1"
    find = codex_verdict if os.environ.get("PROTOCOL", "") == "codex" else streamed_verdict
    verdict = find(raw) if ok else None
    if verdict is None:
        verdict = {"ok": ok, "detail": os.environ.get("DETAIL", "")}
    thread_id = os.environ.get("THREAD_ID", "") or mine_thread_id(raw)
    if thread_id:
        verdict["thread_id"] = thread_id
    print(json.dumps(verdict, separators=(",", ":")))


main()
