<?php

namespace ZenoChatbot\Services\LLMService\AIClient;

use ZenoChatbot\Services\LLMService\AIClient\Config\AiConfig;
use ZenoChatbot\Services\LLMService\AIClient\Config\ConfigBuilder;

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

/**
 * AIClientConfig
 *
 * Purpose:
 * - Accept a base configuration array from system/settings
 * - Provide chainable methods to add/override extra options
 * - Build a validated AiConfig (used by PromptBuilder/AiClient)
 *
 * Required base keys:
 *   - api_key: string (required)
 *   - provider: string (required) (e.g., 'openai'|'gemini' or a provider class-string)
 *   - model: string (required)
 * Optional base keys (defaulted when null/missing):
 *   - temperature: float|null -> defaults to 0.7
 *   - max_tokens: int|null -> defaults to 1000
 *   - system_prompts: string|null (kept as-is; PromptBuilder will convert if needed)
 *
 * Extra options:
 *   - Any additional keys added via set()/merge() chainers; exposed in toArray()
 */
class AIClientConfig
{
    public const DEFAULT_TEMPERATURE = 0.7;
    public const DEFAULT_MAX_TOKENS = 1000;

    /** @var array<string,mixed> */
    protected array $base = [];

    /** @var array<string,mixed> */
    protected array $extra = [];

    public function __construct($base)
    {
        $this->base = $base;
    }

    /**
     * Create from a settings/system array.
     *
     * Expected keys:
     *  - api_key, provider, model (required)
     *  - temperature (float|null), max_tokens (int|null), system_prompts (string|null)
     */
    public static function from(array $settings)
    {
        return new static($settings);
    }

    /** Chainable setters for core fields */
    /** @return static */
    public function withApiKey(string $apiKey)
    {
        $this->base['api_key'] = $apiKey;
        return $this;
    }

    /** @return static */
    public function withProvider(string $provider)
    {
        $this->base['provider'] = $provider;
        return $this;
    }

    /** @return static */
    public function withModel(string $model)
    {
        $this->base['model'] = $model;
        return $this;
    }

    /** @return static */
    public function withTemperature(?float $t)
    {
        $this->base['temperature'] = $t;
        return $this;
    }

    /** @return static */
    public function withMaxTokens(?int $n)
    {
        $this->base['max_tokens'] = $n;
        return $this;
    }

    /** @return static */
    public function withSystemPrompts(?string $s)
    {
        $this->base['system_prompts'] = $s;
        return $this;
    }

    /** Generic chainers for additional options */
    /** @return static */
    public function set(string $key, $value)
    {
        $this->extra[$key] = $value;
        return $this;
    }

    /** @return static */
    public function merge(array $config)
    {
        foreach ($config as $k => $v) {
            $this->extra[$k] = $v;
        }
        return $this;
    }

    /** Accessors */
    public function get(string $key, $default = null)
    {
        $all = $this->toArray();
        return array_key_exists($key, $all) ? $all[$key] : $default;
    }

    /**
     * Return the current configuration as array (base merged with extras).
     */
    public function toArray(): array
    {
        return array_merge($this->base, $this->extra);
    }

    /**
     * Build a validated AiConfig instance (used by AiClient/PromptBuilder).
     * Only core keys are passed to AiConfig; extra keys remain available via toArray().
     */
    public function build(): AiConfig
    {
        $arr = $this->toArray();

        $provider = $arr['provider'] ?? null;
        $model = $arr['model'] ?? null;
        $apiKey = $arr['api_key'] ?? null;

        if (!is_string($provider) || $provider === '') {
            throw new \InvalidArgumentException('AIClientConfig: provider is required and must be a non-empty string.');
        }
        if (!is_string($model) || $model === '') {
            throw new \InvalidArgumentException('AIClientConfig: model is required and must be a non-empty string.');
        }
        if (!is_string($apiKey) || $apiKey === '') {
            throw new \InvalidArgumentException('AIClientConfig: api_key is required and must be a non-empty string.');
        }

        // temperature defaults and validation
        if (array_key_exists('temperature', $arr) && $arr['temperature'] !== null) {
            if (!is_numeric($arr['temperature'])) {
                throw new \InvalidArgumentException('AIClientConfig: temperature must be a float between 0.0 and 1.0.');
            }
            $temperature = (float)$arr['temperature'];
            if ($temperature < 0.0 || $temperature > 1.0) {
                throw new \InvalidArgumentException('AIClientConfig: temperature must be between 0.0 and 1.0.');
            }
        } else {
            $temperature = static::DEFAULT_TEMPERATURE;
        }

        // max_tokens defaults and validation
        if (array_key_exists('max_tokens', $arr) && $arr['max_tokens'] !== null) {
            if (!is_numeric($arr['max_tokens'])) {
                throw new \InvalidArgumentException('AIClientConfig: max_tokens must be an integer between 1 and 10000.');
            }
            $maxTokens = (int)$arr['max_tokens'];
            if ($maxTokens < 1 || $maxTokens > 10000) {
                throw new \InvalidArgumentException('AIClientConfig: max_tokens must be between 1 and 10000.');
            }
        } else {
            $maxTokens = static::DEFAULT_MAX_TOKENS;
        }

        $core = [
            'provider'    => $provider,
            'model'       => $model,
            'api_key'     => $apiKey,
            'temperature' => $temperature,
            'max_tokens'  => $maxTokens,
        ];

        return ConfigBuilder::create()->fromArray($core)->build();
    }

    /** Convenience: return both AiConfig and the merged array */
    public function buildPair(): array
    {
        return [$this->build(), $this->toArray()];
    }


}

