"""
Events - Decoupled event emission and queuing for cross-thread communication.

This module provides three complementary patterns for thread-safe communication
between the GUI main thread and background workers:

1. EventEmitter: Pub/sub pattern for synchronous event broadcast
   - Used for: API lifecycle events (request_start, response_received, tool_execution)
   - Thread-safe: Callbacks execute on emitter's thread
   - Example: ai_client.py emits 'request_start' and 'response_received' events

2. AsyncEventQueue: Producer-consumer pattern via queue.Queue
   - Used for: Decoupled task submission where consumer polls at its own pace
   - Thread-safe: Built on Python's thread-safe queue.Queue
   - Example: Background workers submit tasks, main thread drains queue

3. UserRequestEvent: Structured payload for AI request data
   - Used for: Bundling prompt, context, files, and base_dir into single object
   - Immutable data transfer object for cross-thread handoff

Integration Points:
 - ai_client.py: EventEmitter for API lifecycle events
 - gui_2.py: Consumes events via _process_event_queue()
 - multi_agent_conductor.py: Uses AsyncEventQueue for state updates
 - api_hooks.py: Pushes events to _api_event_queue for external visibility

Thread Safety:
 - EventEmitter: NOT thread-safe for concurrent on/emit (use from single thread)
 - AsyncEventQueue: FULLY thread-safe (built on queue.Queue)
 - UserRequestEvent: Immutable, safe for concurrent access
"""
import queue
from typing import Callable, Any, Dict, List, Tuple, Optional

class EventEmitter:
 """
 Simple event emitter for decoupled communication between modules.
 """

 def __init__(self) -> None:
  """Initializes the EventEmitter with an empty listener map."""
  self._listeners: Dict[str, List[Callable[..., Any]]] = {}

 def on(self, event_name: str, callback: Callable[..., Any]) -> None:
  """
  Registers a callback for a specific event.
  
  Args:
   event_name: The name of the event to listen for.
   callback: The function to call when the event is emitted.
  """
  if event_name not in self._listeners:
   self._listeners[event_name] = []
  self._listeners[event_name].append(callback)

 def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None:
  """
  Emits an event, calling all registered callbacks.
  
  Args:
   event_name: The name of the event to emit.
   *args: Positional arguments to pass to callbacks.
   **kwargs: Keyword arguments to pass to callbacks.
  """
  if event_name in self._listeners:
   for callback in self._listeners[event_name]:
    callback(*args, **kwargs)

 def clear(self) -> None:
  """Clears all registered listeners."""
  self._listeners.clear()

class AsyncEventQueue:
 """
 Synchronous event queue for decoupled communication using queue.Queue.
 (Named AsyncEventQueue for architectural consistency, but is synchronous)
 """

 def __init__(self) -> None:
  """Initializes the AsyncEventQueue with an internal queue.Queue."""
  self._queue: queue.Queue[Tuple[str, Any]] = queue.Queue()
  self.websocket_server: Optional[Any] = None

 def put(self, event_name: str, payload: Any = None) -> None:
  """
  Puts an event into the queue.
  
  Args:
   event_name: The name of the event.
   payload: Optional data associated with the event.
  """
  self._queue.put((event_name, payload))
  if self.websocket_server:
   # Ensure payload is JSON serializable for websocket broadcast
   serializable_payload = payload
   if hasattr(payload, 'to_dict'):
    serializable_payload = payload.to_dict()
   elif hasattr(payload, '__dict__'):
    serializable_payload = vars(payload)
   
   self.websocket_server.broadcast("events", {"event": event_name, "payload": serializable_payload})

 def get(self) -> Tuple[str, Any]:
  """
  Gets an event from the queue.
  
  Returns:
   A tuple containing (event_name, payload).
  """
  return self._queue.get()

 def empty(self) -> bool:
  """
  Checks if the queue is empty.
  
  Returns:
   True if the queue is empty, False otherwise.
  """
  return self._queue.empty()

 def task_done(self) -> None:
  """Signals that a formerly enqueued task is complete."""
  self._queue.task_done()

 def join(self) -> None:
  """Blocks until all items in the queue have been gotten and processed."""
  self._queue.join()

# Alias for backward compatibility
SyncEventQueue = AsyncEventQueue

class UserRequestEvent:
 """
 Payload for a user request event.
 """

 def __init__(self, prompt: str, stable_md: str, file_items: List[Any], disc_text: str, base_dir: str) -> None:
  self.prompt = prompt
  self.stable_md = stable_md
  self.file_items = file_items
  self.disc_text = disc_text
  self.base_dir = base_dir

 def to_dict(self) -> Dict[str, Any]:
  # Ensure all file items and base_dir are JSON serializable
  serializable_files = []
  for f in self.file_items:
   if hasattr(f, 'to_dict'):
    serializable_files.append(f.to_dict())
   elif isinstance(f, (str, dict, list, int, float, bool, type(None))):
    serializable_files.append(f)
   else:
    serializable_files.append(str(f))

  return {
   "prompt": self.prompt,
   "stable_md": self.stable_md,
   "file_items": serializable_files,
   "disc_text": self.disc_text,
   "base_dir": str(self.base_dir)
  }
