<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Lightweight synchronous event emitter for the agent loop.
 *
 * The loop uses this for lifecycle, streaming, tool, compaction, and error
 * events without coupling core execution to a concrete observer.
 */
class Niwa_AI_Agent_Event_Emitter {
	/** Fired once when the agent loop begins. */
	const EVENT_AGENT_START = 'agent_start';

	/** Fired once when the agent loop finishes. */
	const EVENT_AGENT_END = 'agent_end';

	/** Fired at the beginning of every conversation turn. */
	const EVENT_TURN_START = 'turn_start';

	/** Fired at the end of every conversation turn. */
	const EVENT_TURN_END = 'turn_end';

	/** Fired when a new assistant message begins streaming. */
	const EVENT_MESSAGE_START = 'message_start';

	/** Fired for incremental content deltas during streaming. */
	const EVENT_MESSAGE_UPDATE = 'message_update';

	/** Fired when the assistant message is fully received. */
	const EVENT_MESSAGE_END = 'message_end';

	/** Fired before a tool call is executed. */
	const EVENT_TOOL_EXECUTION_START = 'tool_execution_start';

	/** Fired for incremental tool-execution progress. */
	const EVENT_TOOL_EXECUTION_UPDATE = 'tool_execution_update';

	/** Fired after a tool call finishes executing. */
	const EVENT_TOOL_EXECUTION_END = 'tool_execution_end';

	/** Fired when a non-fatal or fatal error occurs inside the loop. */
	const EVENT_ERROR = 'error';

	/** Fired when automatic context compaction starts. */
	const EVENT_COMPACTION_START = 'auto_compaction_start';

	/** Fired when automatic context compaction finishes. */
	const EVENT_COMPACTION_END = 'auto_compaction_end';

	/** Internal key used for wildcard listeners. */
	const WILDCARD = '*';

	/** @var array<string,callable[]> */
	private $listeners = array();

	public static function create() {
		return new self();
	}

	public function on( $event_type, $callback ) {
		$event_type = (string) $event_type;

		if ( ! isset( $this->listeners[ $event_type ] ) ) {
			$this->listeners[ $event_type ] = array();
		}

		$this->listeners[ $event_type ][] = $callback;
	}

	public function off( $event_type, $callback ) {
		$event_type = (string) $event_type;

		if ( empty( $this->listeners[ $event_type ] ) ) {
			return false;
		}

		foreach ( $this->listeners[ $event_type ] as $index => $registered ) {
			if ( $registered === $callback ) {
				array_splice( $this->listeners[ $event_type ], $index, 1 );
				return true;
			}
		}

		return false;
	}

	public function on_all( $callback ) {
		$this->on( self::WILDCARD, $callback );
	}

	public function emit( $event_type, $payload = array() ) {
		$event_type = (string) $event_type;
		$payload    = is_array( $payload ) ? $payload : array();

		$this->emit_to_listeners( isset( $this->listeners[ $event_type ] ) ? $this->listeners[ $event_type ] : array(), $event_type, $payload );

		if ( self::WILDCARD !== $event_type ) {
			$this->emit_to_listeners( isset( $this->listeners[ self::WILDCARD ] ) ? $this->listeners[ self::WILDCARD ] : array(), $event_type, $payload );
		}
	}

	private function emit_to_listeners( $listeners, $event_type, $payload ) {
		foreach ( (array) $listeners as $callback ) {
			if ( is_callable( $callback ) ) {
				call_user_func( $callback, $event_type, $payload );
			}
		}
	}

	public function has_listeners( $event_type ) {
		$event_type = (string) $event_type;

		return ! empty( $this->listeners[ $event_type ] );
	}

	public function reset() {
		$this->listeners = array();
	}
}
