<?php
namespace EasyChat\Library\Ai;





class GeminiStreamParser {
    private $buffer = '';
    private $content = '';
    private $finish_reason = '';
    private $function_calls = [];

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

        if (strpos($this->buffer, 'data:') !== false) {
            $this->processSseFrames($on_delta);
            return;
        }

        $this->processJsonObjects($on_delta);
    }

    public function finish(?callable $on_delta = null) {
        $this->processJsonObjects($on_delta);
        $tail = trim($this->buffer, " \t\n\r,[]");
        $this->buffer = '';
        if ($tail !== '') {
            $json = json_decode($tail, true);
            if (is_array($json)) {
                $this->consumeJson($json, $on_delta);
            }
        }
    }

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

    public function toGeminiResponse() {
        $parts = [];
        if (!empty($this->function_calls)) {
            foreach ($this->function_calls as $call) {
                $parts[] = ['functionCall' => $call];
            }
        } else {
            $parts[] = ['text' => $this->content];
        }

        return json_encode([
            'candidates' => [[
                'content' => [
                    'parts' => $parts,
                ],
                'finishReason' => $this->finish_reason ?: (!empty($this->function_calls) ? 'FUNCTION_CALL' : 'STOP'),
            ]],
            'choices' => [[
                'text' => $this->content,
            ]],
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

    private function processSseFrames(?callable $on_delta = null) {
        while (($pos = strpos($this->buffer, "\n\n")) !== false) {
            $frame = substr($this->buffer, 0, $pos);
            $this->buffer = substr($this->buffer, $pos + 2);

            foreach (explode("\n", $frame) as $line) {
                $line = trim($line);
                if (strpos($line, 'data:') !== 0) {
                    continue;
                }
                $data = trim(substr($line, 5));
                if ($data === '' || $data === '[DONE]') {
                    continue;
                }
                $json = json_decode($data, true);
                if (is_array($json)) {
                    $this->consumeJson($json, $on_delta);
                }
            }
        }
    }

    private function processJsonObjects(?callable $on_delta = null) {
        while (($json_text = $this->extractNextJsonObject()) !== null) {
            $json = json_decode($json_text, true);
            if (is_array($json)) {
                $this->consumeJson($json, $on_delta);
            }
        }
    }

    private function consumeJson(array $json, ?callable $on_delta = null) {
        $candidate = isset($json['candidates'][0]) && is_array($json['candidates'][0])
            ? $json['candidates'][0]
            : [];
        if (isset($candidate['finishReason'])) {
            $this->finish_reason = (string)$candidate['finishReason'];
        }

        $parts = isset($candidate['content']['parts']) && is_array($candidate['content']['parts'])
            ? $candidate['content']['parts']
            : [];

        foreach ($parts as $part) {
            if (isset($part['text']) && is_string($part['text'])) {
                $text = preg_replace("/[#\*]+\s?/i", '', $part['text']);
                if ($text !== '') {
                    $this->content .= $text;
                    if ($on_delta) {
                        $on_delta($text);
                    }
                }
            }
            if (isset($part['functionCall']) && is_array($part['functionCall'])) {
                $this->function_calls[] = $part['functionCall'];
            }
        }
    }

    private function extractNextJsonObject() {
        $start = strpos($this->buffer, '{');
        if ($start === false) {
            $this->buffer = ltrim($this->buffer, " \t\n\r,[]");
            return null;
        }

        $depth = 0;
        $in_string = false;
        $escape = false;
        $length = strlen($this->buffer);

        for ($i = $start; $i < $length; $i++) {
            $char = $this->buffer[$i];
            if ($escape) {
                $escape = false;
                continue;
            }
            if ($char === '\\') {
                $escape = true;
                continue;
            }
            if ($char === '"') {
                $in_string = !$in_string;
                continue;
            }
            if ($in_string) {
                continue;
            }
            if ($char === '{') {
                $depth++;
            } elseif ($char === '}') {
                $depth--;
                if ($depth === 0) {
                    $json_text = substr($this->buffer, $start, $i - $start + 1);
                    $this->buffer = substr($this->buffer, $i + 1);
                    return $json_text;
                }
            }
        }

        if ($start > 0) {
            $this->buffer = substr($this->buffer, $start);
        }
        return null;
    }
}
