<?php

namespace ZenoChatbot\Services\Chat\Embedding;

if (!defined('ABSPATH')) {
    exit;
}

use ZenoChatbot\Services\Chat\Contracts\EmbeddingProviderInterface;
use ZenoChatbot\Services\HttpClient;
use ZenoChatbot\Helper\Settings;

/**
 * Gemini Embedding Provider
 *
 * Uses Google's Gemini embedding model (gemini-embedding-001) to generate embeddings.
 * This model produces up to 3072-dimensional vectors optimized for semantic similarity.
 *
 * @see https://ai.google.dev/gemini-api/docs/embeddings
 */
class GeminiEmbeddingProvider implements EmbeddingProviderInterface
{
    public const API_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models';
    public const MODEL_DEFAULT = 'gemini-embedding-001';

    /**
     * Embedding calls run inside visitor chat requests — keep the timeout
     * well below HttpClient's 30s default so a slow API cannot stall chat.
     */
    public const REQUEST_TIMEOUT = 10;

    protected HttpClient $httpClient;
    protected string $apiKey;
    protected string $model;
    protected int $outputDimensionality;

    /**
     * @param string|null $apiKey API key (uses LLM settings if not provided)
     * @param string $model Embedding model to use
     * @param int $outputDimensionality Output dimensions (768, 1536, or 3072 recommended)
     */
    public function __construct(
        ?string $apiKey = null,
        string $model = self::MODEL_DEFAULT,
        int $outputDimensionality = 768
    ) {
        $this->httpClient = new HttpClient([], static::REQUEST_TIMEOUT);
        $this->apiKey = $apiKey ?? $this->getApiKeyFromSettings();
        $this->model = $model;
        $this->outputDimensionality = $outputDimensionality;
    }

    /**
     * Generate embedding vector for text
     */
    public function embed(string $text): array
    {
        if (empty(trim($text))) {
            return [];
        }

        if (empty($this->apiKey)) {
            throw new \Exception('Gemini API key is not configured');
        }

        $url = $this->buildUrl('embedContent');

        $response = $this->httpClient->post(
            $url,
            [
                'model' => 'models/' . $this->model,
                'content' => ['parts' => [['text' => $text]]],
                'outputDimensionality' => $this->outputDimensionality,
            ],
            ['Content-Type' => 'application/json']
        );

        $body = $response['body'] ?? $response;

        if (!isset($body['embedding']['values'])) {
            $error = $body['error']['message'] ?? 'Invalid embedding response from Gemini';
            // 🔒 PHPCS: Exception messages are not directly rendered.
            // Escaping must happen where the exception message is displayed.
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
            throw new \Exception($error);
        }

        return $body['embedding']['values'];
    }

    /**
     * Generate embeddings for multiple texts in batch
     */
    public function embedBatch(array $texts): array
    {
        if (empty($texts)) {
            return [];
        }

        $texts = array_filter($texts, fn($t) => !empty(trim($t)));
        if (empty($texts)) {
            return [];
        }

        if (empty($this->apiKey)) {
            throw new \Exception('Gemini API key is not configured');
        }

        $url = $this->buildUrl('batchEmbedContents');

        $requests = [];
        foreach (array_values($texts) as $text) {
            $requests[] = [
                'model' => 'models/' . $this->model,
                'content' => ['parts' => [['text' => $text]]],
                'outputDimensionality' => $this->outputDimensionality,
            ];
        }

        $response = $this->httpClient->post(
            $url,
            ['requests' => $requests],
            ['Content-Type' => 'application/json']
        );

        $body = $response['body'] ?? $response;

        if (!isset($body['embeddings']) || !is_array($body['embeddings'])) {
            $error = $body['error']['message'] ?? 'Invalid batch embedding response from Gemini';
            // 🔒 PHPCS: Exception messages are not directly rendered.
            // Escaping must happen where the exception message is displayed.
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
            throw new \Exception($error);
        }

        $embeddings = [];
        foreach ($body['embeddings'] as $embedding) {
            $embeddings[] = $embedding['values'] ?? [];
        }

        return $embeddings;
    }

    protected function buildUrl(string $action): string
    {
        return self::API_BASE_URL . '/' . $this->model . ':' . $action . '?key=' . $this->apiKey;
    }

    protected function getApiKeyFromSettings(): string
    {
        return Settings::getApiKey('llm.api_key', '');
    }

    public function getModel(): string
    {
        return $this->model;
    }

    public function getDimensions(): int
    {
        return $this->outputDimensionality;
    }

    public function setOutputDimensionality(int $dimensions): self
    {
        $this->outputDimensionality = $dimensions;
        return $this;
    }
}

