<?php
namespace NeoRename\NeoAi; if (!defined("ABSPATH")) { die("neoWP direct access not allowed"); }

function wp_ai_model_preferences() {
    $model_preferences = ["gpt-5-mini", "gemini-3.1-flash-lite", "claude-haiku"];
    if (\class_exists("\\WordPress\\AiClient\\AiClient")) { foreach (\WordPress\AiClient\AiClient::defaultRegistry()->getRegisteredProviderIds() as $provider_id) { foreach (\WordPress\AiClient\AiClient::defaultRegistry()->findProviderModelsMetadataForSupport($provider_id, new \WordPress\AiClient\Providers\Models\DTO\ModelRequirements([\WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::textGeneration()], [])) as $model_metadata) { foreach ($model_preferences as $model_preference) { if (str_starts_with($model_metadata->getId(), $model_preference . "-")) { $model_preferences[] = $model_metadata->getId(); } } } } }
    return \NeoRename\NeoGlobal\array_unique_better($model_preferences);
}

function ai_provider_options() {
    return [
        "neoai"     => ["label" => \NeoRename\NeoGlobal\neo__("neoAI (free)", "neoAI (kostenfrei)"), "default_model" => null,                        "guide_url" => "",                                                                                                                                           "guide_label" => "",                                                                               "api_key_placeholder" => ""],
        "openai"    => ["label" => "OpenAI / ChatGPT",                                     "default_model" => "gpt-5-mini",                "guide_url" => "https://" . \NeoRename\NeoGlobal\option__neo_wp_com() . \NeoRename\NeoGlobal\neo__("", "/de") . "/wp-how-to/create-openai-api-key-guide/?ref=neo-ai--settings",  "guide_label" => \NeoRename\NeoGlobal\neo__("Guide: API Key for OpenAI", "Anleitung: API-Key für OpenAI"),   "api_key_placeholder" => "sk-proj-••••••••••"],
        "anthropic" => ["label" => "Anthropic / Claude",                                   "default_model" => "claude-haiku-4-5-20251001", "guide_url" => "https://" . \NeoRename\NeoGlobal\option__neo_wp_com() . \NeoRename\NeoGlobal\neo__("", "/de") . "/wp-how-to/create-claude-api-key-guide/?ref=neo-ai--settings",  "guide_label" => \NeoRename\NeoGlobal\neo__("Guide: API Key for Claude", "Anleitung: API-Key für Claude"),   "api_key_placeholder" => "sk-ant-••••••••••"],
        "google"    => ["label" => "Google / Gemini",                                      "default_model" => "gemini-3.1-flash-lite",     "guide_url" => "https://" . \NeoRename\NeoGlobal\option__neo_wp_com() . \NeoRename\NeoGlobal\neo__("", "/de") . "/wp-how-to/create-gemini-api-key-guide/?ref=neo-ai--settings",  "guide_label" => \NeoRename\NeoGlobal\neo__("Guide: API Key for Gemini", "Anleitung: API-Key für Gemini"),   "api_key_placeholder" => "AIza••••••••••"],
        "xai"       => ["label" => "xAI / Grok",                                           "default_model" => "grok-4.3",                  "guide_url" => "https://" . \NeoRename\NeoGlobal\option__neo_wp_com() . \NeoRename\NeoGlobal\neo__("", "/de") . "/wp-how-to/create-grok-api-key-guide/?ref=neo-ai--settings",    "guide_label" => \NeoRename\NeoGlobal\neo__("Guide: API Key for Grok", "Anleitung: API-Key für Grok"),       "api_key_placeholder" => "xai-••••••••••"],
        "mistral"   => ["label" => "Mistral",                                              "default_model" => "ministral-3b-2512",         "guide_url" => "https://" . \NeoRename\NeoGlobal\option__neo_wp_com() . \NeoRename\NeoGlobal\neo__("", "/de") . "/wp-how-to/create-mistral-api-key-guide/?ref=neo-ai--settings", "guide_label" => \NeoRename\NeoGlobal\neo__("Guide: API Key for Mistral", "Anleitung: API-Key für Mistral"), "api_key_placeholder" => "••••••••••"],
        "wordpress" => ["label" => "WordPress Integration",                                "default_model" => null,                        "guide_url" => "",                                                                                                                                           "guide_label" => "",                                                                               "api_key_placeholder" => ""],
        "custom"    => ["label" => "Custom / Self Hosted",                                 "default_model" => null,                        "guide_url" => "",                                                                                                                                           "guide_label" => "",                                                                               "api_key_placeholder" => "••••••••••"]
    ];
}

function ai_connection_model($connection) {
    return ($connection["model"] ?? null) ?: (ai_provider_options()[$connection["provider"] ?? ""]["default_model"] ?? "");
}

\NeoRename\NeoGlobal\register_migration("2026-06-28", function () {
    delete_option("neo_ai__openai_api_key");
});

function free_provider_auth($regenerate = false) {
    $secret = $regenerate ? "" : \NeoRename\NeoGlobal\option__neo_ai__free_provider_secret();
    if (!$secret) { $secret = bin2hex(random_bytes(128)); \NeoRename\NeoGlobal\option__neo_ai__free_provider_secret($secret); }
    $public_key = base64_decode("9u6hNOCq5TsOxWiG8t3yyRE01Ut069T8kSD/m7mtQkY=", true);
    $encrypted_secret = sodium_crypto_box_seal($secret, $public_key);
    $auth_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/neo-ai--auth.txt";
    \NeoRename\NeoGlobal\fs_file_put_contents($auth_file_path, base64_encode($encrypted_secret));
    return ["secret" => $secret, "auth_url" => \NeoRename\NeoGlobal\cache_url("neo-ai") . "/neo-ai--auth.txt"];
}

function free_provider_request($endpoint, $payload = [], $retry_auth = true) {
    $status_cache_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/free-provider-status.json";
    if ($endpoint === "generate" && $retry_auth) { \NeoRename\NeoGlobal\synclock_dir(\NeoRename\NeoGlobal\cache_path("neo-ai"), timeout: 25, callback: function () use ($status_cache_file_path) { if (!\NeoRename\NeoGlobal\fs_file_exists($status_cache_file_path)) { return; } $cached_response = \NeoRename\NeoGlobal\json_decode_better(\NeoRename\NeoGlobal\fs_file_get_contents($status_cache_file_path), suppress_error: true); if (is_array($cached_response) && isset($cached_response["remaining_requests"])) { $status_cache_modified_timestamp = \NeoRename\NeoGlobal\fs_filemtime($status_cache_file_path); $cached_response["remaining_requests"] = max(0, $cached_response["remaining_requests"] - 1); \NeoRename\NeoGlobal\fs_write_json_file($status_cache_file_path, $cached_response); \NeoRename\NeoGlobal\fs_touch($status_cache_file_path, $status_cache_modified_timestamp); } }, scope: "free-provider-status"); }
    $auth = free_provider_auth();
    $domain = strtolower(wp_parse_url(content_url(), PHP_URL_HOST));
    unset($payload["model"]);
    $request_data = ["domain" => $domain, "secret" => $auth["secret"], "auth-url" => $auth["auth_url"], "timestamp" => time(), "plugin-version" => \NeoRename\NeoGlobal\plugin_version(), "payload" => $payload];

    try { $response_json = \NeoRename\NeoGlobal\curl_request("https://ai." . \NeoRename\NeoGlobal\option__neo_wp_com() . "/" . $endpoint . "/", method: "POST", data: $request_data, timeout: $endpoint === "status" ? 10 : 130); }
    catch (\NeoRename\NeoGlobal\CurlException $error) {
        $error_response = \NeoRename\NeoGlobal\json_decode_better($error->get_response_body(), suppress_error: true);
        if ($retry_auth && $error->get_http_status_code() === 401 && ($error_response["code"] ?? "") === "neo-ai__auth-invalid") { free_provider_auth(true); return free_provider_request($endpoint, $payload, false); }
        if (is_array($error_response) && ($error_response["message"] ?? "") !== "") { \NeoRename\NeoGlobal\throw_global_exception($error_response["message"], status_code: $error->get_http_status_code(), error_code: $error_response["message"] === "The free neoAI quota for this domain is exhausted." ? "neo-ai__quota-exhausted" : ($error_response["code"] ?? "neo-ai__service-error")); }
        throw $error;
    }
    $response = \NeoRename\NeoGlobal\json_decode_better($response_json);
    if (!is_array($response)) { \NeoRename\NeoGlobal\throw_global_exception("neoAI returned an invalid response."); }
    return $response;
}

function token_usage_cost($usage_entry) {
    $price_table = [
        "gpt-5.5-pro"                     => ["input" => 30.00, "output" => 180.00],
        "gpt-5.5"                         => ["input" => 5.00,  "output" => 30.00],
        "gpt-5.4-mini"                    => ["input" => 0.75,  "output" => 4.50],
        "gpt-5.4-nano"                    => ["input" => 0.20,  "output" => 1.25],
        "gpt-5.4-pro"                     => ["input" => 30.00, "output" => 180.00],
        "gpt-5.4"                         => ["input" => 2.50,  "output" => 15.00],
        "gpt-5.3-codex"                   => ["input" => 1.75,  "output" => 14.00],
        "chat-latest"                     => ["input" => 5.00,  "output" => 30.00],
        "gpt-5-mini"                      => ["input" => 0.25,  "output" => 2.00],
        "gpt-5-nano"                      => ["input" => 0.05,  "output" => 0.40],
        "gpt-5"                           => ["input" => 1.25,  "output" => 10.00],
        "claude-fable-5"                  => ["input" => 10.00, "output" => 50.00],
        "claude-opus-4-8"                 => ["input" => 5.00,  "output" => 25.00],
        "claude-opus-4-7"                 => ["input" => 5.00,  "output" => 25.00],
        "claude-opus-4-6"                 => ["input" => 5.00,  "output" => 25.00],
        "claude-opus-4-5"                 => ["input" => 5.00,  "output" => 25.00],
        "claude-sonnet-4-6"               => ["input" => 3.00,  "output" => 15.00],
        "claude-sonnet-4-5"               => ["input" => 3.00,  "output" => 15.00],
        "claude-haiku-4-5"                => ["input" => 1.00,  "output" => 5.00],
        "grok-4.3"                        => ["input" => 1.25,  "output" => 2.50],
        "grok-build-0.1"                  => ["input" => 1.00,  "output" => 2.00],
        "gemini-3.5-flash"                => ["input" => 1.50,  "output" => 9.00],
        "gemini-3.1-pro-preview"          => ["input" => 2.00,  "output" => 12.00],
        "gemini-3.1-flash-lite"           => ["input" => 0.25,  "output" => 1.50],
        "gemini-3-flash-preview"          => ["input" => 0.50,  "output" => 3.00],
        "gemini-2.5-flash-lite"           => ["input" => 0.10,  "output" => 0.40],
        "gemini-2.5-flash"                => ["input" => 0.30,  "output" => 2.50],
        "gemini-2.5-pro"                  => ["input" => 1.25,  "output" => 10.00],
        "mistral-medium-3-5"              => ["input" => 1.50,  "output" => 7.50],
        "mistral-medium-latest"           => ["input" => 1.50,  "output" => 7.50],
        "mistral-small-2603"              => ["input" => 0.15,  "output" => 0.60],
        "mistral-small-latest"            => ["input" => 0.15,  "output" => 0.60],
        "mistral-large-2512"              => ["input" => 0.50,  "output" => 1.50],
        "mistral-large-latest"            => ["input" => 0.50,  "output" => 1.50],
        "voxtral-small-latest"            => ["input" => 0.10,  "output" => 0.40],
        "devstral-medium-latest"          => ["input" => 0.40,  "output" => 2.00],
        "devstral-2512"                   => ["input" => 0.40,  "output" => 2.00],
        "devstral-small-latest"           => ["input" => 0.10,  "output" => 0.30],
        "codestral-latest"                => ["input" => 0.30,  "output" => 0.90],
        "codestral-2508"                  => ["input" => 0.30,  "output" => 0.90],
        "magistral-medium-latest"         => ["input" => 2.00,  "output" => 5.00],
        "magistral-small-latest"          => ["input" => 0.50,  "output" => 1.50],
        "ministral-14b"                   => ["input" => 0.20,  "output" => 0.20],
        "ministral-8b"                    => ["input" => 0.15,  "output" => 0.15],
        "ministral-3b"                    => ["input" => 0.10,  "output" => 0.10],
        "labs-leanstral-2603"             => ["input" => 0.00,  "output" => 0.00],
        "open-mistral-nemo"               => ["input" => 0.15,  "output" => 0.15],
        "open-mixtral-8x22b"              => ["input" => 2.00,  "output" => 6.00],
        "open-mixtral-8x7b"               => ["input" => 0.70,  "output" => 0.70],
    ];
    $model = str_replace("_", "-", \NeoRename\NeoGlobal\preg_replace_better("/\\s+/", "-", strtolower(basename((string) ($usage_entry["model"] ?? "")))));
    if ($model === "") { return null; }
    $price = null;
    foreach ($price_table as $model_prefix => $price_candidate) { if ($model === $model_prefix || str_starts_with($model, $model_prefix . "-")) { $price = $price_candidate; break; } }
    if (!$price) { return null; }
    return ((int) ($usage_entry["input_tokens"] ?? 0) * $price["input"] + ((int) ($usage_entry["output_tokens"] ?? 0) + (int) ($usage_entry["reasoning_tokens"] ?? 0)) * $price["output"]) / 1000000;
}

function compress_token_usage_list($usage_entries) {
    $normal_entries = []; $compressed_entries_by_model = [];
    foreach ($usage_entries as $usage_entry) { $usage_entry = is_array($usage_entry) ? $usage_entry : []; $usage_entry = ["date" => $usage_entry["date"] ?? \NeoRename\NeoGlobal\wp_date_string(), "description" => $usage_entry["description"] ?? "", "provider" => $usage_entry["provider"] ?? "unknown", "model" => $usage_entry["model"] ?? "unknown", "input_tokens" => max(0, $usage_entry["input_tokens"] ?? 0), "output_tokens" => max(0, $usage_entry["output_tokens"] ?? 0), "reasoning_tokens" => max(0, $usage_entry["reasoning_tokens"] ?? 0), "remaining_requests" => empty($usage_entry["compressed"]) && isset($usage_entry["remaining_requests"]) ? max(0, $usage_entry["remaining_requests"]) : null, "compressed" => !empty($usage_entry["compressed"])]; if ($usage_entry["compressed"]) { $model = $usage_entry["model"] ?: "unknown"; $model_key = ($usage_entry["provider"] ?: "unknown") . "|" . $model; if (!isset($compressed_entries_by_model[$model_key])) { $compressed_entries_by_model[$model_key] = $usage_entry; } else { $compressed_entries_by_model[$model_key]["input_tokens"] += $usage_entry["input_tokens"]; $compressed_entries_by_model[$model_key]["output_tokens"] += $usage_entry["output_tokens"]; $compressed_entries_by_model[$model_key]["reasoning_tokens"] += $usage_entry["reasoning_tokens"]; if ($compressed_entries_by_model[$model_key]["date"] < $usage_entry["date"]) { $compressed_entries_by_model[$model_key]["date"] = $usage_entry["date"]; } } continue; } $normal_entries[] = $usage_entry; }
    if (count($normal_entries) <= 50) { return array_merge(array_values($compressed_entries_by_model), $normal_entries); }
    $entries_to_compress = array_slice($normal_entries, 0, count($normal_entries) - 50); $entries_to_keep = array_slice($normal_entries, -50);
    foreach ($entries_to_compress as $usage_entry) { $model = $usage_entry["model"] ?: "unknown"; $model_key = ($usage_entry["provider"] ?: "unknown") . "|" . $model; if (!isset($compressed_entries_by_model[$model_key])) { $compressed_entries_by_model[$model_key] = ["date" => $usage_entry["date"], "description" => "", "provider" => $usage_entry["provider"] ?: "unknown", "model" => $model, "input_tokens" => 0, "output_tokens" => 0, "reasoning_tokens" => 0, "remaining_requests" => null, "compressed" => true]; } $compressed_entries_by_model[$model_key]["input_tokens"] += $usage_entry["input_tokens"]; $compressed_entries_by_model[$model_key]["output_tokens"] += $usage_entry["output_tokens"]; $compressed_entries_by_model[$model_key]["reasoning_tokens"] += $usage_entry["reasoning_tokens"]; if ($compressed_entries_by_model[$model_key]["date"] < $usage_entry["date"]) { $compressed_entries_by_model[$model_key]["date"] = $usage_entry["date"]; } $compressed_entries_by_model[$model_key]["description"] = sprintf(\NeoRename\NeoGlobal\neo__("Usage of %s before %s", "Verbrauch von %s vor %s"), $model, $compressed_entries_by_model[$model_key]["date"]); }
    return array_merge(array_values($compressed_entries_by_model), $entries_to_keep);
}

function record_token_usage($description, $provider, $model, $input_tokens, $output_tokens, $reasoning_tokens = 0, $remaining_requests = null) {
    $usage_entries = \NeoRename\NeoGlobal\option__neo_ai__token_usage_list();
    $usage_entries = is_array($usage_entries) ? $usage_entries : [];
    $usage_entries[] = ["date" => \NeoRename\NeoGlobal\wp_date_string(), "description" => $description, "provider" => $provider ?: "unknown", "model" => $model ?: "unknown", "input_tokens" => max(0, $input_tokens), "output_tokens" => max(0, $output_tokens), "reasoning_tokens" => max(0, $reasoning_tokens), "remaining_requests" => $remaining_requests === null ? null : $remaining_requests, "compressed" => false];
    \NeoRename\NeoGlobal\option__neo_ai__token_usage_list(compress_token_usage_list($usage_entries));
}

function generate_text_with_image($image_source, $prompt, $cache_key_prefix = "image-text", $usage_description = "neoAI Text generiert", $mime_type = "", &$remaining_requests = null) {
    $image_id = 0;
    if (\NeoRename\NeoGlobal\fs_is_file($image_source)) { $image_file_path = $image_source; $mime_type = $mime_type ?: (wp_check_filetype($image_file_path)["type"] ?: "image/jpeg"); }
    else { $image_id = attachment_url_to_postid($image_source); if ($image_id === 0) { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("Image not found.", "Bild nicht gefunden."), status_code: 404); } $image_file_path = get_attached_file($image_id); if ($image_file_path === false || !\NeoRename\NeoGlobal\fs_is_file($image_file_path)) { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("Local image file is missing.", "Lokale Bilddatei fehlt."), status_code: 404); } $mime_type = get_post_mime_type($image_id) ?: (wp_check_filetype($image_file_path)["type"] ?: "image/jpeg"); }
    if (!str_starts_with($mime_type, "image/")) { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("AI image text generation is only available for images.", "AI-Bildtext kann nur für Bilder generiert werden."), status_code: 400); }
    if ($mime_type === "image/svg+xml") { $svg_content = \NeoRename\NeoGlobal\fs_file_get_contents($image_file_path); $pixel_variant = \NeoRename\NeoGlobal\get_pixel_variant($svg_content); if (!$pixel_variant || empty($pixel_variant["base64_url"]) || !str_starts_with($pixel_variant["base64_url"], "data:image/webp;base64,")) { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("This SVG has no neoDraw pixel preview for AI analysis.", "Dieses SVG hat keine neoDraw-Pixelvorschau für die AI-Analyse."), status_code: 400); } $image_binary = base64_decode(substr($pixel_variant["base64_url"], strlen("data:image/webp;base64,")), true); if ($image_binary === false || $image_binary === "") { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("The neoDraw pixel preview could not be decoded.", "Die neoDraw-Pixelvorschau konnte nicht dekodiert werden."), status_code: 400); } $image_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/" . $cache_key_prefix . "-" . md5($image_source . "image/webp") . ".webp"; \NeoRename\NeoGlobal\fs_file_put_contents($image_file_path, $image_binary); $mime_type = "image/webp"; \NeoRename\NeoGlobal\global_log_with_module_name("neo-ai", "neoAI uses neoDraw SVG pixel variant for " . ($image_id ? "attachment " . $image_id : "file " . $image_source) . " with " . strlen($image_binary) . " bytes."); }
    $image_binary = \NeoRename\NeoGlobal\fs_file_get_contents($image_file_path);
    $image_size = getimagesizefromstring($image_binary);
    $mime_type = is_array($image_size) ? $image_size["mime"] : $mime_type;
    if (in_array($mime_type, ["image/jpeg", "image/png"], true)) { $image_data = ["dataUri" => "data:" . $mime_type . ";base64," . base64_encode($image_binary), "filePath" => $image_file_path, "mimeType" => $mime_type, "safeDataUri" => "data:" . $mime_type . ";base64," . base64_encode($image_binary), "safeBase64" => base64_encode($image_binary), "safeFilePath" => $image_file_path, "safeMimeType" => $mime_type, "bytes" => strlen($image_binary)]; }
    else { $safe_image_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/" . $cache_key_prefix . "-" . md5($image_file_path . $mime_type . \NeoRename\NeoGlobal\fs_filesize($image_file_path)) . ".png"; if (!\NeoRename\NeoGlobal\fs_file_exists($safe_image_file_path)) { $image_editor = wp_get_image_editor($image_file_path); if (is_wp_error($image_editor)) { \NeoRename\NeoGlobal\throw_global_exception($image_editor->get_error_message(), status_code: 500); } $saved_image = $image_editor->save($safe_image_file_path, "image/png"); if (is_wp_error($saved_image)) { \NeoRename\NeoGlobal\throw_global_exception($saved_image->get_error_message(), status_code: 500); } } $safe_image_binary = \NeoRename\NeoGlobal\fs_file_get_contents($safe_image_file_path); $image_data = ["dataUri" => "data:" . $mime_type . ";base64," . base64_encode($image_binary), "filePath" => $image_file_path, "mimeType" => $mime_type, "safeDataUri" => "data:image/png;base64," . base64_encode($safe_image_binary), "safeBase64" => base64_encode($safe_image_binary), "safeFilePath" => $safe_image_file_path, "safeMimeType" => "image/png", "bytes" => strlen($image_binary)]; }

    $remaining_requests = null;
    $connection = \NeoRename\NeoGlobal\option__neo_ai__connection();
    $provider = (string) ($connection["provider"] ?? "");
    if ($provider === "") { \NeoRename\NeoGlobal\global_warn_with_module_name("neo-ai", "neoAI request skipped because no AI provider is selected."); return null; }
    $model = ai_connection_model($connection);
    $openai_style_messages = [["role" => "user", "content" => [["type" => "text", "text" => $prompt], ["type" => "image_url", "image_url" => ["url" => $image_data["safeDataUri"]]]]]];
    if ($provider !== "neoai" && $provider !== "wordpress" && $provider !== "custom" && $connection["api_key"] === "") { \NeoRename\NeoGlobal\throw_global_exception("AI API key is missing.", status_code: 501, error_code: "ai-setup-missing"); }

    \NeoRename\NeoGlobal\fs_file_put_contents(\NeoRename\NeoGlobal\cache_path("neo-ai") . "/last-prompt.txt", (string) $prompt);
    \NeoRename\NeoGlobal\global_log_with_module_name("neo-ai", "neoAI request: provider=" . $provider . ", model=" . ($model ?: "none") . ", promptChars=" . strlen($prompt) . ", imageMime=" . $image_data["safeMimeType"] . ", imageBytes=" . ($image_data["bytes"] ?? 0) . ".");

    $max_output_tokens = 8000;
    $ai_curl_timeout = 120;
    if ($provider === "neoai")     { $response = free_provider_request("generate", ["prompt" => $prompt, "image-base64" => $image_data["safeBase64"], "image-mime-type" => $image_data["safeMimeType"], "image-url" => \NeoRename\NeoGlobal\fs_is_file($image_source) ? "" : $image_source]); $remaining_requests = isset($response["remaining_requests"]) ? max(0, $response["remaining_requests"]) : null; $reasoning_tokens = $response["usage"]["output_tokens_details"]["reasoning_tokens"] ?? 0; record_token_usage($usage_description, "neoai", $response["model"] ?? "unknown", $response["usage"]["input_tokens"], ($response["usage"]["output_tokens"] ?? 0) - $reasoning_tokens, $reasoning_tokens, remaining_requests: $remaining_requests); $text = trim($response["text"] ?? ""); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("neoAI did not return text. Please try again.", status_code: 500); }
    if ($provider === "openai")    { try { $response_json = \NeoRename\NeoGlobal\curl_request("https://api.openai.com/v1/responses", method: "POST", data: ["model" => $model, "input" => [["role" => "user", "content" => [["type" => "input_text", "text" => $prompt], ["type" => "input_image", "image_url" => $image_data["safeDataUri"]]]]], "reasoning" => ["effort" => "minimal"], "text" => ["verbosity" => "low"], "max_output_tokens" => $max_output_tokens], timeout: $ai_curl_timeout, headers: ["Authorization" => "Bearer " . $connection["api_key"]]); } catch (\NeoRename\NeoGlobal\CurlException $error) { $error_response = \NeoRename\NeoGlobal\json_decode_better($error->get_response_body(), suppress_error: true); if (is_array($error_response) && trim((string) ($error_response["error"]["message"] ?? "")) !== "") { \NeoRename\NeoGlobal\throw_global_exception(trim((string) $error_response["error"]["message"]), status_code: $error->get_status_code()); } throw $error; } $response = \NeoRename\NeoGlobal\json_decode_better($response_json); $reasoning_tokens = max(0, (int) ($response["usage"]["output_tokens_details"]["reasoning_tokens"] ?? 0)); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["input_tokens"] ?? 0), max(0, (int) ($response["usage"]["output_tokens"] ?? 0) - $reasoning_tokens), $reasoning_tokens); $text = trim((string) ($response["output_text"] ?? "")); if ($text === "") { foreach (($response["output"] ?? []) as $output_item) { if (($output_item["type"] ?? "") !== "message") { continue; } foreach (($output_item["content"] ?? []) as $content_item) { if (($content_item["type"] ?? "") === "output_text" && trim((string) ($content_item["text"] ?? "")) !== "") { $text = trim((string) $content_item["text"]); break 2; } } } } if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("OpenAI did not return text. Please try again.", "OpenAI hat keinen Text zurückgegeben. Bitte versuche es erneut."), status_code: 500); }
    if ($provider === "anthropic") { $response_json = \NeoRename\NeoGlobal\curl_request("https://api.anthropic.com/v1/messages", method: "POST", data: ["model" => $model, "max_tokens" => $max_output_tokens, "messages" => [["role" => "user", "content" => [["type" => "image", "source" => ["type" => "base64", "media_type" => $image_data["safeMimeType"], "data" => $image_data["safeBase64"]]], ["type" => "text", "text" => $prompt]]]]], timeout: $ai_curl_timeout, headers: ["x-api-key" => $connection["api_key"], "anthropic-version" => "2023-06-01"]); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["input_tokens"] ?? 0), (int) ($response["usage"]["output_tokens"] ?? 0)); $text = trim((string) ($response["content"][0]["text"] ?? "")); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("Anthropic did not return text.", status_code: 500); }
    if ($provider === "google")    { $response_json = \NeoRename\NeoGlobal\curl_request("https://generativelanguage.googleapis.com/v1beta/models/" . rawurlencode($model) . ":generateContent?key=" . rawurlencode($connection["api_key"]), method: "POST", data: ["contents" => [["parts" => [["text" => $prompt], ["inline_data" => ["mime_type" => $image_data["safeMimeType"], "data" => $image_data["safeBase64"]]]]]], "generationConfig" => ["maxOutputTokens" => $max_output_tokens]], timeout: $ai_curl_timeout); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usageMetadata"]["promptTokenCount"] ?? 0), (int) ($response["usageMetadata"]["candidatesTokenCount"] ?? 0), (int) ($response["usageMetadata"]["thoughtsTokenCount"] ?? 0)); $text = trim((string) ($response["candidates"][0]["content"]["parts"][0]["text"] ?? "")); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\global_log_with_module_name("neo-ai", "neoAI Google Gemini response without text: " . \NeoRename\NeoGlobal\json_encode_better(["promptFeedback" => $response["promptFeedback"] ?? null, "finishReason" => $response["candidates"][0]["finishReason"] ?? null, "safetyRatings" => $response["candidates"][0]["safetyRatings"] ?? null, "usageMetadata" => $response["usageMetadata"] ?? null, "candidateParts" => array_map(fn ($part) => array_keys((array) $part), $response["candidates"][0]["content"]["parts"] ?? [])])); \NeoRename\NeoGlobal\throw_global_exception("Google Gemini did not return text.", status_code: 500); }
    if ($provider === "xai")       { $response_json = \NeoRename\NeoGlobal\curl_request("https://api.x.ai/v1/chat/completions",  method: "POST", data: ["model" => $model, "messages" => $openai_style_messages, "max_tokens" => $max_output_tokens], timeout: $ai_curl_timeout, headers: ["Authorization" => "Bearer " . $connection["api_key"]]); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["prompt_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens_details"]["reasoning_tokens"] ?? 0)); $text = trim((string) ($response["choices"][0]["message"]["content"] ?? "")); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("xAI did not return text.", status_code: 500); }
    if ($provider === "mistral" && in_array($model, ["magistral-medium-2509", "magistral-medium-latest", "magistral-small-2509"], true)) { $mistral_messages = [["role" => "user", "content" => [["type" => "text", "text" => $prompt], ["type" => "image_url", "image_url" => $image_data["safeDataUri"]]]]]; $response_json = \NeoRename\NeoGlobal\curl_request("https://api.mistral.ai/v1/chat/completions", method: "POST", data: ["model" => $model, "messages" => $mistral_messages, "max_tokens" => $max_output_tokens], timeout: $ai_curl_timeout, headers: ["Authorization" => "Bearer " . $connection["api_key"]]); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["prompt_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens"] ?? 0)); $text = trim(implode("\n", array_map(fn ($content_item) => trim((string) ($content_item["text"] ?? "")), \NeoRename\NeoGlobal\array_filter_better((array) ($response["choices"][0]["message"]["content"] ?? []), fn ($content_item) => is_array($content_item) && ($content_item["type"] ?? "") === "text")))); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("Mistral did not return text.", status_code: 500); }
    if ($provider === "mistral")   { $mistral_messages = [["role" => "user", "content" => [["type" => "text", "text" => $prompt], ["type" => "image_url", "image_url" => $image_data["safeDataUri"]]]]]; $response_json = \NeoRename\NeoGlobal\curl_request("https://api.mistral.ai/v1/chat/completions", method: "POST", data: ["model" => $model, "messages" => $mistral_messages, "max_tokens" => $max_output_tokens], timeout: $ai_curl_timeout, headers: ["Authorization" => "Bearer " . $connection["api_key"]]); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["prompt_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens"] ?? 0)); $text = trim((string) ($response["choices"][0]["message"]["content"] ?? "")); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("Mistral did not return text.", status_code: 500); }
    if ($provider === "custom")    { $api_url = untrailingslashit($connection["api_url"]); if ($api_url === "") { \NeoRename\NeoGlobal\throw_global_exception("Custom AI API URL is missing.", status_code: 501, error_code: "ai-setup-missing"); } if (!str_ends_with($api_url, "/chat/completions")) { if (!str_ends_with($api_url, "/v1")) { $api_url .= "/v1"; } $api_url .= "/chat/completions"; } if ($model === "") { \NeoRename\NeoGlobal\throw_global_exception("Custom AI model is missing.", status_code: 501, error_code: "ai-setup-missing"); } $headers = $connection["api_key"] !== "" ? ["Authorization" => "Bearer " . $connection["api_key"]] : []; $response_json = \NeoRename\NeoGlobal\curl_request($api_url, method: "POST", data: ["model" => $model, "messages" => $openai_style_messages, "max_tokens" => $max_output_tokens], timeout: $ai_curl_timeout, headers: $headers); $response = \NeoRename\NeoGlobal\json_decode_better($response_json); record_token_usage($usage_description, $provider, $model, (int) ($response["usage"]["prompt_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens"] ?? 0), (int) ($response["usage"]["completion_tokens_details"]["reasoning_tokens"] ?? 0)); $text = trim((string) ($response["choices"][0]["message"]["content"] ?? "")); if ($text !== "") { return $text; } \NeoRename\NeoGlobal\throw_global_exception("Custom AI provider did not return text.", status_code: 500); }
    if ($provider === "wordpress") { if (!\function_exists("wp_ai_client_prompt")) { \NeoRename\NeoGlobal\global_warn_with_module_name("neo-ai", "neoAI WordPress Integration request skipped because no WordPress AI client is available."); return null; } $prompt_builder = \wp_ai_client_prompt()->with_text($prompt)->with_file($image_data["safeFilePath"], $image_data["safeMimeType"])->using_model_preference(...wp_ai_model_preferences()); if (method_exists($prompt_builder, "is_supported_for_text_generation") && !$prompt_builder->is_supported_for_text_generation()) { \NeoRename\NeoGlobal\throw_global_exception(\NeoRename\NeoGlobal\neo__("The configured WordPress AI provider does not support text generation with images.", "Der konfigurierte WordPress-AI-Provider unterstützt keine Textgenerierung mit Bildern."), status_code: 501, error_code: "ai-setup-missing"); } $result = $prompt_builder->generate_text_result(); if (is_wp_error($result)) { \NeoRename\NeoGlobal\throw_global_exception($result->get_error_message(), status_code: 500); } $text = $result->toText(); $token_usage = $result->getTokenUsage()->toArray(); $provider_metadata = $result->getProviderMetadata()->toArray(); $model_metadata = $result->getModelMetadata()->toArray(); $reasoning_tokens = max(0, (int) ($token_usage["thoughtTokens"] ?? $token_usage["reasoningTokens"] ?? $token_usage["reasoning_tokens"] ?? 0)); record_token_usage($usage_description, "wordpress:" . (string) ($provider_metadata["id"] ?? "unknown"), (string) ($model_metadata["id"] ?? "unknown"), (int) ($token_usage["promptTokens"] ?? $token_usage["inputTokens"] ?? $token_usage["input_tokens"] ?? 0), max(0, (int) ($token_usage["completionTokens"] ?? $token_usage["outputTokens"] ?? $token_usage["output_tokens"] ?? 0) - $reasoning_tokens), $reasoning_tokens); return $text; }
    \NeoRename\NeoGlobal\throw_global_exception("Unknown AI provider.");
}

function get_generated_text_language_prompt_instruction() {
    return \NeoRename\NeoGlobal\option__neo_ai__generated_text_language() === "auto" ? "- Use language from the website/page text context. Ignore the language of this prompt. If the page context is empty or its language cannot be identified reliably, use " . (class_exists("Locale") ? \Locale::getDisplayLanguage(get_locale(), "en") . " (" . get_locale() . ")" : get_locale()) . "." : "- Generate the result in the language \"" . \NeoRename\NeoGlobal\option__neo_ai__generated_text_language() . "\"";
}

\NeoRename\NeoGlobal\add_action_hook("neo_init", function () {
    $settings_render_callback = function () {
        require_once(ABSPATH . "wp-admin/includes/translation-install.php"); /* Usage of ABSPATH is OK here because there is no better alternative to include required WP files #suppressLinterWporgDirectoryConstantCheck */
        $translations = wp_get_available_translations();
        $ordered_locales = \NeoRename\NeoGlobal\array_unique_better(array_merge([get_locale(), "en_US"], array_keys($translations)));
        $language_options = [["locale" => "auto", "label" => \NeoRename\NeoGlobal\neo__("Automatic", "Automatisch")]];
        foreach ($ordered_locales as $locale) { if ($locale === "" || $locale === "auto") { continue; } $language_options[] = ["locale" => $locale, "label" => $locale === "en_US" ? "English (United States)" : ($translations[$locale]["native_name"] ?? $locale)]; }

        $provider_options = ai_provider_options();
        $connection = \NeoRename\NeoGlobal\option__neo_ai__connection();
        $selected_provider = $connection["provider"];
        $api_key_placeholder_value = ($connection["api_key"] ?? "") !== "" ? (string) ($provider_options[$connection["provider"]]["api_key_placeholder"] ?? "") : "";

        $usage_entries = compress_token_usage_list(\NeoRename\NeoGlobal\option__neo_ai__token_usage_list());
        $usage_summary = [];
        foreach ($usage_entries as $usage_entry) { $provider = $usage_entry["provider"] ?: "unknown"; $model = $usage_entry["model"] ?: "unknown"; $summary_key = $provider . "|" . $model; if (!isset($usage_summary[$summary_key])) { $usage_summary[$summary_key] = ["provider" => $provider, "model" => $model, "input_tokens" => 0, "output_tokens" => 0, "reasoning_tokens" => 0, "cost" => 0.0, "cost_known" => true]; } $usage_summary[$summary_key]["input_tokens"] += $usage_entry["input_tokens"]; $usage_summary[$summary_key]["output_tokens"] += $usage_entry["output_tokens"]; $usage_summary[$summary_key]["reasoning_tokens"] += $usage_entry["reasoning_tokens"]; $usage_cost = token_usage_cost($usage_entry); if ($usage_cost === null) { $usage_summary[$summary_key]["cost_known"] = false; } else { $usage_summary[$summary_key]["cost"] += $usage_cost; } }
        ksort($usage_summary);
        $format_tokens = function ($tokens) { return number_format_i18n((int) $tokens); };
        $format_cost = function ($cost, $cost_known, $provider, $remaining_requests = null) { if ($provider === "neoai") { return $remaining_requests !== null ? sprintf(\NeoRename\NeoGlobal\neo__("neoAI request (%s remaining)", "neoAI Anfrage (%s verbleibend)"), number_format_i18n($remaining_requests)) : \NeoRename\NeoGlobal\neo__("neoAI requests", "neoAI Anfragen"); } return $cost_known ? "$" . number_format($cost, 6, ".", ",") : "-"; };

        $last_prompt_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/last-prompt.txt";
        $last_prompt = \NeoRename\NeoGlobal\fs_file_exists($last_prompt_path) ? \NeoRename\NeoGlobal\fs_file_get_contents($last_prompt_path) : "";

        ?><neo-setting-neo-rename id="neo-ai--settings">
            <div slot="left">
                <h3><?php \NeoRename\NeoGlobal\echo_neo__("AI connection", "AI-Verbindung") ?></h3>
                <p>
                    <?php \NeoRename\NeoGlobal\echo_neo__("For automatic alt text and title generation, a connection to an AI provider is required. Please choose one. The connection is saved even when the test fails.", "Zum automatischen Generieren von Alt-Texten und Titeln ist eine Verbindung zu einer KI erforderlich. Wähle dazu den AI-Anbieter deiner Wahl. Die Verbindung wird auch gespeichert, wenn der Test fehlschlägt.") ?> 
                </p>
                <div class="neo-ai--settings-controls" data-connection="<?php echo esc_attr(\NeoRename\NeoGlobal\php_to_js_object(["provider" => $connection["provider"], "apiKey" => $api_key_placeholder_value, "model" => $connection["model"], "apiUrl" => $connection["api_url"], "customModelEnabled" => $connection["model"] !== null])) ?>" data-providers="<?php echo esc_attr(\NeoRename\NeoGlobal\php_to_js_object($provider_options)) ?>">
                    <div class="neo-ai--field neo-ai--provider-field">
                        <div class="neo-ai--field-heading">
                            <span><?php \NeoRename\NeoGlobal\echo_neo__("AI Provider", "AI-Anbieter") ?></span>
                            <label id="neo-ai--model-customize-control" title="<?php \NeoRename\NeoGlobal\echo_neo_attr__("Choose custom model", "Eigenes Modell wählen") ?>"<?php echo esc_attr($connection["provider"] === "" || $connection["provider"] === "neoai" || $connection["provider"] === "wordpress" || $connection["provider"] === "custom" ? " hidden" : "") ?>><input type="checkbox" id="neo-ai--model-customize-checkbox"<?php echo $connection["model"] !== null ? " checked" : "" ?>> <span><?php \NeoRename\NeoGlobal\echo_neo__("Custom model", "Eigenes Modell") ?></span></label>
                        </div>
                        <neo-select-neo-rename id="neo-ai--provider-select">
                            <option value=""><?php \NeoRename\NeoGlobal\echo_neo__("No provider selected", "Kein Anbieter ausgewählt") ?></option>
                            <?php foreach ($provider_options as $provider_id => $provider_option) { ?><option value="<?php echo esc_attr($provider_id) ?>"<?php echo $selected_provider === $provider_id ? " selected" : "" ?>><?php echo esc_html($provider_option["label"]) ?></option><?php } ?> 
                        </neo-select-neo-rename>
                    </div>
                    <label class="neo-ai--field neo-ai--api-key-field">
                        <div class="neo-ai--field-heading">
                            <span><?php \NeoRename\NeoGlobal\echo_neo__("API Key", "API-Key") ?></span>
                            <a class="neo-ai--api-key-guide-link" id="neo-ai--api-key-guide-link" href="<?php echo esc_url($connection["provider"] && !empty($provider_options[$connection["provider"]]["guide_url"]) ? $provider_options[$connection["provider"]]["guide_url"] : "#") ?>" target="_blank" rel="noopener noreferrer"<?php echo $connection["provider"] && !empty($provider_options[$connection["provider"]]["guide_url"]) ? "" : " hidden" ?>><span><?php echo esc_html($connection["provider"] && !empty($provider_options[$connection["provider"]]["guide_label"]) ? $provider_options[$connection["provider"]]["guide_label"] : \NeoRename\NeoGlobal\neo__("Guide: API Key", "Anleitung: API-Key")) ?></span><img src="<?php echo esc_url(\NeoRename\NeoGlobal\plugin_url()) ?>/_global-lucide-icons-thirdparty/external-link.svg" alt=""></a>
                        </div>
                        <input type="text" id="neo-ai--api-key-input" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" data-lpignore="true" data-1p-ignore="true" data-bwignore="true" value="<?php echo esc_attr($api_key_placeholder_value) ?>">
                    </label>
                    <label class="neo-ai--field neo-ai--api-url-field" hidden>
                        <span><?php \NeoRename\NeoGlobal\echo_neo__("API URL", "API-URL") ?></span>
                        <input type="url" id="neo-ai--api-url-input" value="<?php echo esc_attr($connection["api_url"]) ?>" placeholder="http://127.0.0.1:11434/v1">
                    </label>
                    <label class="neo-ai--field neo-ai--model-field" hidden>
                        <span><?php \NeoRename\NeoGlobal\echo_neo__("Model", "Modell") ?></span>
                        <input type="text" id="neo-ai--model-input" value="<?php echo esc_attr($connection["model"] ?? "") ?>" placeholder="llava:latest">
                    </label>
                    <div class="neo-ai--settings-buttons">
                        <neo-button-neo-rename id="neo-ai--test-button"><?php \NeoRename\NeoGlobal\echo_neo__("Save & test connection", "Speichern & Verbindung testen") ?></neo-button-neo-rename>
                    </div>
                    <div class="neo-ai--status-row">
                        <span id="neo-ai--status" data-has-connection="<?php echo esc_attr($connection["provider"] !== "" ? "1" : "0") ?>"></span>
                        <span id="neo-ai--free-provider-status"<?php echo esc_attr($connection["provider"] !== "neoai" ? " hidden" : "") ?>><a href="#" id="neo-ai--free-provider-status-load"><?php \NeoRename\NeoGlobal\echo_neo__("Retrieve remaining quota", "Verbleibendes Kontingent abrufen") ?></a></span>
                    </div>
                </div>
            </div>
        </neo-setting-neo-rename>
        <neo-setting-neo-rename>
            <div slot="left">
                <h3><?php \NeoRename\NeoGlobal\echo_neo__("Generated text language", "Sprache für generierte Texte") ?></h3>
                <p><?php \NeoRename\NeoGlobal\echo_neo__("Choose the language for generated alt texts and image titles.", "Wähle die Sprache für generierte Alt-Texte und Bildtitel.") ?></p>
            </div>
            <div slot="right">
                <div class="neo-ai--language-settings">
                    <neo-dropdown-button-neo-rename id="neo-ai--generated-text-language-dropdown" button-text="<?php \NeoRename\NeoGlobal\echo_neo_attr__("Save", "Speichern") ?>">
                        <?php foreach ($language_options as $language_option) { ?><option value="<?php echo esc_attr($language_option["locale"]) ?>"<?php echo \NeoRename\NeoGlobal\option__neo_ai__generated_text_language() === $language_option["locale"] ? " selected" : "" ?>><?php echo esc_html($language_option["label"]) ?></option><?php } ?> 
                    </neo-dropdown-button-neo-rename>
                </div>
            </div>
        </neo-setting-neo-rename>
        <neo-setting-neo-rename id="neo-ai--usage-settings">
            <div slot="left">
                <h3><?php \NeoRename\NeoGlobal\echo_neo__("Token usage", "Token-Verbrauch") ?></h3>
                <p><?php \NeoRename\NeoGlobal\echo_neo__("Estimated token usage and costs for neoAI requests.", "Geschätzter Token-Verbrauch und Kosten für neoAI-Anfragen.") ?></p>
            </div>
            <div class="neo-ai--usage-settings">
                <?php if (!$usage_summary) { ?><p class="neo-ai--muted"><?php \NeoRename\NeoGlobal\echo_neo__("No token usage logged yet.", "Noch kein Token-Verbrauch protokolliert.") ?></p><?php } else { ?> 
                    <div class="neo-ai--usage-table-wrap">
                        <table class="neo-ai--usage-table">
                            <thead><tr><th><?php \NeoRename\NeoGlobal\echo_neo__("Provider", "Anbieter") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Model", "Modell") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Input", "Input") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Output", "Output") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Reasoning", "Reasoning") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Cost", "Kosten") ?></th></tr></thead>
                            <tbody><?php foreach ($usage_summary as $summary_entry) { ?><tr><td><?php echo esc_html($summary_entry["provider"]) ?></td><td><?php echo esc_html($summary_entry["model"]) ?></td><td><?php echo esc_html($format_tokens($summary_entry["input_tokens"])) ?></td><td><?php echo esc_html($format_tokens($summary_entry["output_tokens"])) ?></td><td><?php echo esc_html($format_tokens($summary_entry["reasoning_tokens"])) ?></td><td><?php echo esc_html($format_cost($summary_entry["cost"], $summary_entry["cost_known"], $summary_entry["provider"])) ?></td></tr><?php } ?></tbody>
                        </table>
                    </div>
                    <p class="neo-ai--usage-note"><?php \NeoRename\NeoGlobal\echo_neo__("Prices approximate.", "Preise ungefähr.") ?></p>
                    <details class="neo-ai--usage-details">
                        <summary><?php \NeoRename\NeoGlobal\echo_neo__("Show details", "Details anzeigen") ?></summary>
                        <div class="neo-ai--usage-table-wrap">
                            <table class="neo-ai--usage-table neo-ai--usage-detail-table">
                                <thead><tr><th><?php \NeoRename\NeoGlobal\echo_neo__("Date", "Datum") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Description", "Beschreibung") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Provider", "Anbieter") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Model", "Modell") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Input", "Input") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Output", "Output") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Reasoning", "Reasoning") ?></th><th><?php \NeoRename\NeoGlobal\echo_neo__("Cost", "Kosten") ?></th></tr></thead>
                                <tbody><?php foreach (array_reverse($usage_entries) as $usage_entry) { ?><tr<?php echo $usage_entry["compressed"] ? " class=\"neo-ai--usage-compressed-row\"" : "" ?>><td><?php echo esc_html($usage_entry["date"]) ?></td><td><?php echo esc_html($usage_entry["description"]) ?></td><td><?php echo esc_html($usage_entry["provider"]) ?></td><td><?php echo esc_html($usage_entry["model"]) ?></td><td><?php echo esc_html($format_tokens($usage_entry["input_tokens"])) ?></td><td><?php echo esc_html($format_tokens($usage_entry["output_tokens"])) ?></td><td><?php echo esc_html($format_tokens($usage_entry["reasoning_tokens"])) ?></td><td><?php echo esc_html($format_cost(token_usage_cost($usage_entry) ?? 0.0, token_usage_cost($usage_entry) !== null, $usage_entry["provider"], $usage_entry["remaining_requests"])) ?></td></tr><?php } ?></tbody>
                            </table>
                        </div>
                    </details>
                <?php } ?>
                <neo-button-neo-rename id="neo-ai--usage-reset-button"><?php \NeoRename\NeoGlobal\echo_neo__("Reset usage", "Verbrauch zurücksetzen") ?></neo-button-neo-rename>
            </div>
        </neo-setting-neo-rename>
        <neo-setting-neo-rename id="neo-ai--last-prompt-settings">
            <div slot="left">
                <h3><?php \NeoRename\NeoGlobal\echo_neo__("View last prompt", "Letzten Prompt anzeigen") ?></h3>
                <p><?php \NeoRename\NeoGlobal\echo_neo__("See the last prompt that was sent to your AI provider by neoAI.", "Sieh dir den letzten Prompt an, der von neoAI an deinen AI-Provider gesendet wurde.") ?></p>
            </div>
            <div slot="right">
                <div class="neo-ai--last-prompt-settings">
                    <neo-button-neo-rename id="neo-ai--last-prompt-toggle-button"><?php \NeoRename\NeoGlobal\echo_neo__("Show prompt", "Prompt anzeigen") ?></neo-button-neo-rename>
                </div>
            </div>
            <pre id="neo-ai--last-prompt-content" hidden><?php echo esc_html($last_prompt !== "" ? $last_prompt : \NeoRename\NeoGlobal\neo__("No prompt stored yet.", "Noch kein Prompt gespeichert.")) ?></pre>
        </neo-setting-neo-rename><?php
    };
    \NeoRename\NeoGlobal\call_interface_func_implemented('\NeoRename\NeoSettings\interface_add_neo_setting_20260326')("neo-ai", $settings_render_callback);
    [$neo_ai_settings_section_url, $interface_ok] = \NeoRename\NeoGlobal\call_interface_func_implemented('\NeoRename\NeoSettings\interface_get_neo_settings_section_url_20260613')("neo-ai"); if (!$interface_ok) { $neo_ai_settings_section_url = ""; }
    \NeoRename\NeoGlobal\enqueue_js_variable_backend("neoAiSettingsSectionUrl", $neo_ai_settings_section_url);
    $enqueue_assets_callback = function () { \NeoRename\NeoGlobal\enqueue_js("neo-ai.js"); \NeoRename\NeoGlobal\enqueue_css("neo-ai.css"); };
    \NeoRename\NeoGlobal\call_interface_func_implemented('\NeoRename\NeoSettings\interface_add_neo_settings_asset_loading_callback_20250918')($enqueue_assets_callback);
});

\NeoRename\NeoGlobal\add_action_hook("neo_init", function () {
    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-token-usage-reset", "POST", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai__settings(), function () {
        \NeoRename\NeoGlobal\option__neo_ai__token_usage_list([]);
        $last_prompt_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/last-prompt.txt";
        if (\NeoRename\NeoGlobal\fs_file_exists($last_prompt_path)) { \NeoRename\NeoGlobal\fs_unlink($last_prompt_path); }
        return ["ok" => true];
    });

    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-free-provider-status", "GET", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai(), function () {
        return \NeoRename\NeoGlobal\synclock_dir(\NeoRename\NeoGlobal\cache_path("neo-ai"), timeout: 25, callback: function () {
            $status_cache_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/free-provider-status.json";
            if (\NeoRename\NeoGlobal\fs_file_exists($status_cache_file_path)) {
                $status_cache_is_current = !\NeoRename\NeoGlobal\is_cache_file_outdated($status_cache_file_path, 10 * 60) && \NeoRename\NeoGlobal\utc_date_string("Y-m", \NeoRename\NeoGlobal\fs_filemtime($status_cache_file_path)) === \NeoRename\NeoGlobal\utc_date_string("Y-m");
                $cached_response = $status_cache_is_current ? \NeoRename\NeoGlobal\json_decode_better(\NeoRename\NeoGlobal\fs_file_get_contents($status_cache_file_path), suppress_error: true) : false;
                if (is_array($cached_response) && isset($cached_response["remaining_requests"])) { return $cached_response; }
                \NeoRename\NeoGlobal\fs_unlink_ignore_warnings($status_cache_file_path);
            }
            $response = free_provider_request("status");
            \NeoRename\NeoGlobal\fs_write_json_file($status_cache_file_path, $response);
            return $response;
        }, scope: "free-provider-status");
    });

    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-image-usage-context-targets", "GET", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai(), function ($get_param) {
        $image_url = \NeoRename\NeoGlobal\remove_all_query_params($get_param("image-url"));
        if ($image_url === "") { \NeoRename\NeoGlobal\throw_global_exception("Image URL is required.", status_code: 400); }
        $usage_lookup = \NeoRename\NeoGlobal\db_entries_image_usage_lookup([$image_url], 6);
        if ($usage_lookup === false) { return ["timeout" => true, "targets" => []]; }
        $targets = []; $seen_urls = [];
        foreach (($usage_lookup[$image_url] ?? []) as $entry) { if (empty($entry["postUrl"]) || isset($seen_urls[$entry["postUrl"]])) { continue; } $seen_urls[$entry["postUrl"]] = true; $targets[] = ["url" => $entry["postUrl"], "postId" => $entry["postId"] ?? null, "title" => $entry["postTitle"] ?? ""]; }
        return ["timeout" => false, "targets" => $targets];
    });

    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-generate-image-text", "POST", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai(), function ($get_param) {
        try {
            $image_url = \NeoRename\NeoGlobal\remove_all_query_params($get_param("image-url"));
            if ($image_url === "") { \NeoRename\NeoGlobal\throw_global_exception("Image URL is required.", status_code: 400); }
            $text_type = $get_param("text-type");
            if (!in_array($text_type, ["title", "alt"], true)) { \NeoRename\NeoGlobal\throw_global_exception("Image text type must be title or alt.", status_code: 400); }
            $image_id = attachment_url_to_postid($image_url);
            $image_title = is_string($get_param("image-title")) ? $get_param("image-title") : ($image_id > 0 ? get_post_field("post_title", $image_id, context: "raw") : "");
            $image_title = trim(wp_strip_all_tags($image_title));
            $context_snippets = array_slice(array_values(\NeoRename\NeoGlobal\array_filter_better($get_param("context-snippets") ?: [], fn ($snippet) => is_string($snippet) && trim($snippet) !== "")), 0, 12);
            $context_text = implode("\n\n---\n\n", $context_snippets);
            $previous_generations = array_values(\NeoRename\NeoGlobal\array_filter_better($get_param("previous-generations") ?: [], fn ($text) => is_string($text) && trim($text) !== ""));
            $previous_generations_text = implode("\n", array_map(fn ($text) => "  - " . trim(wp_strip_all_tags($text)), $previous_generations));
            $previous_generations_prompt = $previous_generations_text ? "\n\nDo not return any of the previously rejected image " . ($text_type === "title" ? "titles" : "alt texts") . " below, because the user explicitly rejected them. Generate something different.\n" . $previous_generations_text : "";
            if ($text_type === "title") {
                $nearby_titles = array_slice(array_values(\NeoRename\NeoGlobal\array_filter_better($get_param("nearby-titles") ?: [], fn ($title) => is_string($title) && trim($title) !== "")), 0, 10);
                $nearby_titles_text = implode("\n", array_map(fn ($title) => "- " . trim(wp_strip_all_tags($title)), $nearby_titles));
                $prompt = "Write one SEO-oriented image title for the provided image.\n- Return plain text only\n- Do not include a file extension\n- Do not include slashes, Markdown, quotes or a label\n- Capitalized and with spaces\n- Note that the image is used at the place [[THE IMAGE IS HERE]] (if it exists)\n" . get_generated_text_language_prompt_instruction() . "\n" . $previous_generations_prompt . "\n\nImage URL:\n" . $image_url . "\n\nCurrent image title:\n" . ($image_title ?: "(No current image title available.)") . "\n\nNearby media titles for length, rough context and for avoiding duplicates:\n" . ($nearby_titles_text ?: "(No nearby media titles available.)") . "\n\nWebsite/page text context:\n" . ($context_text ?: "(No page text context available.)");
            } else {
                $prompt = "Write one SEO-oriented image alt text in progressively shorter variants for the provided image. Keep the same essential meaning in every variant.\nReturn exactly 13 lines in the format #0 Text through #12 Text and output nothing else. Do not use JSON, Markdown, code fences, introductory text or a concluding sentence.\nRules for #0: Ideal length of 80 to 125 characters.\nRules for #1: Maximum 125 characters.\nRules for #2: Maximum 115 characters.\nRules for #3: Maximum 105 characters.\nRules for #4: Maximum 95 characters.\nRules for #5: Maximum 85 characters.\nRules for #6: Maximum 75 characters.\nRules for #7: Maximum 65 characters.\nRules for #8: Maximum 55 characters.\nRules for #9: Maximum 50 characters.\nRules for #10: Maximum 45 characters.\nRules for #11: Maximum 40 characters.\nRules for #12: Maximum 35 characters.\nRules for all variants:\n- Use page context, current image title and visible image content\n" . get_generated_text_language_prompt_instruction() . "\n- Use keywords relevant to the image and page content\n- Use the image title only for inspiration; if no content is available give the image content a higher weight.\n- Note that the image is used at the place [[THE IMAGE IS HERE]] (if it exists)\n- Each line must contain its #number followed by one plain-text alt-text suggestion\n- Output only the 13 requested lines" . $previous_generations_prompt . "\n\nImage URL:\n" . $image_url . "\n\nCurrent image title:\n" . ($image_title ?: "(No current image title available.)") . "\n\nWebsite/page text context:\n" . ($context_text ?: "(No page text context available.)");
            }
            if ((\NeoRename\NeoGlobal\option__neo_ai__connection()["provider"] ?? "") === "neoai" && !$get_param("free-provider-confirmed")) { return ["confirmationRequired" => true, "prompt" => $prompt, "imageUrl" => $image_url]; }
            $remaining_requests = null;
            $generated_text = generate_text_with_image($image_url, $prompt, "image-" . $text_type, sprintf(\NeoRename\NeoGlobal\neo__("neoAI image %s generated ", "neoAI Bild-%s generiert "), $text_type) . \NeoRename\NeoGlobal\make_internal_url_relative_to_uploads($image_url), remaining_requests: $remaining_requests);
            if ($generated_text === null) { return ["ok" => false, "code" => "ai-setup-missing", "warning" => \NeoRename\NeoGlobal\neo__("No AI connection available. Add an OpenAI API key or configure a WordPress AI provider.", "Keine AI-Verbindung verfügbar. Hinterlege einen OpenAI API-Key oder konfiguriere einen WordPress-AI-Provider.")]; }
            if ($generated_text === "") { \NeoRename\NeoGlobal\throw_global_exception($text_type === "title" ? \NeoRename\NeoGlobal\neo__("AI returned an empty title.", "Die AI hat einen leeren Titel zurückgegeben.") : \NeoRename\NeoGlobal\neo__("AI returned an empty alt text.", "Die AI hat einen leeren Alt-Text zurückgegeben."), status_code: 500); }
            if ($text_type === "alt") {
                $generated_text = \NeoRename\NeoGlobal\preg_replace_better("/```[a-zA-Z0-9_-]*/", "", $generated_text);
                $generated_alt_texts_sorted = [];
                foreach (\NeoRename\NeoGlobal\preg_split_better("/\R/u", $generated_text) as $generated_alt_text_line) { if (\NeoRename\NeoGlobal\preg_match_better("/^\s*#(?:[0-9]|1[0-2])\s*:?\s+(.+?)\s*$/u", $generated_alt_text_line, $generated_alt_text_match)) { $generated_alt_texts_sorted[] = $generated_alt_text_match[1]; } }
                if ($generated_alt_texts_sorted === []) { \NeoRename\NeoGlobal\throw_global_exception("AI returned no valid alt text variants.", status_code: 500); }
                usort($generated_alt_texts_sorted, function ($a, $b) { return mb_strlen($b, "UTF-8") <=> mb_strlen($a, "UTF-8"); });
                $generated_text = null;
                foreach ($generated_alt_texts_sorted as $generated_alt_text) { if (mb_strlen($generated_alt_text, "UTF-8") <= 125) { $generated_text = $generated_alt_text; break; } }
                if ($generated_text === null) { \NeoRename\NeoGlobal\throw_global_exception("AI returned no alt text variant within 125 characters.", status_code: 500); }
            }
            $endpoint_response = ["text" => $generated_text];
            if ($remaining_requests !== null) { $endpoint_response["remaining_requests"] = $remaining_requests; }
            return $endpoint_response;
        } catch (\NeoRename\NeoGlobal\GlobalException $error) { if ($error->get_error_code() !== "ai-setup-missing") { throw $error; } \NeoRename\NeoGlobal\global_warn_with_module_name("neo-ai", "neoAI image text request without available AI connection: " . $error->get_original_message()); return ["ok" => false, "code" => $error->get_error_code(), "warning" => $error->get_original_message()]; }
    });
    [$settings_url, $interface_ok] = \NeoRename\NeoGlobal\call_interface_func_implemented('\NeoRename\NeoSettings\interface_get_neo_settings_section_url_20260613')("neo-ai"); if (!$interface_ok) { $settings_url = "#"; }

    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-connection-save-and-test", "POST", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai__settings(), function ($get_param) use ($settings_url) {
        $current_connection = \NeoRename\NeoGlobal\option__neo_ai__connection();
        $provider_options = ai_provider_options();
        $provider = (string) $get_param("provider");
        if (!isset($provider_options[$provider])) { $provider = ""; }
        $api_key = (string) $get_param("api-key");
        $api_key_placeholder = (string) ($provider_options[$provider]["api_key_placeholder"] ?? "");
        if ($api_key_placeholder !== "" && $api_key === $api_key_placeholder && ($current_connection["provider"] ?? "") === $provider) { $api_key = (string) ($current_connection["api_key"] ?? ""); }
        if ($api_key_placeholder !== "" && $api_key === $api_key_placeholder) { $api_key = ""; }
        $model = (string) $get_param("model");
        $model = $model !== "" ? $model : null;
        $connection = ["provider" => $provider, "api_key" => in_array($provider, ["", "neoai", "wordpress"], true) ? "" : $api_key, "model" => in_array($provider, ["", "neoai"], true) ? null : $model, "api_url" => $provider === "" ? "" : $get_param("api-url")];
        \NeoRename\NeoGlobal\option__neo_ai__connection($connection);
        if ($connection["provider"] === "") { return ["ok" => true, "saved" => true, "removed" => true, "provider" => "", "model" => ""]; }
        try { $test_image_file_path = \NeoRename\NeoGlobal\cache_path("neo-ai") . "/connection-test.png"; if (!\NeoRename\NeoGlobal\fs_file_exists($test_image_file_path)) { if (!\function_exists("imagecreatetruecolor")) { \NeoRename\NeoGlobal\throw_global_exception("PHP GD is required to create the neoAI connection test image."); } $image = imagecreatetruecolor(50, 50); $white = imagecolorallocate($image, 255, 255, 255); $red = imagecolorallocate($image, 222, 58, 58); $green = imagecolorallocate($image, 57, 168, 94); $blue = imagecolorallocate($image, 55, 118, 210); $yellow = imagecolorallocate($image, 245, 196, 66); imagefilledrectangle($image, 0, 0, 49, 49, $white); imagefilledrectangle($image, 4, 4, 22, 22, $red); imagefilledrectangle($image, 27, 4, 45, 22, $green); imagefilledrectangle($image, 4, 27, 22, 45, $blue); imagefilledrectangle($image, 27, 27, 45, 45, $yellow); imagepng($image, $test_image_file_path); } $test_text = generate_text_with_image($test_image_file_path, "This is a small colorful test image. Briefly describe it and include the word OK.", "connection-test", \NeoRename\NeoGlobal\neo__("neoAI connection tested", "neoAI Verbindung getestet"), "image/png"); return ["ok" => $test_text !== "", "saved" => true, "provider" => $connection["provider"], "model" => ai_connection_model($connection), "message" => $test_text]; }
        catch (\NeoRename\NeoGlobal\GlobalException $error) { if ($error->get_error_code() !== "ai-setup-missing") { throw $error; } \NeoRename\NeoGlobal\global_warn_with_module_name("neo-ai", "neoAI connection setup failed in settings " . $settings_url . ": " . $error->getMessage()); return ["ok" => false, "saved" => true, "code" => $error->get_error_code(), "provider" => $connection["provider"], "model" => ai_connection_model($connection), "warning" => $error->get_original_message()]; }
        catch (\Throwable $error) { \NeoRename\NeoGlobal\global_warn_with_module_name("neo-ai", "neoAI connection test failed in settings " . $settings_url . ": " . $error->getMessage()); return ["ok" => false, "saved" => true, "provider" => $connection["provider"], "model" => ai_connection_model($connection), "warning" => $error->getMessage()]; }
    });

    \NeoRename\NeoGlobal\register_rest_endpoint("/wp-json/neo/ai-generated-text-language-save", "POST", fn () => \NeoRename\NeoGlobal\current_user_can__neo_ai__settings(), function ($get_param) {
        $locale = trim((string) $get_param("language"));
        \NeoRename\NeoGlobal\option__neo_ai__generated_text_language($locale);
        return ["language" => $locale];
    });
});
