<?php
/**
 * Backend AI Client (Week 26)
 *
 * Instead of BYOK (Claude/OpenAI keys), we proxy generation through the backend service.
 * The backend enforces credits + selects provider/model (Gemini for free, GPT-4o for paid).
 *
 * @package BoostBot
 */

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

class BoostBot_Backend_AI_Client {

    /**
     * @var BoostBot_Credits_Backend_Client
     */
    private $backend;

    public function __construct() {
        if ( ! class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            throw new Exception( 'Credits backend client not available.' );
        }
        $this->backend = new BoostBot_Credits_Backend_Client();
    }

    /**
     * Send a message for completion via backend proxy.
     *
     * Signature intentionally mirrors BoostBot_Claude_Client::send_message usage in Chat Handler.
     *
     * @param array $messages Claude-style messages: [{role:'user'|'assistant', content:'...'}]
     * @param array $options  system, max_tokens, temperature
     * @return array { content: string, usage?: mixed, stop_reason?: string }
     * @throws Exception on backend error
     */
    public function send_message( array $messages, array $options = array() ) {
        $t0 = microtime( true );
        $site_id = $this->backend->ensure_site_id();
        BoostBot_Logger::info( sprintf( '[TIMING] backend_ai.ensure_site_id: %dms', round( ( microtime( true ) - $t0 ) * 1000 ) ) );
        if ( is_wp_error( $site_id ) ) {
            throw new Exception( esc_html( $site_id->get_error_message() ) );
        }

        $payload = array(
            'messages'     => $this->normalize_messages( $messages ),
            'system'       => isset( $options['system'] ) ? (string) $options['system'] : '',
            'max_tokens'   => isset( $options['max_tokens'] ) ? (int) $options['max_tokens'] : null,
            'temperature'  => isset( $options['temperature'] ) ? (float) $options['temperature'] : null,
            // Week 28: per-message metering/idempotency + output cap (chars)
            'conversation_id' => isset( $options['conversation_id'] ) ? (string) $options['conversation_id'] : null,
            'idempotency_key' => isset( $options['idempotency_key'] ) ? (string) $options['idempotency_key'] : null,
            'output_char_limit' => isset( $options['output_char_limit'] ) ? (int) $options['output_char_limit'] : null,
        );

        // Remove nulls to keep backend schema simple.
        if ( null === $payload['max_tokens'] ) {
            unset( $payload['max_tokens'] );
        }
        if ( null === $payload['temperature'] ) {
            unset( $payload['temperature'] );
        }
        if ( empty( $payload['conversation_id'] ) ) {
            unset( $payload['conversation_id'] );
        }
        if ( empty( $payload['idempotency_key'] ) ) {
            unset( $payload['idempotency_key'] );
        }
        if ( null === $payload['output_char_limit'] ) {
            unset( $payload['output_char_limit'] );
        }
        if ( '' === $payload['system'] ) {
            unset( $payload['system'] );
        }

        $t1 = microtime( true );
        $resp = $this->backend->chat( (string) $site_id, $payload );
        BoostBot_Logger::info( sprintf( '[TIMING] backend_ai.chat_call: %dms', round( ( microtime( true ) - $t1 ) * 1000 ) ) );
        BoostBot_Logger::info( sprintf( '[TIMING] backend_ai.total: %dms', round( ( microtime( true ) - $t0 ) * 1000 ) ) );
        if ( is_wp_error( $resp ) ) {
            throw new Exception( esc_html( $resp->get_error_message() ) );
        }

        $content = isset( $resp['content'] ) ? (string) $resp['content'] : '';

        return array(
            'content'     => $content,
            'usage'       => isset( $resp['usage'] ) ? $resp['usage'] : null,
            'stop_reason' => null,
            'provider'    => isset( $resp['provider'] ) ? $resp['provider'] : null,
            'model'       => isset( $resp['model'] ) ? $resp['model'] : null,
        );
    }

    // Note: previous versions exposed send_message_streaming() and
    // stream_to_browser() methods that used low-level PHP HTTP calls
    // to relay SSE chunks from the backend through
    // PHP to the browser. They were never wired up â€” the live chat widget
    // streams directly browser â†” backend over SSE, so the WP plugin only
    // mediates JWT handshake and conversation storage. The methods were
    // removed in 2.2.19 to comply with WordPress.org guidance that all
    // HTTP traffic in plugin code go through the WP HTTP API. If a server-
    // side streaming relay is needed in the future, it should be built on
    // wp_remote_request() with the http_api_curl filter.

    private function normalize_messages( array $messages ): array {
        $out = array();

        foreach ( $messages as $m ) {
            if ( ! is_array( $m ) ) {
                continue;
            }
            $role    = isset( $m['role'] ) ? (string) $m['role'] : '';
            $content = isset( $m['content'] ) ? (string) $m['content'] : '';

            if ( '' === $role || '' === $content ) {
                continue;
            }

            // Backend supports: system/user/assistant; system is passed separately.
            if ( 'system' === $role ) {
                continue;
            }

            $out[] = array(
                'role'    => $role,
                'content' => $content,
            );
        }

        // Ensure there's at least one message (backend requires min(1)).
        if ( empty( $out ) ) {
            $out[] = array(
                'role'    => 'user',
                'content' => 'Hello',
            );
        }

        return $out;
    }
}


