<?php
namespace boru\ai\Provider\OpenAI;

use boru\ai\Provider\ProviderSignal;
use boru\ai\Schema\Response;

class OpenAIStreamParser
{
    /** @var OpenAIMapper */
    protected $mapper;

    /** @var string */
    protected $buffer = '';

    /** @var string */
    protected $currentText = '';

    /** @var Response|null */
    protected $finalResponse;

    public function __construct(OpenAIMapper $mapper = null)
    {
        $this->mapper = $mapper ? $mapper : new OpenAIMapper();
    }

    public function finalResponse()
    {
        return $this->finalResponse;
    }

    public function push($chunk, $signalCallback = null)
    {
        $this->buffer .= (string)$chunk;

        while (($pos = strpos($this->buffer, "\n\n")) !== false) {
            $rawEvent = substr($this->buffer, 0, $pos);
            $this->buffer = substr($this->buffer, $pos + 2);
            $this->parseEventBlock($rawEvent, $signalCallback);
        }
    }

    protected function parseEventBlock($rawEvent, $signalCallback = null)
    {
        $lines = preg_split('/\r?\n/', trim($rawEvent));
        $dataLines = array();

        foreach ($lines as $line) {
            if (strpos($line, 'data:') === 0) {
                $dataLines[] = trim(substr($line, 5));
            }
        }

        if (empty($dataLines)) {
            return;
        }

        $data = implode("\n", $dataLines);
        if ($data === '[DONE]') {
            if ($this->currentText !== '') {
                $signal = new ProviderSignal(ProviderSignal::TEXT_COMPLETED, array(
                    'text' => $this->currentText,
                ), $this->finalResponse);
                $this->dispatch($signal, $signalCallback);
            }
            return;
        }

        $decoded = json_decode($data, true);
        if (!is_array($decoded) || !isset($decoded['type'])) {
            return;
        }

        $type = $decoded['type'];
        if ($type === 'response.output_text.delta') {
            $delta = isset($decoded['delta']) ? $decoded['delta'] : '';
            $this->currentText .= $delta;
            $signal = new ProviderSignal(ProviderSignal::TEXT_DELTA, array(
                'delta' => $delta,
                'text' => $delta,
                'provider_event_type' => $type,
            ));
            $this->dispatch($signal, $signalCallback);
            return;
        }

        if ($type === 'response.completed' && isset($decoded['response']) && is_array($decoded['response'])) {
            $this->finalResponse = $this->mapper->mapResponse($decoded['response']);
            $signal = new ProviderSignal(ProviderSignal::RESPONSE_COMPLETED, array(
                'provider_event_type' => $type,
            ), $this->finalResponse);
            $this->dispatch($signal, $signalCallback);
            return;
        }

        if ($type === 'response.error') {
            $signal = new ProviderSignal(ProviderSignal::RESPONSE_ERROR, array(
                'error' => isset($decoded['error']) ? $decoded['error'] : null,
                'provider_event_type' => $type,
            ), $this->finalResponse);
            $this->dispatch($signal, $signalCallback);
            return;
        }
    }

    protected function dispatch(ProviderSignal $signal, $signalCallback = null)
    {
        if (is_callable($signalCallback)) {
            call_user_func($signalCallback, $signal);
        }
    }
}
