<?php
/**
 * Gemini API Client
 *
 * Minimal client for Google Gemini API (AI Studio / Generative Language API).
 * Used for indexing-time tasks (summarization/keywords/classification).
 *
 * @package BoostBot
 * @since 1.7.0
 */

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

/**
 * Class BoostBot_Gemini_Client
 *
 * @since 1.7.0
 */
class BoostBot_Gemini_Client {

    /**
     * Gemini API base URL.
     *
     * @var string
     */
    private $api_base = 'https://generativelanguage.googleapis.com/v1beta/models/';

    /**
     * API key.
     *
     * @var string
     */
    private $api_key;

    /**
     * Model ID.
     *
     * @var string
     */
    private $model = 'gemini-2.5-flash';

    /**
     * Timeout in seconds.
     *
     * @var int
     */
    private $timeout = 60;

    /**
     * Constructor.
     *
     * @param string|null $api_key Optional API key; if not provided, uses settings.
     */
    public function __construct( ?string $api_key = null ) {
        $this->api_key = $api_key ?: (string) BoostBot_Settings::get( 'gemini_api_key', '' );

        $configured_model = (string) BoostBot_Settings::get( 'gemini_indexing_model', '' );
        if ( ! empty( $configured_model ) ) {
            $this->model = $configured_model;
        }
    }

    /**
     * Check if Gemini is available.
     *
     * @return bool True if key is present.
     */
    public function is_available(): bool {
        return ! empty( $this->api_key );
    }

    /**
     * Generate structured JSON output for a prompt.
     *
     * Uses Gemini responseMimeType JSON. Schema enforcement is intentionally omitted
     * for maximum compatibility across deployments; callers must validate JSON.
     *
     * @param string $prompt Prompt text.
     * @param array  $options Options: max_output_tokens, temperature, top_p.
     * @return array{json:string, usage:array, model:string} Result.
     * @throws Exception On API error.
     */
    public function generate_json( string $prompt, array $options = array() ): array {
        if ( ! $this->is_available() ) {
            throw new Exception( 'Gemini API key not configured' );
        }

        $url = $this->api_base . rawurlencode( $this->model ) . ':generateContent?key=' . rawurlencode( $this->api_key );

        $payload = array(
            'contents' => array(
                array(
                    'role'  => 'user',
                    'parts' => array(
                        array( 'text' => $prompt ),
                    ),
                ),
            ),
            'generationConfig' => array(
                'responseMimeType'   => 'application/json',
                'maxOutputTokens'    => isset( $options['max_output_tokens'] ) ? (int) $options['max_output_tokens'] : 500,
                'temperature'        => isset( $options['temperature'] ) ? (float) $options['temperature'] : 0.2,
                'topP'               => isset( $options['top_p'] ) ? (float) $options['top_p'] : 0.9,
                // Avoid “thinking tokens” bloat by keeping output constrained.
            ),
        );

        $response = wp_remote_post( $url, array(
            'timeout' => $this->timeout,
            'headers' => array(
                'Content-Type' => 'application/json',
            ),
            'body'    => wp_json_encode( $payload ),
        ) );

        if ( is_wp_error( $response ) ) {
            throw new Exception( esc_html( $response->get_error_message() ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( $code >= 400 ) {
            $msg = $body['error']['message'] ?? 'Gemini API error';
            throw new Exception( esc_html( $msg ), absint( $code ) );
        }

        $text = $body['candidates'][0]['content']['parts'][0]['text'] ?? '';
        $usage = $body['usageMetadata'] ?? array();

        return array(
            'json'  => (string) $text,
            'usage' => is_array( $usage ) ? $usage : array(),
            'model' => $this->model,
        );
    }
}


