<?php
// LocalAIApi — client for AI services.
// Supports the Flatlogic proxy and direct OpenAI API calls.

class LocalAIApi
{
    /** @var array<string,mixed>|null */
    private static ?array $configCache = null;

    /**
     * Create a response from the configured AI service.
     *
     * @param array<string,mixed> $params Request body (model, input, etc.).
     * @param array<string,mixed> $options Extra options.
     * @return array<string,mixed>
     */
    public static function createResponse(array $params, array $options = []): array
    {
        $cfg = self::config();
        $payload = $params;

        if ($cfg['is_custom']) {
            // Direct OpenAI API call
            if (empty($payload['input']) || !is_array($payload['input'])) {
                return [
                    'success' => false,
                    'error'   => 'input_missing',
                    'message' => 'Parameter "input" is required for custom AI call.',
                ];
            }

            // OpenAI uses 'messages', not 'input'
            $openAiPayload = [
                'model' => $payload['model'] ?? $cfg['default_model'],
                'messages' => $payload['input'],
            ];

            return self::request(null, $openAiPayload, $options);

        } else {
            // Flatlogic Proxy call
            if (empty($payload['input']) || !is_array($payload['input'])) {
                return [
                    'success' => false,
                    'error'   => 'input_missing',
                    'message' => 'Parameter "input" is required and must be an array.',
                ];
            }

            if (!isset($payload['model']) || $payload['model'] === '') {
                $payload['model'] = $cfg['default_model'];
            }

            $initial = self::request($options['path'] ?? null, $payload, $options);
            if (empty($initial['success'])) {
                return $initial;
            }

            // Async flow for Flatlogic proxy
            $data = $initial['data'] ?? null;
            if (is_array($data) && isset($data['ai_request_id'])) {
                $aiRequestId = $data['ai_request_id'];
                $pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300;
                $pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5;
                return self::awaitResponse($aiRequestId, [
                    'timeout' => $pollTimeout,
                    'interval' => $pollInterval,
                    'headers' => $options['headers'] ?? [],
                    'timeout_per_call' => $options['timeout'] ?? null,
                ]);
            }

            return $initial;
        }
    }

    /**
     * Perform a raw request.
     *
     * @param string|null $path Endpoint path.
     * @param array<string,mixed> $payload JSON payload.
     * @param array<string,mixed> $options Additional options.
     * @return array<string,mixed>
     */
    public static function request(?string $path = null, array $payload = [], array $options = []): array
    {
        $cfg = self::config();

        if (!$cfg['is_custom']) {
            $projectUuid = $cfg['project_uuid'];
            if (empty($projectUuid)) {
                return [
                    'success' => false,
                    'error'   => 'project_uuid_missing',
                    'message' => 'PROJECT_UUID is not defined; aborting AI request.',
                ];
            }
        }

        $defaultPath = $cfg['responses_path'] ?? null;
        $resolvedPath = $path ?? ($options['path'] ?? $defaultPath);
        if (empty($resolvedPath)) {
            return [
                'success' => false,
                'error'   => 'path_missing',
                'message' => 'Cannot resolve AI endpoint path.',
            ];
        }

        $url = self::buildUrl($resolvedPath, $cfg['base_url']);
        $timeout = (int)($options['timeout'] ?? $cfg['timeout'] ?? 30);
        $verifyTls = (bool)($options['verify_tls'] ?? $cfg['verify_tls'] ?? true);
        
        $headers = $cfg['headers'] ?? [];
        if (!empty($options['headers']) && is_array($options['headers'])) {
            $headers = array_merge($headers, $options['headers']);
        }
        
        if (!$cfg['is_custom']) {
             if (!empty($cfg['project_uuid']) && !array_key_exists('project_uuid', $payload)) {
                $payload['project_uuid'] = $cfg['project_uuid'];
            }
        }

        $body = json_encode($payload, JSON_UNESCAPED_UNICODE);
        if ($body === false) {
            return ['success' => false, 'error' => 'json_encode_failed'];
        }

        return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls);
    }
    
    /**
     * Extract plain text from an AI response.
     *
     * @param array<string,mixed> $response
     * @return string
     */
    public static function extractText(array $response): string
    {
        $payload = $response['data'] ?? $response;
        if (!is_array($payload)) {
            return '';
        }

        // OpenAI API format
        if (!empty($payload['choices'][0]['message']['content'])) {
            return (string) $payload['choices'][0]['message']['content'];
        }
        
        // Flatlogic proxy format
        if (!empty($payload['output']) && is_array($payload['output'])) {
            $combined = '';
            foreach ($payload['output'] as $item) {
                if (!isset($item['content']) || !is_array($item['content'])) continue;
                foreach ($item['content'] as $block) {
                    if (($block['type'] ?? '') === 'output_text' && !empty($block['text'])) {
                        $combined .= $block['text'];
                    }
                }
            }
            if ($combined !== '') return $combined;
        }

        return '';
    }

    // --- Helper methods below are mostly unchanged ---

    /**
     * Load configuration.
     * @return array<string,mixed>
     */
    private static function config(): array
    {
        if (self::$configCache === null) {
            $configPath = __DIR__ . '/config.php';
            if (!file_exists($configPath)) throw new RuntimeException('AI config not found');
            self::$configCache = require $configPath;
        }
        return self::$configCache;
    }

    /**
     * Shared cURL sender.
     * @return array<string,mixed>
     */
    private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array
    {
        if (!function_exists('curl_init')) {
            return ['success' => false, 'error' => 'curl_missing', 'message' => 'PHP cURL extension is missing.'];
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0);

        if (strtoupper($method) === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? '');
        }

        $responseBody = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($responseBody === false) {
            $error = curl_error($ch);
            curl_close($ch);
            return ['success' => false, 'error' => 'curl_error', 'message' => $error];
        }
        curl_close($ch);

        $decoded = json_decode($responseBody, true);

        if ($status >= 200 && $status < 300) {
            return ['success' => true, 'status' => $status, 'data' => $decoded ?? $responseBody];
        }

        $errorMessage = 'Request failed';
        if (is_array($decoded) && isset($decoded['error']['message'])) {
            $errorMessage = $decoded['error']['message'];
        } elseif(is_string($responseBody)) {
            $errorMessage = $responseBody;
        }
        
        return [
            'success' => false,
            'status' => $status,
            'error' => $errorMessage,
            'response' => $decoded ?? $responseBody
        ];
    }
    
    /**
     * Build an absolute URL.
     */
    private static function buildUrl(string $path, string $baseUrl): string
    {
        if (str_starts_with($path, 'http')) return $path;
        return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
    }

    // --- Methods for Flatlogic proxy async flow ---

    public static function awaitResponse($aiRequestId, array $options = []): array
    {
        $deadline = time() + (int)($options['timeout'] ?? 300);
        $interval = (int)($options['interval'] ?? 5);

        while (time() < $deadline) {
            $statusResp = self::fetchStatus($aiRequestId, $options);
            if (!($statusResp['success'] ?? false)) return $statusResp;

            $data = $statusResp['data'] ?? [];
            $statusValue = $data['status'] ?? null;

            if ($statusValue === 'success') {
                return ['success' => true, 'status' => 200, 'data' => $data['response'] ?? $data];
            }
            if ($statusValue === 'failed') {
                return ['success' => false, 'error' => 'AI request failed', 'data' => $data];
            }
            sleep($interval);
        }
        return ['success' => false, 'error' => 'timeout'];
    }

    public static function fetchStatus($aiRequestId, array $options = []): array
    {
        $cfg = self::config();
        if (empty($cfg['project_uuid'])) {
            return ['success' => false, 'error' => 'project_uuid_missing'];
        }
        $statusPath = self::resolveStatusPath($aiRequestId, $cfg);
        $url = self::buildUrl($statusPath, $cfg['base_url']);

        $headers = $options['headers'] ?? [];
        $headers[] = 'Accept: application/json';
        $headers[] = $cfg['project_header'] . ': ' . $cfg['project_uuid'];
        
        return self::sendCurl($url, 'GET', null, $headers, (int)($options['timeout'] ?? 30), true);
    }
    
    private static function resolveStatusPath($aiRequestId, array $cfg): string
    {
        $basePath = rtrim($cfg['responses_path'] ?? '', '/');
        return $basePath . '/' . rawurlencode((string)$aiRequestId) . '/status';
    }

    /**
     * Snake_case alias for createResponse.
     * @deprecated
     */
    public static function create_response(array $params, array $options = []): array
    {
        return self::createResponse($params, $options);
    }
    
    /**
     * Decode JSON from response text.
     */
    public static function decodeJsonFromResponse(array $response): ?array
    {
        $text = self::extractText($response);
        if ($text === '') return null;
        $decoded = json_decode($text, true);
        if(is_array($decoded)) return $decoded;
        // Strip markdown fences
        $stripped = preg_replace('/^```json\n?|\n?```$/', '', $text);
        if ($stripped !== $text) {
             $decoded = json_decode($stripped, true);
             if(is_array($decoded)) return $decoded;
        }
        return null;
    }
}

// Legacy alias
if (!class_exists('OpenAIService')) {
    class_alias(LocalAIApi::class, 'OpenAIService');
}