<?php
class AiomaticHuggingFaceSDK
{
    protected $apiKey;
    protected $env;
    protected $model;

    protected $chatCompletionsUrl = 'https://router.huggingface.co/v1/chat/completions';
    protected $responsesBaseUrl   = 'https://router.huggingface.co/v1/responses';

    public function __construct($env)
    {
        $this->env = $env;
        $this->set_environment();
    }

    protected function set_environment()
    {
        if (empty($this->env['apikey'])) {
            throw new Exception('API key is required.');
        }

        $this->apiKey = $this->env['apikey'];

        $this->model = $this->env['model']
            ?? 'meta-llama/Llama-3.1-8B-Instruct';
    }

    protected function build_headers()
    {
        return [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->apiKey,
        ];
    }

    protected function build_chat_body($model, $messages, $extra = [])
    {
        $body = [
            'model'    => $model,
            'messages' => $messages,
        ];

        if (!empty($extra)) {
            $body = array_merge($body, $extra);
        }

        return json_encode($body);
    }

    public function chat_completion($messages, $params = [])
    {
        $model = $params['model'] ?? $this->model;
        unset($params['model']);

        $body = $this->build_chat_body($model, $messages, $params);

        if (!empty($params['stream'])) {
            $this->stream_chat_as_sse($body);
            exit;
        }

        return $this->send_request(
            $this->chatCompletionsUrl,
            $body,
            $this->build_headers(),
            'POST'
        );
    }
    protected function stream_chat_as_sse($body)
    {
        header('Content-Type: text/event-stream; charset=utf-8');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        header('X-Accel-Buffering: no');

        @ini_set('zlib.output_compression', '0');
        while (ob_get_level() > 0) ob_end_flush();
        ob_implicit_flush(true);

        $buffer = '';
        $done = false;

        $ch = curl_init($this->chatCompletionsUrl);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => $this->build_headers(),
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$buffer, &$done) {
                $buffer .= $chunk;

                while (($pos = strpos($buffer, "\n")) !== false) {
                    $line = trim(substr($buffer, 0, $pos));
                    $buffer = substr($buffer, $pos + 1);

                    if ($line === '' || $line[0] === ':') continue;

                    if (strpos($line, 'data:') !== 0) continue;

                    $payload = trim(substr($line, 5));

                    if ($payload === '[DONE]' && !$done) {
                        echo "data: [DONE]\n\n";
                        flush();
                        $done = true;
                        continue;
                    }

                    $json = json_decode($payload, true);
                    if (!isset($json['choices'][0]['delta']['content'])) continue;

                    echo "data: " . json_encode([
                        'delta' => $json['choices'][0]['delta']['content']
                    ]) . "\n\n";
                    flush();
                }

                return strlen($chunk);
            }
        ]);

        curl_exec($ch);
        curl_close($ch);

        if (!$done) {
            echo "data: [DONE]\n\n";
            flush();
        }

        exit; 
    }
    public function generate_text($query, $extra = [])
    {
        $messages = [
            [
                'role'    => 'user',
                'content' => $query,
            ],
        ];

        return $this->chat_completion($messages, $extra);
    }

    protected function build_responses_body($model, $input, $instructions = null, $extra = [])
    {
        $body = [
            'model' => $model,
            'input' => $input,
        ];

        if ($instructions !== null) {
            $body['instructions'] = $instructions;
        }

        if (!empty($extra)) {
            $body = array_merge($body, $extra);
        }

        return json_encode($body);
    }

    public function responses_create($input, $params = [])
    {
        $model        = $params['model'] ?? $this->model;
        $instructions = $params['instructions'] ?? null;

        unset($params['model'], $params['instructions']);

        $body = $this->build_responses_body(
            $model,
            $input,
            $instructions,
            $params
        );

        if (!empty($params['stream'])) {
            return $this->send_responses_stream(
                $this->responsesBaseUrl,
                $body,
                $this->build_headers()
            );
        }

        return $this->send_request(
            $this->responsesBaseUrl,
            $body,
            $this->build_headers(),
            'POST'
        );
    }

    protected function send_responses_stream($url, $body, $headers)
    {
        $ch = curl_init($url);

        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_WRITEFUNCTION  => function ($ch, $data) {
                echo $data;
                ob_flush();
                flush();
                return strlen($data);
            },
        ]);

        curl_exec($ch);

        if (curl_errno($ch)) {
            throw new Exception('Stream error: ' . curl_error($ch));
        }

        curl_close($ch);
    }
    protected function send_request($url, $body, $headers, $method = 'POST')
    {
        $ch = curl_init($url);

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_HTTPHEADER     => $headers,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($httpCode >= 400) {
            $err = curl_error($ch);
            curl_close($ch);
            throw new Exception("HTTP {$httpCode}: {$response} {$err}");
        }

        curl_close($ch);

        return json_decode($response, true);
    }
    public function list_models($params = [])
    {
        $url = 'https://huggingface.co/api/models?' . http_build_query($params);

        return $this->send_request_list(
            $url,
            null,
            $this->build_headers(),
            'GET'
        );
    }

    protected function send_request_list($url, $body, $headers, $method)
    {
        $ch = curl_init($url);

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_HTTPHEADER     => $headers,
        ]);

        $response = curl_exec($ch);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

        curl_close($ch);

        $body = substr($response, $headerSize);
        return json_decode($body, true);
    }
}
?>
