<?php

namespace Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content;

use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Stream_Event_Service;

/**
 * Class Stream_Parser
 *
 * Parses streamed response chunks from Gemini API into complete JSON objects.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content
 * @since 1.0.0
 */
class Stream_Parser {

	/**
	 * Array of fully parsed JSON response objects.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	public array $complete_jsons = [];

	/**
	 * Parsed error object from stream (if any).
	 *
	 * @var array|null
	 * @since 1.0.0
	 */
	public ?array $error = null;

	/**
	 * The streaming buffer
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected string $buffer = '';

	/**
	 * Callback parser for streamed chunks.
	 *
	 * Processes chunks of streamed response, parses JSON content,
	 * detects errors, and collects valid message chunks.
	 *
	 * @param  string  $chunk  Single streamed chunk from the HTTP response.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function parser( string $chunk ): void {
		$this->buffer .= $chunk;
		// Try decoding complete JSON objects from buffer
		while ( $json = $this->extract_json_object( $this->buffer ) ) {
			$decoded = json_decode( $json );
			if ( json_last_error() !== JSON_ERROR_NONE ) {
				continue;
			}
			if ( ! empty( $decoded->error ) ) {
				$this->error = (array) $decoded->error;

				return;
			}
			$this->complete_jsons[] = $decoded;
			$this->print_text_if_available( $decoded );
		}
	}

	/**
	 * Extracts a full JSON object from buffer if available.
	 *
	 * @param  string  $buffer
	 *
	 * @return string|null
	 * @since 1.0.0
	 */
	protected function extract_json_object( string &$buffer ): ?string {
		$start = strpos( $buffer, '{' );
		$end   = strrpos( $buffer, '}' );
		if ( $start !== false && $end !== false && $end > $start ) {
			$possible = substr( $buffer, $start, $end - $start + 1 );
			if ( Helper::is_probable_json( $possible ) ) {
				$buffer = substr( $buffer, $end + 1 ); // Remove processed chunk

				return $possible;
			}
		}

		return null;
	}

	/**
	 * Prints streamed content (if present) to output buffer and flushes it.
	 *
	 * @param  object  $data  A single parsed stream chunk.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function print_text_if_available( $data ): void {
		$text = $data->candidates[0]->content->parts[0]->text ?? null;
		if ( is_string( $text ) && strlen( $text ) ) {
			Stream_Event_Service::text( $text );
		}
	}
}
