<?php
// Service/Features/ApiClientService.php
// 该文件负责处理与 AI API 客户端相关的逻辑，例如客户端的创建。

namespace HaoZiTeam\AIPost\Service\Features;

defined('ABSPATH') || exit;

use HaoZiTeam\ChatGPT\V2;
use HaoZiTeam\AIPost\Service\Features\WenXinClient;
use HaoZiTeam\AIPost\Service\Features\SecureSettings;
use Exception;

class ApiClientService
{
    /**
     * 根据设置创建并返回一个 API 客户端实例 (V2)。
     *
     * @param array $settings 插件的全局设置。
     * @param string $task_key 当前任务的键名。
     * @param array $task_config 当前任务的特定配置。
     * @return V2|null V2 客户端实例，如果创建失败则返回 null。
     */
    /**
     * 根据设置创建并返回一个 API 客户端实例。
     *
     * @param array $settings 插件的全局设置。
     * @param string $task_key 当前任务的键名。
     * @param array $task_config 当前任务的特定配置。
     * @return V2|WenXinClient|null 客户端实例，如果创建失败则返回 null。
     */
    public static function create_api_client(array $settings, string $task_key, array $task_config, string $overrideModel = '')
    {
        try {
            // 客户端初始化即检查一次字体目录与默认字体
            try { self::ensure_default_font_present(); } catch (\Throwable $e) { /* silent */ }
            $apiServerType = $settings['api-server-type'] ?? 'custom';
            // 任务级覆盖：允许不同任务使用不同的 API 类型/端点
            // - task-api-server-type: global(默认) 或 openai/custom/deepseek/doubao/bai-lian/wenxin
            $taskApiType = isset($task_config['task-api-server-type']) ? trim((string)$task_config['task-api-server-type']) : '';
            if ($taskApiType !== '' && $taskApiType !== 'global') {
                $apiServerType = $taskApiType;
            }

            $chat_gpt = null;

            // 优先使用插件自带 CA 证书，强制传给 Guzzle 的 verify 选项
            // 路径：wp-content/plugins/ai-post/resources/cacert.pem
            $pluginRoot = \dirname(__DIR__, 2); // 从 Service/Features 回到插件根目录
            $forcedCABundle = $pluginRoot . '/resources/cacert.pem';
            $verifyOption = @file_exists($forcedCABundle) ? $forcedCABundle : true; // 找不到则回退系统默认

            if ($apiServerType === 'custom' || $apiServerType === 'openai' || $apiServerType === '') {
                $accounts = $settings['openai-accounts'] ?? [];
                $apiServer = trim((string)($settings['openai-api-server'] ?? 'https://sk.slapi.cn/'));

                // 兼容用户把完整接口地址粘贴进来：只保留 baseUrl
                $apiServer = rtrim($apiServer, "/\t\n\r ");
                $apiServer = preg_replace('#/v\d+/chat/completions/?$#i', '', $apiServer);
                $apiServer = preg_replace('#/chat/completions/?$#i', '', $apiServer);
                $apiServer = rtrim((string)$apiServer, "/\t\n\r ");
                if ($apiServer === '') {
                    $apiServer = 'https://sk.slapi.cn/';
                }
                if (substr($apiServer, -1) !== '/') {
                    $apiServer .= '/';
                }

                // sk.slapi.cn 的 OpenAI 兼容接口通常位于 /v1/ 下；根路径可能返回站点 HTML 而非 JSON
                // 若用户仅填写了域名根，则自动补全 /v1/
                $parsed = @parse_url($apiServer);
                $host = is_array($parsed) ? (string)($parsed['host'] ?? '') : '';
                $path = is_array($parsed) ? (string)($parsed['path'] ?? '') : '';
                if ($host === 'sk.slapi.cn' && ($path === '' || $path === '/')) {
                    $apiServer = rtrim($apiServer, '/') . '/v1/';
                }

                $selectedModel = 'gpt-4o-mini';
                $apiKey = '';

                if (!empty($accounts) && is_array($accounts)) {
                    $account = $accounts[rand(0, count($accounts) - 1)];
                    $model_selection = $account['model-selection'] ?? $selectedModel;
                    $custom_model_name = $account['custom-model-name'] ?? '';
                    if ($model_selection === 'custom') {
                        $selectedModel = !empty($custom_model_name) ? (string)$custom_model_name : $selectedModel;
                    } else {
                        $selectedModel = (string)$model_selection;
                    }
                    $apiKey = trim((string)($account['account-secret-key'] ?? ''));
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }

                // 安全存储回退：openai/custom 回退 openai_key / custom_key
                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('openai_key');
                    if (!$secKey) {
                        $secKey = SecureSettings::get('custom_key');
                    }
                    if (is_string($secKey) && $secKey !== '') {
                        $apiKey = trim($secKey);
                    }
                }

                if (empty($apiKey)) {
                    if (function_exists('error_log')) {
                        error_log('AI Post ApiClientService: custom/openai missing apiKey, cannot create client.');
                    }
                    return null;
                }

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    null,
                    null,
                    360,
                    [
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'deepseek') {
                $accounts = $settings['deepseek-accounts'] ?? [];
                $apiServer = 'https://api.deepseek.com/'; // Default DeepSeek API server
                // Ensure server URL ends with a slash
                if (substr($apiServer, -1) !== '/') {
                    $apiServer .= '/';
                }

                if (empty($accounts)) {
                    return null;
                }

                $account = $accounts[rand(0, count($accounts) - 1)];

                $model_selection = $account['model-selection'] ?? 'deepseek-chat';
                $custom_model_name = $account['custom-model-name'] ?? '';

                if ($model_selection === 'custom') {
                    $selectedModel = !empty($custom_model_name) ? $custom_model_name : 'deepseek-chat';
                } else {
                    $selectedModel = $model_selection;
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }
                
                $apiKey = $account['account-secret-key'] ?? '';
                // 安全存储回退：deepseek 仅回退 deepseek_key
                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('deepseek_key');
                    if (is_string($secKey) && $secKey !== '') { $apiKey = trim($secKey); }
                }

                // 安全存储回退：openai/custom 回退 openai_key / custom_key
                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('openai_key');
                    if (!$secKey && ($apiServerType === 'custom' || $apiServerType === '')) {
                        $secKey = SecureSettings::get('custom_key');
                    }
                    if (is_string($secKey) && $secKey !== '') { $apiKey = trim($secKey); }
                }
                if (empty($apiKey)) {
                    return null;
                }

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    null, // proxy
                    null, // proxy_auth
                    360,  // timeout
                    [
                        'is_deepseek' => true,
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'moonshot') {
                $accounts = $settings['moonshot-accounts'] ?? [];
                $apiServer = 'https://api.moonshot.cn/v1/';
                if (substr($apiServer, -1) !== '/') {
                    $apiServer .= '/';
                }

                $selectedModel = 'moonshot-v1-8k';
                $apiKey = '';

                if (!empty($accounts) && is_array($accounts)) {
                    $account = $accounts[rand(0, count($accounts) - 1)];
                    $model_selection = $account['model-selection'] ?? $selectedModel;
                    $custom_model_name = $account['custom-model-name'] ?? '';

                    if ($model_selection === 'custom') {
                        $selectedModel = !empty($custom_model_name) ? (string)$custom_model_name : $selectedModel;
                    } else {
                        $selectedModel = (string)$model_selection;
                    }
                    $apiKey = trim((string)($account['account-secret-key'] ?? ''));
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }

                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('moonshot_key');
                    if (is_string($secKey) && $secKey !== '') { $apiKey = trim($secKey); }
                }
                if (empty($apiKey)) {
                    return null;
                }

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    null,
                    null,
                    360,
                    [
                        'is_moonshot' => true,
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'doubao') {
                $accounts = $settings['doubao-accounts'] ?? [];
                $apiServer = 'https://ark.cn-beijing.volces.com/api/v3/'; // Default Doubao API server
                // Ensure server URL ends with a slash and remove chat/completions if present
                if (substr($apiServer, -1) !== '/') {
                    $apiServer .= '/';
                }
                // NOTE: do NOT use rtrim($url, 'chat/completions/') as it is a character mask
                // and may unexpectedly trim valid URL chars. Use regex to remove path suffix instead.
                $apiServer = preg_replace('#/chat/completions/?$#i', '/', $apiServer);
                $apiServer = rtrim((string)$apiServer, "/\t\n\r ");
                if (substr($apiServer, -1) !== '/') { // Ensure it ends with a slash after normalization
                    $apiServer .= '/';
                }

                // Doubao OpenAI-compatible endpoint lives under /api/v3/
                // If user configured a host root or other path, normalize to /api/v3/
                $parsed = @parse_url($apiServer);
                $host = is_array($parsed) ? (string)($parsed['host'] ?? '') : '';
                $path = is_array($parsed) ? (string)($parsed['path'] ?? '') : '';
                if ($host !== '' && (stripos($host, 'volces.com') !== false)) {
                    if ($path === '' || $path === '/' || stripos($path, '/api/v3') === false) {
                        $scheme = is_array($parsed) && !empty($parsed['scheme']) ? (string)$parsed['scheme'] : 'https';
                        $apiServer = $scheme . '://' . $host . '/api/v3/';
                    }
                }

                if (empty($accounts)) {
                    return null;
                }

                $account = $accounts[rand(0, count($accounts) - 1)];

                $model_selection = $account['model-selection'] ?? 'doubao-seed-2-0-mini-260215'; // Default model
                $custom_model_name = $account['custom-model-name'] ?? '';

                if ($model_selection === 'custom') {
                    $selectedModel = !empty($custom_model_name) ? $custom_model_name : 'doubao-seed-2-0-mini-260215';
                } else {
                    $selectedModel = $model_selection;
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }

                // 兼容旧豆包模型ID（历史版本使用 1.5 点号写法或缺少日期后缀）
                $selectedModel = trim((string)$selectedModel);
                $doubaoModelMap = [
                    'doubao-1.5-pro-32k-250115' => 'doubao-1-5-pro-32k-250115',
                    'doubao-1.5-pro-32k' => 'doubao-1-5-pro-32k-250115',
                    'doubao-1.5-pro-256k' => 'doubao-1-5-pro-256k-250115',
                    'doubao-1-5-pro-256k' => 'doubao-1-5-pro-256k-250115',
                ];
                if (isset($doubaoModelMap[$selectedModel])) {
                    $selectedModel = $doubaoModelMap[$selectedModel];
                }

                // 豆包可用模型白名单（用户已在命令行验证通过）
                // 若设置中残留旧模型名或误填模型名，这里统一回退，避免 404/无权限错误。
                $doubaoAllowedModels = [
                    'doubao-seed-2-0-mini-260215',
                    'doubao-1-5-lite-32k-250115',
                    'doubao-1-5-pro-32k-250115',
                    'doubao-seed-2-0-pro-260215',
                    'doubao-seed-2-0-lite-260215',
                    'doubao-seed-2-0-code-preview-260215',
                    'doubao-seed-1-8-251228',
                    'doubao-seed-1-6-flash-250828',
                    'doubao-seed-1-6-vision-250815',
                    'doubao-seed-1-6-lite-251015',
                    'glm-4-7-251222',
                    'deepseek-v3-2-251201',
                    'deepseek-v3-1-terminus',
                    'deepseek-r1-250528',
                    'deepseek-v3-250324',
                ];
                if (!in_array($selectedModel, $doubaoAllowedModels, true)) {
                    $selectedModel = 'doubao-seed-2-0-mini-260215';
                }
                
                $apiKey = $account['account-secret-key'] ?? '';
                // 安全存储回退：doubao 回退 doubao_key
                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('doubao_key');
                    if (is_string($secKey) && $secKey !== '') { $apiKey = trim($secKey); }
                }
                if (empty($apiKey)) {
                    return null;
                }

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    null, // proxy
                    null, // proxy_auth
                    360,  // timeout
                    [
                        'is_doubao' => true,
                        'temperature' => $account['temperature'] ?? 0.7,
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'bai-lian') {
                $accounts = $settings['bai-lian-accounts'] ?? [];
                $apiServer = 'https://dashscope.aliyuncs.com/compatible-mode/v1/'; // 阿里百炼兼容模式API服务器
                // 确保服务器URL以斜杠结尾
                if (substr($apiServer, -1) !== '/') {
                    $apiServer .= '/';
                }

                if (empty($accounts)) {
                    return null;
                }

                $account = $accounts[rand(0, count($accounts) - 1)];

                $model_selection = $account['model-selection'] ?? 'qwen-plus'; // 默认模型
                $custom_model_name = $account['custom-model-name'] ?? '';

                if ($model_selection === 'custom') {
                    $selectedModel = !empty($custom_model_name) ? $custom_model_name : 'qwen-plus';
                } else {
                    $selectedModel = $model_selection;
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }
                
                $apiKey = $account['account-secret-key'] ?? '';
                // 确保密钥没有多余空格
                $apiKey = trim($apiKey);
                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('bailian_key');
                    if (is_string($secKey) && $secKey !== '') { $apiKey = trim($secKey); }
                }
                if (empty($apiKey)) {
                    return null;
                }

                // 获取温度设置
                $temperature = $account['temperature'] ?? 0.7;
                $temperature = (float)$temperature; // 确保类型正确

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    $temperature, // 传递温度参数
                    null, // topP
                    360,  // timeout
                    [
                        'is_bai_lian' => true,
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'zhipu') {
                $accounts = $settings['zhipu-accounts'] ?? [];
                // 智谱：支持两个兼容端点轮询
                // - 默认优先 open.bigmodel.cn
                // - 同一请求内多次 ask() 会按轮询顺序切换
                $primaryServer = 'https://open.bigmodel.cn/api/paas/v4/';
                $fallbackServer = 'https://api.z.ai/api/paas/v4/';

                $primaryServer = rtrim(trim((string)$primaryServer), "/\t\n\r ");
                if (substr($primaryServer, -1) !== '/') {
                    $primaryServer .= '/';
                }
                $fallbackServer = rtrim(trim((string)$fallbackServer), "/\t\n\r ");
                if (substr($fallbackServer, -1) !== '/') {
                    $fallbackServer .= '/';
                }

                $selectedModel = 'glm-4.7-flashx';
                $apiKey = '';

                if (!empty($accounts) && is_array($accounts)) {
                    $account = $accounts[rand(0, count($accounts) - 1)];
                    $model_selection = $account['model-selection'] ?? $selectedModel;
                    $custom_model_name = $account['custom-model-name'] ?? '';
                    if ($model_selection === 'custom') {
                        $selectedModel = !empty($custom_model_name) ? $custom_model_name : $selectedModel;
                    } else {
                        $selectedModel = (string)$model_selection;
                    }
                    $apiKey = trim((string)($account['account-secret-key'] ?? ''));
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }

                if (empty($apiKey)) {
                    $secKey = SecureSettings::get('zhipu_key');
                    if (is_string($secKey) && $secKey !== '') {
                        $apiKey = trim($secKey);
                    }
                }
                if (empty($apiKey)) {
                    return null;
                }

                $factory = function (string $baseUrl) use ($apiKey, $selectedModel, $verifyOption) {
                    return new V2(
                        $apiKey,
                        $baseUrl,
                        $selectedModel,
                        null, // proxy
                        null, // proxy_auth
                        360,  // timeout
                        [
                            'headers' => [
                                'Authorization' => 'Bearer ' . $apiKey,
                                'Content-Type' => 'application/json'
                            ],
                            'verify' => $verifyOption
                        ]
                    );
                };

                static $zhipu_rr = 0;
                $servers = [$primaryServer, $fallbackServer];
                $startIndex = $zhipu_rr % count($servers);
                $zhipu_rr++;

                $chat_gpt = new ZhipuPollClient(
                    $factory,
                    $servers,
                    $startIndex,
                    (string)$task_key
                );
            } elseif ($apiServerType === 'spark') {
                $accounts = $settings['spark-accounts'] ?? [];
                // 讯飞星火：
                // - x1/spark-x 走 v2: https://spark-api-open.xf-yun.com/v2/chat/completions
                // - lite 走 v1: https://spark-api-open.xf-yun.com/v1/chat/completions
                $apiServerV2 = 'https://spark-api-open.xf-yun.com/v2/';
                $apiServerV1 = 'https://spark-api-open.xf-yun.com/v1/';
                if (substr($apiServerV2, -1) !== '/') {
                    $apiServerV2 .= '/';
                }
                if (substr($apiServerV1, -1) !== '/') {
                    $apiServerV1 .= '/';
                }

                $apiServer = $apiServerV2;
                $selectedModel = 'spark-x';

                $apiKeyV2 = '';
                $apiKeyV1 = '';
                $apiKeyPro = '';
                $apiKeyMax = '';

                if (!empty($accounts) && is_array($accounts)) {
                    $account = $accounts[rand(0, count($accounts) - 1)];

                    $model_selection = $account['model-selection'] ?? $selectedModel;
                    $custom_model_name = $account['custom-model-name'] ?? '';
                    if ($model_selection === 'custom') {
                        $selectedModel = !empty($custom_model_name) ? (string)$custom_model_name : $selectedModel;
                    } else {
                        $selectedModel = (string)$model_selection;
                    }

                    // v2 / v1 两套 APIPassword（不共用）
                    $apiKeyV2 = trim((string)($account['account-secret-key-v2'] ?? ''));
                    $apiKeyV1 = trim((string)($account['account-secret-key-v1'] ?? ''));
                    // v2 的 pro/max 两套 APIPassword（可选；当模型为 spark-pro / spark-max 时优先使用）
                    $apiKeyPro = trim((string)($account['account-secret-key-pro'] ?? ''));
                    $apiKeyMax = trim((string)($account['account-secret-key-max'] ?? ''));

                    // 兼容旧配置：原 account-secret-key 视为 v2（x1/x2）
                    if ($apiKeyV2 === '') {
                        $apiKeyV2 = trim((string)($account['account-secret-key'] ?? ''));
                    }
                }

                if (trim($overrideModel) !== '') {
                    $selectedModel = trim($overrideModel);
                }

                // 模型归一化：兼容历史别名；允许自定义模型名直通
                $m = trim((string)$selectedModel);
                if ($m === 'spark-x2') {
                    $m = 'spark-x';
                }
                if ($m === 'x1.5' || $m === 'spark-x1.5' || $m === 'spark-x1') {
                    $m = 'x1';
                }
                if ($m === '') {
                    $m = 'spark-x';
                }

                $selectedModel = $m;
                if ($selectedModel === 'lite') {
                    $apiServer = $apiServerV1;
                } else {
                    $apiServer = $apiServerV2;
                }

                // secure 优先：spark_v2_key / spark_v1_key
                if ($apiKeyV2 === '') {
                    $secKeyV2 = SecureSettings::get('spark_v2_key');
                    if (is_string($secKeyV2) && $secKeyV2 !== '') {
                        $apiKeyV2 = trim($secKeyV2);
                    }
                }
                if ($apiKeyV1 === '') {
                    $secKeyV1 = SecureSettings::get('spark_v1_key');
                    if (is_string($secKeyV1) && $secKeyV1 !== '') {
                        $apiKeyV1 = trim($secKeyV1);
                    }
                }

                // 可选：secure 的 pro/max key
                if ($apiKeyPro === '') {
                    $secKeyPro = SecureSettings::get('spark_pro_key');
                    if (is_string($secKeyPro) && $secKeyPro !== '') {
                        $apiKeyPro = trim($secKeyPro);
                    }
                }
                if ($apiKeyMax === '') {
                    $secKeyMax = SecureSettings::get('spark_max_key');
                    if (is_string($secKeyMax) && $secKeyMax !== '') {
                        $apiKeyMax = trim($secKeyMax);
                    }
                }

                // 兼容旧 secure：spark_key 视为 v2
                if ($apiKeyV2 === '') {
                    $secKey = SecureSettings::get('spark_key');
                    if (is_string($secKey) && $secKey !== '') {
                        $apiKeyV2 = trim($secKey);
                    }
                }

                // 选择最终 key：
                // - lite -> v1
                // - spark-pro -> pro
                // - spark-max -> max
                // - 其它 -> v2
                if ($selectedModel === 'lite') {
                    $apiKey = $apiKeyV1;
                } elseif ($selectedModel === 'spark-pro') {
                    $apiKey = $apiKeyPro !== '' ? $apiKeyPro : $apiKeyV2;
                } elseif ($selectedModel === 'spark-max') {
                    $apiKey = $apiKeyMax !== '' ? $apiKeyMax : $apiKeyV2;
                } else {
                    $apiKey = $apiKeyV2;
                }
                if (empty($apiKey)) {
                    return null;
                }

                $chat_gpt = new V2(
                    $apiKey,
                    $apiServer,
                    $selectedModel,
                    null, // proxy
                    null, // proxy_auth
                    360,  // timeout
                    [
                        'is_spark' => true,
                        'headers' => [
                            'Authorization' => 'Bearer ' . $apiKey,
                            'Content-Type' => 'application/json'
                        ],
                        'verify' => $verifyOption
                    ]
                );

            } elseif ($apiServerType === 'wenxin') {
                $accounts = $settings['wenxin-accounts'] ?? [];
                
                if (empty($accounts)) {
                    // 安全存储回退（无账号项时尝试）：仅当 secure 中存在键值时使用
                    $apiKey = SecureSettings::get('wenxin_key');
                    if (is_string($apiKey) && $apiKey !== '') {
                        // 若仅提供了 apiKey 而无 secret 无法调用，仍返回 null
                    }
                    return null;
                }


                $account = $accounts[rand(0, count($accounts) - 1)];

                $model_selection = $account['model-selection'] ?? 'ernie-4.0-8k';
                if (trim($overrideModel) !== '') {
                    $model_selection = trim($overrideModel);
                }
                $apiKey = $account['api-key'] ?? '';
                $secretKey = $account['secret-key'] ?? '';
                if (empty($apiKey) || empty($secretKey)) {
                    $sec = SecureSettings::get('wenxin_key');
                    // 仅当安全存储内能解析出 apiKey:secretKey 形式时才使用
                    if (is_string($sec) && strpos($sec, ':') !== false) {
                        list($ak, $sk) = explode(':', $sec, 2);
                        $ak = trim((string)$ak); $sk = trim((string)$sk);
                        if ($ak !== '' && $sk !== '') { $apiKey = $ak; $secretKey = $sk; }
                    }
                }

                if (empty($apiKey) || empty($secretKey)) {
                    return null;
                }

                // 仅允许温度透传，其余参数在客户端内固定为合理默认
                $defaultOptions = [];
                if (isset($account['temperature']) && $account['temperature'] !== '') {
                    $defaultOptions['temperature'] = (float)$account['temperature'];
                }

                $chat_gpt = new WenXinClient(
                    $apiKey,
                    $secretKey,
                    $model_selection,
                    $defaultOptions
                );
            }
        } catch (Exception $e) {
            return null;
        }

        // 统一语言守卫：为所有模型注入目标语言约束 + 语言校验/重试/翻译兜底
        try {
            // 允许在特定场景彻底禁用语言守卫（例如编辑器一键生成：让模型自行判断标题语言）
            $disableGuard = (bool)($task_config['disable-language-guard'] ?? false);
            if ($disableGuard) {
                return $chat_gpt;
            }
            $lang = self::resolve_target_language($task_config);
            if (is_array($lang) && !empty($lang['display'])) {
                $chat_gpt = new LanguageGuardClient($chat_gpt, (string)$lang['display'], (string)($lang['code'] ?? ''), (string)$task_key);
            }
        } catch (\Throwable $e) {
            // 守卫初始化失败不影响主流程
        }

        return $chat_gpt;
    }

    /**
     * 从任务配置解析目标语言。
     * - 优先使用 post-tags-language（如 tags-en/tags-ja/tags-ko-KR 等）映射到显示名称
     * - custom 则使用 custom-language-description
     */
    private static function resolve_target_language(array $task_config): array
    {
        $langKey = isset($task_config['post-tags-language']) ? (string)$task_config['post-tags-language'] : '';
        $langKey = trim($langKey);

        if ($langKey === 'custom') {
            $desc = isset($task_config['custom-language-description']) ? trim((string)$task_config['custom-language-description']) : '';
            return [
                'code' => $langKey,
                'display' => $desc !== '' ? $desc : '自定义语言',
            ];
        }

        if ($langKey === '') {
            return [ 'code' => '', 'display' => '' ];
        }

        $map = \HaoZiTeam\AIPost\Service\Features\LanguageUtils::get_language_map();
        $display = isset($map[$langKey]) ? (string)$map[$langKey] : '';
        if ($display === '') {
            $display = $langKey;
        }
        return [
            'code' => $langKey,
            'display' => $display,
        ];
    }

}

class ZhipuFailoverClient
{
    private $client;
    private $factory;
    private string $primaryServer;
    private string $fallbackServer;
    private string $taskKey;
    private bool $switched = false;
    private bool $inFailover = false;

    public function __construct($client, callable $factory, string $primaryServer, string $fallbackServer, string $taskKey = '')
    {
        $this->client = $client;
        $this->factory = $factory;
        $this->primaryServer = $primaryServer;
        $this->fallbackServer = $fallbackServer;
        $this->taskKey = (string)$taskKey;
    }

    public function __call($name, $arguments)
    {
        if (is_object($this->client)) {
            $method = (string)$name;
            if (is_callable([$this->client, $method])) {
                return $this->client->{$method}(...$arguments);
            }
        }
        throw new \BadMethodCallException('Method not found: ' . (string)$name);
    }

    public function getModel()
    {
        if (is_object($this->client) && method_exists($this->client, 'getModel')) {
            return $this->client->getModel();
        }
        return null;
    }

    public function ask(...$args)
    {
        try {
            return $this->client->ask(...$args);
        } catch (\Throwable $e) {
            // 防止递归/重复 failover
            if ($this->inFailover || $this->switched) {
                throw $e;
            }

            $this->inFailover = true;
            try {
                if (function_exists('error_log')) {
                    error_log('[AI-Post][ZhipuFailover] primary failed, fallback retry once. task=' . $this->taskKey . ' primary=' . $this->primaryServer . ' fallback=' . $this->fallbackServer . ' err=' . $e->getMessage());
                }

                $factory = $this->factory;
                $this->client = $factory($this->fallbackServer);
                $this->switched = true;

                $resp = $this->client->ask(...$args);
                if (function_exists('error_log')) {
                    error_log('[AI-Post][ZhipuFailover] fallback success, switched for subsequent calls. task=' . $this->taskKey . ' using=' . $this->fallbackServer);
                }
                return $resp;
            } finally {
                $this->inFailover = false;
            }
        }
    }
}

class ZhipuPollClient
{
    private $factory;
    private array $servers;
    private int $index;
    private string $taskKey;
    private array $messages = [];

    public function __construct(callable $factory, array $servers, int $startIndex = 0, string $taskKey = '')
    {
        $this->factory = $factory;
        $this->servers = array_values(array_filter(array_map('trim', $servers), function ($s) {
            return $s !== '';
        }));
        if (empty($this->servers)) {
            $this->servers = ['https://open.bigmodel.cn/api/paas/v4/'];
        }
        $this->index = $startIndex;
        $this->taskKey = (string)$taskKey;
    }

    public function addMessage(string $message, string $role = 'user'): void
    {
        $this->messages[] = [
            'role' => (string)$role,
            'content' => (string)$message,
        ];
    }

    public function clearMessages(): void
    {
        $this->messages = [];
    }

    private function applyMessages($client): void
    {
        if (!is_object($client) || empty($this->messages)) {
            return;
        }
        if (!is_callable([$client, 'addMessage'])) {
            return;
        }
        foreach ($this->messages as $m) {
            $role = isset($m['role']) ? (string)$m['role'] : 'user';
            $content = isset($m['content']) ? (string)$m['content'] : '';
            if ($content === '') {
                continue;
            }
            $client->addMessage($content, $role);
        }
    }

    private function curServer(): string
    {
        $n = count($this->servers);
        if ($n <= 0) {
            return '';
        }
        $i = $this->index % $n;
        if ($i < 0) {
            $i = 0;
        }
        return (string)$this->servers[$i];
    }

    private function nextServer(): string
    {
        $this->index++;
        return $this->curServer();
    }

    public function getModel()
    {
        $baseUrl = $this->curServer();
        $client = ($this->factory)($baseUrl);
        $this->applyMessages($client);
        if (is_object($client) && method_exists($client, 'getModel')) {
            return $client->getModel();
        }
        return null;
    }

    public function ask(...$args)
    {
        $baseUrl = $this->curServer();
        $client = ($this->factory)($baseUrl);
        $this->applyMessages($client);

        try {
            $res = $client->ask(...$args);
            $this->nextServer();
            return $res;
        } catch (\Throwable $e) {
            $retryUrl = $this->nextServer();
            if (function_exists('error_log')) {
                error_log('[AI-Post][ZhipuPoll] request failed, retry other endpoint once. task=' . $this->taskKey . ' base=' . $baseUrl . ' retry=' . $retryUrl . ' err=' . $e->getMessage());
            }

            $client2 = ($this->factory)($retryUrl);
            $res2 = $client2->ask(...$args);
            $this->nextServer();
            return $res2;
        }
    }
}

/**
 * LanguageGuardClient
 * - 包裹底层 AI 客户端（V2/WenXinClient 等），统一对 ask() 注入目标语言约束。
 * - 对输出进行语言一致性检测：若不符合目标语言，则重试一次；仍不符合则翻译兜底。
 */
class LanguageGuardClient
{
    private $client;
    private string $targetDisplay;
    private string $targetCode;
    private string $taskKey;
    private bool $inGuard = false;

    private function startsWith(string $haystack, string $needle): bool
    {
        if ($needle === '') {
            return true;
        }
        return substr($haystack, 0, strlen($needle)) === $needle;
    }

    private function contains(string $haystack, string $needle): bool
    {
        if ($needle === '') {
            return true;
        }
        return strpos($haystack, $needle) !== false;
    }

    public function __construct($client, string $targetDisplay, string $targetCode = '', string $taskKey = '')
    {
        $this->client = $client;
        $this->targetDisplay = trim($targetDisplay);
        $this->targetCode = trim($targetCode);
        $this->taskKey = (string)$taskKey;
    }

    public function __call($name, $arguments)
    {
        if (is_object($this->client)) {
            $method = (string)$name;
            if (is_callable([$this->client, $method])) {
                return $this->client->{$method}(...$arguments);
            }
        }
        throw new \BadMethodCallException('Method not found: ' . (string)$name);
    }

    public function getModel()
    {
        if (is_object($this->client) && method_exists($this->client, 'getModel')) {
            return $this->client->getModel();
        }
        return null;
    }

    /**
     * 兼容 V2::ask(string $prompt, string $user=null, bool $stream=false)
     * 兼容 WenXinClient::ask(string $prompt, array $options=[])
     */
    public function ask(...$args)
    {
        $prompt = isset($args[0]) ? (string)$args[0] : '';
        $rest = array_slice($args, 1);

        if ($this->inGuard) {
            return $this->callRawAsk($prompt, $rest);
        }

        $this->inGuard = true;
        try {
            $basePrompt = $this->injectLanguageControl($prompt, false);
            $resp = $this->callRawAsk($basePrompt, $rest);
            $answer = is_array($resp) ? (string)($resp['answer'] ?? '') : '';

            if ($answer !== '' && $this->isLanguageMismatch($answer)) {
                if (function_exists('error_log')) {
                    error_log('[AI-Post][LangGuard] mismatch detected, retry once. task=' . $this->taskKey . ' target=' . $this->targetDisplay);
                }

                $retryPrompt = $this->injectLanguageControl($prompt, true);
                $resp2 = $this->callRawAsk($retryPrompt, $rest);
                $answer2 = is_array($resp2) ? (string)($resp2['answer'] ?? '') : '';
                if ($answer2 !== '' && !$this->isLanguageMismatch($answer2)) {
                    return $resp2;
                }

                // 翻译兜底（保持格式优先：只翻译文本，不改变结构）
                if ($answer2 !== '') {
                    $translated = $this->translateFallback($answer2, $rest);
                    if (is_array($translated) && (string)($translated['answer'] ?? '') !== '') {
                        return $translated;
                    }
                }
                // 返回重试结果（即使仍不符，也不阻断主流程）
                return $resp2;
            }

            return $resp;
        } finally {
            $this->inGuard = false;
        }
    }

    private function callRawAsk(string $prompt, array $rest)
    {
        try {
            // V2::ask($prompt, $user=null, $stream=false)
            if (count($rest) === 0) {
                return $this->client->ask($prompt);
            }
            if (count($rest) === 1) {
                return $this->client->ask($prompt, $rest[0]);
            }
            if (count($rest) >= 2) {
                return $this->client->ask($prompt, $rest[0], $rest[1]);
            }
        } catch (\ArgumentCountError $e) {
            // WenXinClient::ask($prompt, array $options=[])
            $options = [];
            if (count($rest) >= 1 && is_array($rest[0])) {
                $options = $rest[0];
            }
            return $this->client->ask($prompt, $options);
        }
        return $this->client->ask($prompt);
    }

    private function injectLanguageControl(string $prompt, bool $hard): string
    {
        $lang = $this->targetDisplay !== '' ? $this->targetDisplay : '目标语言';
        $control = "\n\n【语言控制（全局）】\n";
        $control .= "你必须使用 {$lang} 输出最终内容。\n";
        if (!$this->isChineseTarget()) {
            $control .= "禁止输出简体中文或繁体中文；禁止混用多种语言。\n";
        } else {
            $control .= "禁止混用多种语言。\n";
        }
        if ($this->isEnglishTarget()) {
            $control .= "英文任务额外规则：禁止出现任何中文字符（含中文标点/汉字）。\n";
        }
        if ($hard) {
            $control .= "必须严格遵守以上语言规则；若无法满足，请直接按原要求输出，但仍需保持目标语言一致。\n";
        }
        return rtrim($prompt) . $control;
    }

    private function isEnglishTarget(): bool
    {
        $code = strtolower($this->targetCode);
        $disp = strtolower($this->targetDisplay);
        if ($code === 'en' || $this->startsWith($code, 'tags-en')) {
            return true;
        }
        return (strpos($disp, 'english') !== false) || (mb_strpos($this->targetDisplay, '英文', 0, 'UTF-8') !== false);
    }

    private function isChineseTarget(): bool
    {
        $code = strtolower($this->targetCode);
        $disp = strtolower($this->targetDisplay);
        if ($code === 'zh' || $code === 'zh-cn' || $code === 'zh-hans' || $code === 'zh-tw' || $code === 'zh-hant') {
            return true;
        }
        if ($this->startsWith($code, 'tags-zh')) {
            return true;
        }
        if (strpos($disp, 'chinese') !== false) {
            return true;
        }
        return (mb_strpos($this->targetDisplay, '中文', 0, 'UTF-8') !== false);
    }

    private function isLanguageMismatch(string $text): bool
    {
        $text = trim($text);
        if ($text === '' || $this->targetDisplay === '') {
            return false;
        }

        // 中文目标：不做中文字符限制（仅靠提示约束），避免误判。
        if ($this->isChineseTarget()) {
            return false;
        }

        // 英文：出现中文字符则判定不一致（最严格）
        if ($this->isEnglishTarget()) {
            return (bool)preg_match('/[\x{4e00}-\x{9fff}]/u', $text);
        }

        // 日文：必须包含假名（平假名/片假名），否则可能是中文
        $code = strtolower($this->targetCode);
        if ($code === 'ja' || $this->startsWith($code, 'tags-ja') || mb_strpos($this->targetDisplay, '日语', 0, 'UTF-8') !== false) {
            $hasKana = (bool)preg_match('/[\x{3040}-\x{30ff}]/u', $text);
            if ($hasKana) {
                return false;
            }
            // 无假名且大量 CJK，倾向中文
            return (bool)preg_match('/[\x{4e00}-\x{9fff}]/u', $text);
        }

        // 韩文：必须包含韩文字母，否则可能跑偏
        if ($code === 'ko' || $this->startsWith($code, 'tags-ko') || mb_strpos($this->targetDisplay, '韩语', 0, 'UTF-8') !== false) {
            $hasHangul = (bool)preg_match('/[\x{ac00}-\x{d7af}]/u', $text);
            if ($hasHangul) {
                return false;
            }
            return (bool)preg_match('/[\x{4e00}-\x{9fff}]/u', $text);
        }

        // 其它语言：核心目标是避免跑回中文（简/繁）。
        return (bool)preg_match('/[\x{4e00}-\x{9fff}]/u', $text);
    }

    private function translateFallback(string $answer, array $rest)
    {
        $lang = $this->targetDisplay !== '' ? $this->targetDisplay : '目标语言';
        $prompt = "请将下面内容翻译为 {$lang}。\n";
        $prompt .= "要求：\n";
        $prompt .= "1) 必须保持原有格式/结构不变（如果是 JSON/HTML/Markdown，请保持结构与标签不变，只翻译文本内容）。\n";
        $prompt .= "2) 禁止输出中文（简体/繁体），禁止添加解释。\n";
        $prompt .= "3) 只输出翻译后的内容本体。\n\n";
        $prompt .= "【待翻译内容】\n";
        $prompt .= $answer;

        if (function_exists('error_log')) {
            error_log('[AI-Post][LangGuard] translate fallback. task=' . $this->taskKey . ' target=' . $lang);
        }

        return $this->callRawAsk($prompt, $rest);
    }
}