import json
import sys
from typing import Any, Dict

# The string used to identify system events in the stdout stream.
EVENT_PREFIX = "__CHARM_EVENT__"


class CharmEmitter:
    """Static utility to emit structured events to stdout."""

    @staticmethod
    def _write(event_type: str, payload: Dict[str, Any]):
        """Wraps data in JSON and prints it with the event prefix."""
        data = {"type": event_type, **payload}
        json_str = json.dumps(data, ensure_ascii=False)
        if sys.__stdout__:
            sys.__stdout__.write(f"{EVENT_PREFIX}{json_str}\n")
            sys.__stdout__.flush()

    @staticmethod
    def emit_status(message: str):
        """Report current system status."""
        CharmEmitter._write("status", {"content": message})

    @staticmethod
    def emit_thinking(content: str):
        """Emit internal reasoning logs or debug prints."""
        CharmEmitter._write("thinking", {"content": content})

    @staticmethod
    def emit_delta(content: str):
        """Emit a streaming token (for LLM responses)."""
        CharmEmitter._write("delta", {"content": content})

    @staticmethod
    def emit_final(content: str, format: str = "markdown"):
        """Emit the final result of the execution."""
        CharmEmitter._write("final", {"content": content, "format": format})

    @staticmethod
    def emit_error(message: str):
        """Emit a structured error message."""
        CharmEmitter._write("error", {"content": message})

    @staticmethod
    def emit_artifact(name: str, url: str, mime: str):
        """Emit a file artifact generated by the agent."""
        CharmEmitter._write("artifact", {"content": {"name": name, "url": url, "mime": mime}})


class StdoutInterceptor:
    """Hijacks sys.stdout to prevent raw prints from breaking the event protocol."""

    def __init__(self):
        # Keep a reference to the real stdout.
        self.terminal = sys.__stdout__
        self.buffer = ""

    def write(self, message):
        if not message:
            return

        # Pass through internal events directly.
        if message.startswith(EVENT_PREFIX):
            if self.terminal:
                self.terminal.write(message)
                self.terminal.flush()
            return

        # Buffer raw prints and wrap them as 'thinking' events.
        self.buffer += message
        if "\n" in self.buffer:
            lines = self.buffer.split("\n")
            # Process complete lines
            for line in lines[:-1]:
                if line.strip():
                    payload = {"type": "thinking", "content": line + "\n"}
                    json_str = json.dumps(payload, ensure_ascii=False)
                    if self.terminal:
                        self.terminal.write(f"{EVENT_PREFIX}{json_str}\n")

            if self.terminal:
                self.terminal.flush()
            # Keep incomplete line in buffer
            self.buffer = lines[-1]

    def flush(self):
        """Force flush remaining buffer."""
        if self.buffer.strip():
            payload = {"type": "thinking", "content": self.buffer + "\n"}
            json_str = json.dumps(payload, ensure_ascii=False)
            if self.terminal:
                self.terminal.write(f"{EVENT_PREFIX}{json_str}\n")
            self.buffer = ""
        if self.terminal:
            self.terminal.flush()
