<?php
namespace EasyChat\Library\Ai;








class OpenAiSseStreamParser {
    private $buffer = '';
    private $content = '';
    private $finish_reason = '';
    private $done = false;
    private $tool_calls = [];
    private $grounding = null;
    private $provider_error = null;

    public function feed($chunk, ?callable $on_delta = null) {
        $this->buffer .= (string)$chunk;
        $this->buffer = str_replace("\r\n", "\n", $this->buffer);

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

    public function finish(?callable $on_delta = null) {
        $tail = trim($this->buffer);
        $this->buffer = '';
        if ($tail !== '') {
            $this->processFrame($tail, $on_delta);
        }
    }

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

    public function getFinishReason() {
        if ($this->finish_reason !== '') {
            return $this->finish_reason;
        }
        return !empty($this->tool_calls) ? 'tool_calls' : 'stop';
    }

    public function toOpenAiResponse() {
        $message = [
            'role'    => 'assistant',
            'content' => $this->content !== '' ? $this->content : null,
        ];

        if (!empty($this->tool_calls)) {
            $message['tool_calls'] = array_values($this->tool_calls);
        }

        $response = [
            'choices' => [[
                'message'       => $message,
                'finish_reason' => $this->getFinishReason(),
                'text'          => $this->content,
            ]],
        ];
        if (is_array($this->grounding)) {
            $response['easytokens'] = ['grounding' => $this->grounding];
        }
        if (is_array($this->provider_error)) {
            $response['error'] = $this->provider_error;
        }
        return json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

    private function processFrame($frame, ?callable $on_delta = null) {
        $frame = trim((string)$frame);
        if ($frame === '') {
            return;
        }

        $data_lines = [];
        foreach (explode("\n", $frame) as $line) {
            $line = trim($line);
            if ($line === '' || strpos($line, 'event:') === 0 || strpos($line, ':') === 0) {
                continue;
            }
            if (strpos($line, 'data:') === 0) {
                $data_lines[] = trim(substr($line, 5));
            } elseif (substr($line, 0, 1) === '{') {
                $data_lines[] = $line;
            }
        }

        foreach ($data_lines as $data) {
            if ($data === '' || $data === '[DONE]') {
                $this->done = true;
                continue;
            }
            $json = json_decode($data, true);
            if (!is_array($json)) {
                continue;
            }
            $this->consumeJson($json, $on_delta);
        }
    }

    private function consumeJson(array $json, ?callable $on_delta = null) {
        if (isset($json['error']) && is_array($json['error'])) {
            $this->provider_error = $json['error'];
        }
        if (isset($json['easytokens']['grounding']) && is_array($json['easytokens']['grounding'])) {
            $this->grounding = $json['easytokens']['grounding'];
        }
        if (empty($json['choices']) || !is_array($json['choices'])) {
            return;
        }

        foreach ($json['choices'] as $choice) {
            if (isset($choice['finish_reason']) && $choice['finish_reason'] !== null) {
                $this->finish_reason = (string)$choice['finish_reason'];
            }

            $delta = isset($choice['delta']) && is_array($choice['delta']) ? $choice['delta'] : [];

            $text = '';
            if (isset($delta['content']) && is_string($delta['content'])) {
                $text = $delta['content'];
            } elseif (isset($choice['text']) && is_string($choice['text'])) {
                $text = $choice['text'];
            }

            if ($text !== '') {
                $this->content .= $text;
                if ($on_delta) {
                    $on_delta($text);
                }
            }

            if (!empty($delta['tool_calls']) && is_array($delta['tool_calls'])) {
                foreach ($delta['tool_calls'] as $tool_delta) {
                    $this->accumulateToolCall($tool_delta);
                }
            }
        }
    }

    private function accumulateToolCall(array $tool_delta) {
        $index = isset($tool_delta['index']) ? (int)$tool_delta['index'] : 0;
        if (!isset($this->tool_calls[$index])) {
            $this->tool_calls[$index] = [
                'id'       => '',
                'type'     => 'function',
                'function' => [
                    'name'      => '',
                    'arguments' => '',
                ],
            ];
        }

        if (!empty($tool_delta['id'])) {
            $this->tool_calls[$index]['id'] = (string)$tool_delta['id'];
        }
        if (!empty($tool_delta['type'])) {
            $this->tool_calls[$index]['type'] = (string)$tool_delta['type'];
        }
        if (isset($tool_delta['function']) && is_array($tool_delta['function'])) {
            if (isset($tool_delta['function']['name'])) {
                $this->tool_calls[$index]['function']['name'] .= (string)$tool_delta['function']['name'];
            }
            if (isset($tool_delta['function']['arguments'])) {
                $this->tool_calls[$index]['function']['arguments'] .= (string)$tool_delta['function']['arguments'];
            }
        }
    }
}
