<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Data_Object;
use Limb_Chatbot\Includes\Data_Objects\Message;

/**
 * Class Stream_Event_Service
 *
 * Centralized service for emitting typed streaming events
 * over a Server-Sent Events (SSE) connection.
 *
 * @since 1.0.0
 */
final class Stream_Event_Service {

	/**
	 * Supported stream event types.
	 *
	 * @var string[]
	 */
	public const EVENT_TEXT = 'text';
	public const EVENT_PARAMETER = 'parameter';
	public const EVENT_ACTION_SUBMISSION = 'action_submission';
	public const EVENT_ACTION_CANCELLATION = 'action_cancellation';
	public const EVENT_LIVE_AGENT_DISCONNECTION = 'live_agent_disconnection';
	public const EVENT_PARAMETER_VALUE = 'parameter_value';
	public const EVENT_LIVE_AGENT_CONNECTION = 'live_agent_connection';
	public const EVENT_ERROR = 'error';
	public const EVENT_FATAL_ERROR = 'fatal_error';
	public const EVENT_DONE = 'done';
	public const EVENT_USER_MESSAGE_SAVED = 'user_message_saved';
	const EVENT_CHAT_CREATED = 'chat_created';
	const EVENT_CHATBOT_MESSAGE_SAVED = 'chatbot_message_saved';
	const EVENT_CHAT_DELETED = 'chat_deleted';
	/**
	 * Indicates whether headers were already sent.
	 *
	 * @var bool
	 */
	private static bool $initialized = false;

	/**
	 * Emits a typed SSE event.
	 *
	 * @param  string  $event
	 * @param  array|string|null  $data
	 *
	 * @return void
	 */
	public static function emit( string $event, $data = null ): void {
		self::init();

		echo "event: {$event}\n";

		if ( $data !== null ) {
			$payload = wp_json_encode( self::clean_data( $data ), JSON_UNESCAPED_UNICODE );

			echo "data: {$payload}\n";
		}

		echo "\n";

		@ob_flush();
		@flush();
	}

	/**
	 * Initializes streaming headers and buffering.
	 *
	 * Safe to call multiple times.
	 *
	 * @return void
	 */
	public static function init(): void {
		if ( self::$initialized ) {
			return;
		}

		nocache_headers();

		header( 'Content-Type: text/event-stream; charset=utf-8' );
		header( 'Cache-Control: no-cache, no-transform' );
		header( 'Connection: keep-alive' );
		header( 'X-Accel-Buffering: no' );

		while ( ob_get_level() > 0 ) {
			ob_end_flush();
		}

		ob_implicit_flush( true );

		self::$initialized = true;
	}

	public static function clean_data( $data ) {
		if ( $data instanceof Data_Object ) {
			return $data->toArray();
		}
		if ( is_array( $data ) ) {
			return array_map( array( self::class, 'clean_data' ), $data );
		}
		if ( is_string( $data ) ) {
			return str_replace( "\0", '', $data );
		}

		return $data;
	}

	public static function message( Message $message ) {
		if ( empty( $message->get_content() ) || ! is_array( $message->get_content() ) ) {
			exit;
		}
		foreach ( $message->get_content() as $part ) {
			$type = $part['type'] ?? '';
			if ( ! $type ) {
				continue;
			}
			unset( $part['type'] );
			self::part_factory( $type, $part );
		}
		self::chatbot_message_saved( $message );
		exit;
	}

	private static function part_factory( $type, $content ) {
		if ( $type === Message::CONTENT_TYPE_PARAMETER ) {
			self::parameter( $content );
		} elseif ( $type === Message::CONTENT_TYPE_ACTION_CANCELLATION ) {
			self::action_cancellation( $content );
		} elseif ( $type === Message::CONTENT_TYPE_PARAMETER_VALUE ) {
			self::parameter_value( $content );
		} elseif ( $type === Message::CONTENT_TYPE_ACTION_SUBMISSION ) {
			self::action_submission( $content );
		}
	}

	private static function normalize_data( $data ) {
		// Handle Data_Object
		if ( $data instanceof Data_Object ) {
			$data = $data->toArray();
		}

		// If it's an object, convert to array recursively
		if ( is_object( $data ) ) {
			$data = json_decode( wp_json_encode( $data, JSON_UNESCAPED_UNICODE ), true );
		}

		// If it's an array, normalize each element
		if ( is_array( $data ) ) {
			foreach ( $data as $key => $value ) {
				$data[ $key ] = self::normalize_data( $value );
			}
		}

		// Clean strings from null bytes
		if ( is_string( $data ) ) {
			$data = str_replace( "\0", '', $data );
		}

		return $data;
	}

	public static function fatal( string $message ): void {
		self::emit( self::EVENT_FATAL_ERROR, [ 'message' => $message ] );
		self::done();
		exit;
	}

	public static function parameter( $content, array $meta = [] ) {
		self::emit( self::EVENT_PARAMETER, array_merge( self::normalize_data( $content ), $meta ) );
	}

	public static function done(): void {
		self::emit( 'done', '[DONE]' );
	}

	public static function error( string $message, array $context = [] ): void {
		$payload = array_merge(
			[ 'message' => Helper::linkify_urls_in_text( $message ) ],
			Helper::linkify_urls_in_value( $context )
		);
		self::emit( self::EVENT_ERROR, $payload );
	}

	public static function text( string $content, array $meta = [] ): void {
		self::emit( self::EVENT_TEXT, array_merge( [ 'content' => $content ], $meta ) );
	}

	private static function action_cancellation( $content, array $meta = [] ) {
		self::emit( self::EVENT_ACTION_CANCELLATION, array_merge( self::normalize_data( $content ), $meta ) );
	}

	private static function parameter_value( $content, array $meta = [] ) {
		self::emit( self::EVENT_PARAMETER_VALUE, array_merge( self::normalize_data( $content ), $meta ) );
	}

	private static function action_submission( $content, array $meta = [] ) {
		self::emit( self::EVENT_ACTION_SUBMISSION, array_merge( self::normalize_data( $content ), $meta ) );
	}

	public static function user_message_saved( ?Message $message ) {
		self::emit( self::EVENT_USER_MESSAGE_SAVED, $message );
	}

	public static function chat_created( ?Chat $chat ) {
		self::emit( self::EVENT_CHAT_CREATED, $chat );
	}

	private static function chatbot_message_saved( Message $message ) {
		self::emit( self::EVENT_CHATBOT_MESSAGE_SAVED, $message );
	}

	public static function chat_deleted( Chat $chat ) {
		self::emit( self::EVENT_CHAT_DELETED, $chat );
	}
}
