<?php
/**
 * AI endpoint for FormElements (aiChat + aiAssist) — the single entry point for the AiHandler
 * class. SSE-only: the answer is always streamed (live reasoning + answer) so a running request
 * can be cancelled for real — aborting the connection aborts the upstream generation and frees
 * the GPU — via AiHandler::processStream().
 *
 * POST parameters: sip, _ai_mode, _ai_input, [_ai_target_language, _ai_reference,
 *                  _ai_format, _ai_history, _ai_sources].
 *
 * SSE frames: event: reasoning|content  data: {"text":"..."}
 *             event: done                data: {"status","content","sources"}
 *             event: error               data: {"message":"..."}
 */

namespace IMATHUZH\Qfq\Api;

require_once(__DIR__ . '/../../vendor/autoload.php');

use IMATHUZH\Qfq\Core\Ai\AiHandler;

// Detect API calls if this is defined. Do not try use "const QFQ_API 'Api call'" in Constants.php: this will break detection of API Calls
define('QFQ_API', 'Api call');

$flush = function (): void {
    if (ob_get_level() > 0) {
        @ob_flush();
    }
    flush();
};

/** Write one SSE frame. $data string -> {"text": ...}; array -> sent as-is. */
$emit = function (string $event, $data) use ($flush): void {
    $payload = is_array($data) ? $data : ['text' => (string)$data];
    echo AI_SSE_EVENT_PREFIX . $event . "\n";
    echo AI_SSE_DATA_PREFIX . ' ' . json_encode($payload, JSON_INVALID_UTF8_SUBSTITUTE) . "\n\n";
    $flush();
};

// SSE runtime flags + headers (queued; sent with the first body byte below). Set BEFORE the
// try so even a bootstrap error is delivered as an SSE 'error' frame.
@ini_set('zlib.output_compression', '0');
ignore_user_abort(true);   // keep running on disconnect so the stream loop can close upstream
@set_time_limit(0);
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache, no-transform');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');   // disable nginx/proxy response buffering for this response

try {
    // Bootstrap QFQ (config / stores / SESSION) + parse the request.
    $handler = new AiHandler();

    while (ob_get_level() > 0) {
        ob_end_flush();
    }
    ob_implicit_flush(true);
    // SSE comment line (ignored by clients) - primes the connection
    echo ": stream open\n\n";
    $flush();

    $handler->processStream($emit);

} catch (\Throwable $e) {

    $message = 'An unexpected error occurred.';
    try {
        // UserFormException messages might be JSON with separate user/developer parts:
        // only the user part goes to the client, the developer part to the PHP error log.
        $decoded = json_decode($e->getMessage(), true);
        if (is_array($decoded) && isset($decoded[ERROR_MESSAGE_TO_USER])) {
            $message = $decoded[ERROR_MESSAGE_TO_USER];
            error_log('QFQ aiHandler.php: ' . ($decoded[ERROR_MESSAGE_TO_DEVELOPER] ?? ''));
        } else {
            $message = $e->getMessage();
        }
    } catch (\Throwable $exception) {
        // Keep generic message.
    }

    $emit(AI_STREAM_ERROR, [API_MESSAGE => $message]);
}
