<?php
// Service/Features/ImageProcessor.php
// 该文件主要负责提供图片处理相关的功能，例如 WebP 转换。

namespace HaoZiTeam\AIPost\Service\Features;

use PicturesUtil\Article_With_Pictures_Util;

defined('ABSPATH') || exit;

use HaoZiTeam\AIPost\Service\Features\NumberUtils; // Add this if not already present
use HaoZiTeam\AIPost\Service\Features\Logger;

class ImageProcessor
{
    /**
     * 解析插件内置 CA 证书路径（resources/cacert.pem）。
     */
    private static function resolve_cacert_path(): ?string
    {
        $candidate = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'cacert.pem';
        if (@file_exists($candidate)) {
            return $candidate;
        }
        return null;
    }

    /**
     * 解析方式二标题图所需字体的真实文件路径（最小侵入，保持向后兼容）。
     * 优先级：
     * 1) 显式传入路径（$selected 原值，如为绝对/相对存在则直接使用）
     * 2) 插件内 fonts 目录
     * 3) 上传目录 wp-content/uploads/aipost-fonts 及其子目录
     * 4) 系统字体目录（Windows/Linux/macOS 常见路径）
     * 5) 若仍未找到，返回空字符串并交由上层处理（沿用旧逻辑的告警与回退）
     */
    private static function resolve_font_path(string $selected, string $plugin_file_path): string
    {
        $selected = trim($selected);
        $plugin_base = rtrim($plugin_file_path, "\\/\t\n\r ");

        // 构建候选文件名列表：优先 $selected，其次常见 CJK/多语字体文件名（子集化优先）
        $common_font_files = [
            $selected,
            // 思源 / Noto / 常见 CJK
            'NotoSansSC-Regular.otf', 'NotoSansSC-Regular.ttf',
            'NotoSansCJKsc-Regular.otf', 'NotoSansCJKsc-Regular.ttf',
            'SourceHanSansCN-Regular.otf', 'SourceHanSansCN-Regular.ttf',
            'NotoSansTC-Regular.otf', 'NotoSansTC-Regular.ttf',
            'NotoSansJP-Regular.otf', 'NotoSansJP-Regular.ttf',
            'NotoSansKR-Regular.otf', 'NotoSansKR-Regular.ttf',
            // 拉丁扩展/通用
            'DejaVuSans.ttf', 'DejaVuSansCondensed.ttf',
            'ArialUnicodeMS.ttf', 'ArialUnicode.ttf',
            // 兼容旧版本内置中文字体名
            'WeiRuanYaHei.ttf', 'HanyiYAaKU.ttf', 'JingDongLangZheng.ttf',
        ];

        // 目录集合：插件 fonts、uploads 字体目录、系统字体目录
        $dirs = [];
        // 2) 插件 fonts
        $dirs[] = $plugin_base . DIRECTORY_SEPARATOR . 'fonts';
        // 3) uploads/aipost-fonts（管理员可按需放置字体包）
        $upload_dir = function_exists('wp_upload_dir') ? wp_upload_dir() : null;
        if (is_array($upload_dir) && !empty($upload_dir['basedir'])) {
            $dirs[] = rtrim($upload_dir['basedir'], "\\/") . DIRECTORY_SEPARATOR . 'aipost-fonts';
        }
        // 4) 系统字体目录
        if (stripos(PHP_OS, 'WIN') === 0) {
            $dirs[] = 'C:\\Windows\\Fonts';
        } else {
            // Linux & macOS 常见目录
            $dirs[] = '/usr/share/fonts';
            $dirs[] = '/usr/local/share/fonts';
            $dirs[] = '/Library/Fonts';
            $dirs[] = '/System/Library/Fonts';
            // 用户级字体目录（若 PHP 有权限可用）
            $home = getenv('HOME');
            if ($home) { $dirs[] = rtrim($home, "\\/") . '/Library/Fonts'; }
        }

        // 1) 显式传入路径优先（可能是绝对/相对路径）
        if ($selected !== '') {
            if (@file_exists($selected)) {
                Logger::debug('Font: using explicit path: ' . $selected, 'TitleImage');
                return $selected;
            }
            // 同名文件在插件 fonts 目录（保持与旧逻辑兼容）
            $candidate = $plugin_base . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . $selected;
            if (@file_exists($candidate)) {
                Logger::debug('Font: found in plugin fonts: ' . $candidate, 'TitleImage');
                return $candidate;
            }
        }

        // 在各目录中按常见文件名顺序查找
        foreach ($dirs as $dir) {
            if (!is_string($dir) || $dir === '' || !@is_dir($dir)) { continue; }
            foreach ($common_font_files as $fname) {
                if ($fname === '') { continue; }
                $path = rtrim($dir, "\\/") . DIRECTORY_SEPARATOR . $fname;
                if (@file_exists($path)) {
                    Logger::debug('Font: found: ' . $path, 'TitleImage');
                    return $path;
                }
            }
        }

        // 没找到：返回空串，由上层打印统一警告并继续其它回退色板逻辑
        return '';
    }

    /**
     * 在本地图片文件上叠加标题文字（用于避免由 AI 在图中生成文字）。
     * - 复用配图方式二的文字渲染逻辑，确保显示效果一致
     * - 会直接修改传入的 $file_path 所指向的文件内容。
     * - 从已有设置中读取基础样式（若不存在则回退默认）。
     *
     * @param string $file_path       已侧载到本地的图片绝对路径
     * @param string $title           需要叠加的标题
     * @param array  $task_settings   任务级设置（读取字体/字号/颜色/位置等）
     * @param string $plugin_file_path 插件主文件路径，用于定位 fonts 目录
     * @return bool 成功与否
     */
    public static function overlay_title_on_file(string $file_path, string $title, array $task_settings, string $plugin_file_path): bool
    {
        $title = trim((string)$title);
        if ($title === '' || !@file_exists($file_path) || !@is_writable($file_path)) {
            Logger::warning('TitleOverlay: invalid input or file not writable: ' . $file_path, 'TitleImage');
            return false;
        }

        try {
            // 复用配图方式二的逻辑，使用 Article_With_Pictures_Util 类
            if (!class_exists('PicturesUtil\\Article_With_Pictures_Util')) {
                Logger::error('TitleOverlay: Article_With_Pictures_Util class not found', 'TitleImage');
                return false;
            }

            // 读取原图片信息
            $info = @getimagesize($file_path);
            if (!$info) {
                Logger::warning('TitleOverlay: getimagesize failed: ' . $file_path, 'TitleImage');
                return false;
            }

            $original_width = $info[0];
            $original_height = $info[1];
            $mime = $info['mime'] ?? '';

            // 创建 Article_With_Pictures_Util 实例，复用配图方式二的参数
            $api = new \PicturesUtil\Article_With_Pictures_Util($original_width, $original_height);
            
            // 设置字体相关参数（复用配图方式二的设置键名）
            $font_selection = $task_settings['font-selection'] ?? 'WeiRuanYaHei.ttf';
            $font_size = (int)($task_settings['post-image-font-size'] ?? 30);
            $font_color = $task_settings['post-image-font-color'] ?? '#ffffff';

            // 兼容性增强：解析字体的真实路径（优先插件 fonts 目录）
            $resolved_font = '';
            try {
                $plugin_base = rtrim($plugin_file_path, "\\/\t\n\r ");
                $candidates = [];
                if (!empty($font_selection)) {
                    // 若传入已是绝对/相对路径，也尝试原样
                    $candidates[] = $font_selection;
                    $candidates[] = $plugin_base . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . $font_selection;
                }
                // 回退候选：常见中文字体文件（插件自带）
                $candidates[] = $plugin_base . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . 'WeiRuanYaHei.ttf';
                $candidates[] = $plugin_base . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . 'HanyiYAaKU.ttf';
                $candidates[] = $plugin_base . DIRECTORY_SEPARATOR . 'fonts' . DIRECTORY_SEPARATOR . 'JingDongLangZheng.ttf';

                foreach ($candidates as $cand) {
                    if (is_string($cand) && @file_exists($cand)) { $resolved_font = $cand; break; }
                }
                if ($resolved_font) {
                    $api->setFontFile($resolved_font);
                } else {
                    Logger::warning('TitleOverlay: font file not found. selection=' . (string)$font_selection, 'TitleImage');
                }
            } catch (\Throwable $e) {
                Logger::error('TitleOverlay: font resolution exception: ' . $e->getMessage(), 'TitleImage');
            }
            $api->setFontSize($font_size);
            $api->setIsMultiLine(true);
            
            // 设置文字颜色
            $text_rgb = $api->getRGB(trim($font_color));
            if (!empty($text_rgb)) {
                $api->setTextRGB($text_rgb);
            }

            // 读取原图作为背景
            $bg_image_data = file_get_contents($file_path);
            if ($bg_image_data !== false) {
                $bg_image = @imagecreatefromstring($bg_image_data);
                if ($bg_image) {
                    $api->setBackgroundImg($bg_image);
                    Logger::debug('TitleOverlay: loaded background image: ' . $original_width . 'x' . $original_height, 'TitleImage');
                } else {
                    Logger::error('TitleOverlay: failed to create image from background data', 'TitleImage');
                    return false;
                }
            } else {
                Logger::error('TitleOverlay: failed to read background image data', 'TitleImage');
                return false;
            }

            // 设置文字内容
            $api->setText($title);
            
            // 生成带标题的图片，直接保存到原文件路径
            $result = $api->saveImage($file_path);
            
            if (empty($result) || !file_exists($file_path)) {
                Logger::error('TitleOverlay: failed to save overlay image', 'TitleImage');
                return false;
            }
            
            Logger::debug('TitleOverlay: success: ' . $file_path, 'TitleImage');
            return true;

        } catch (Exception $e) {
            Logger::error('TitleOverlay: exception: ' . $e->getMessage(), 'TitleImage');
            return false;
        }
    }

    /**
     * 稳健下载远程文件到临时路径。
     * 优先 download_url；失败则挂载 http_api_curl 设置 CA 并用 wp_remote_get 重试。
     * 成功返回临时文件完整路径；失败返回 WP_Error。
     */
    private static function download_to_temp(string $url, int $timeout = 60)
    {
        if (!function_exists('download_url')) {
            if (defined('ABSPATH')) {
                require_once ABSPATH . 'wp-admin/includes/file.php';
            }
        }

        // 1) 首选 WordPress 自带的下载方法（会跟随重定向）
        $tmp = \download_url($url, $timeout);
        if (!is_wp_error($tmp)) {
            return $tmp;
        }

        // 记录首次失败
        Logger::debug('ImageDownload: download_url failed: ' . $tmp->get_error_message(), 'ImageSideload');

        // 2) 带自定义 CA 的 wp_remote_get 重试
        $ca = self::resolve_cacert_path();
        $filter = null;
        if ($ca) {
            $filter = function ($handle) use ($ca) {
                if (is_resource($handle)) {
                    @curl_setopt($handle, CURLOPT_CAINFO, $ca);
                }
            };
            \add_action('http_api_curl', $filter, 10, 1);
        }

        $args = [
            'timeout'      => $timeout,
            'redirection'  => 5,
            'sslverify'    => true,
            'headers'      => [
                'User-Agent' => 'AI-Post/Downloader (+https://wp-aipost.com)',
                'Referer'    => home_url('/'),
            ],
        ];

        // 重试机制：最多 3 次
        $resp = null;
        $attempts = 3;
        for ($i = 1; $i <= $attempts; $i++) {
            $resp = \wp_remote_get($url, $args);
            if (is_wp_error($resp)) {
                Logger::debug('ImageDownload: wp_remote_get error (attempt ' . $i . '/' . $attempts . '): ' . $resp->get_error_message(), 'ImageSideload');
                // SSL 相关错误通常包含 cURL 60/77 等信息，此处直接重试
            } else {
                $code = (int) \wp_remote_retrieve_response_code($resp);
                $ct   = (string) \wp_remote_retrieve_header($resp, 'content-type');
                $cl   = (string) \wp_remote_retrieve_header($resp, 'content-length');
                Logger::debug('ImageDownload: http status=' . $code . ', ct=' . $ct . ', cl=' . ($cl !== '' ? $cl : 'unknown') . ' (attempt ' . $i . '/' . $attempts . ')', 'ImageSideload');

                if ($code >= 200 && $code < 300) {
                    $body = \wp_remote_retrieve_body($resp);
                    // 体积过小且非明显图片类型时视为失败，避免把错误页当作图片
                    $is_likely_image = (stripos($ct, 'image/') === 0) || $ct === '' /* 某些源站返回空 CT */;
                    if (is_string($body) && $body !== '' && (strlen($body) > 100 || $is_likely_image)) {
                        break; // 认为成功
                    } else {
                        $resp = new \WP_Error('suspicious_body', 'Suspicious or too small body');
                        Logger::debug('ImageDownload: body suspicious/too small (len=' . (is_string($body) ? strlen($body) : -1) . ', ct=' . $ct . ')', 'ImageSideload');
                    }
                } else {
                    $resp = new \WP_Error('http_status', 'Bad HTTP status: ' . $code);
                }
            }

            if ($i < $attempts) {
                // 退避等待：0.3s, 0.6s
                usleep(300000 * $i);
            }
        }

        if ($ca && $filter) {
            \remove_action('http_api_curl', $filter, 10);
        }

        if (is_wp_error($resp)) {
            return $resp;
        }

        $body = \wp_remote_retrieve_body($resp);
        if (!is_string($body) || $body === '') {
            return new \WP_Error('empty_body', 'Empty body');
        }

        if (!function_exists('wp_tempnam')) {
            if (defined('ABSPATH')) {
                require_once ABSPATH . 'wp-admin/includes/file.php';
            }
        }
        $temp_file = \wp_tempnam('ai-post-img');
        if (!$temp_file) {
            return new \WP_Error('temp_create_failed', 'Failed to create temp file');
        }
        $bytes = @file_put_contents($temp_file, $body);
        if ($bytes === false) {
            return new \WP_Error('temp_write_failed', 'Failed to write temp file');
        }
        return $temp_file;
    }

    /**
     * 标准化：下载远程图片 -> 插入媒体库并绑定父文章 -> 可选设为特色图。
     *
     * @param int    $post_id            父文章 ID（>0 时绑定父子关系）。
     * @param string $remote_image_url   远程图片 URL。
     * @param bool   $set_as_featured     是否将该图片设为特色图。
     * @param string $alt                 可选 ALT 文本（仅用于返回用，不写入 IMG 标签）。
     * @param int    $timeout             下载超时时间，默认 60 秒。
     * @return array 返回结构：
     *               [
     *                 'ok' => bool,
     *                 'attachment_id' => int|null,
     *                 'url' => string|null,
     *                 'featured_set' => bool,
     *                 'mime' => string|null,
     *                 'error' => string|null,
     *               ]
     */
    public static function attach_remote_image_to_post(int $post_id, string $remote_image_url, bool $set_as_featured = false, string $alt = '', int $timeout = 60): array
    {
        $result = [
            'ok'            => false,
            'attachment_id' => null,
            'url'           => null,
            'featured_set'  => false,
            'mime'          => null,
            'error'         => null,
        ];

        if (empty($remote_image_url)) {
            $result['error'] = 'empty_url';
            return $result;
        }

        // 加载必要的 WP 函数
        if (!function_exists('media_handle_sideload')) {
            if (defined('ABSPATH')) {
                require_once(ABSPATH . 'wp-admin/includes/media.php');
                require_once(ABSPATH . 'wp-admin/includes/file.php');
                require_once(ABSPATH . 'wp-admin/includes/image.php');
            } else {
                $result['error'] = 'missing_wp_bootstrap';
                return $result;
            }
        }

        // 1) 稳健下载到临时文件
        $temp_file = self::download_to_temp($remote_image_url, $timeout);
        if (is_wp_error($temp_file)) {
            Logger::warning('AttachImage: download failed: ' . $remote_image_url . ' | ' . $temp_file->get_error_message(), 'ImageSideload');
            $result['error'] = $temp_file->get_error_message();
            return $result;
        }

        // 2) 识别 MIME / 扩展名（多重兜底）
        $actual_mime_type = '';
        $actual_extension = '';

        // 2.1 基于 URL 的扩展名初判
        $url_path = parse_url($remote_image_url, PHP_URL_PATH) ?: '';
        $url_ext = strtolower(pathinfo($url_path, PATHINFO_EXTENSION) ?: '');
        $ext_map = [
            'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg',
            'png' => 'image/png',  'gif'  => 'image/gif',
            'webp'=> 'image/webp', 'bmp'  => 'image/bmp',
            'avif'=> 'image/avif'
        ];
        if ($url_ext && isset($ext_map[$url_ext])) {
            $actual_extension = $url_ext === 'jpeg' ? 'jpg' : $url_ext;
            $actual_mime_type = $ext_map[$url_ext];
        }

        // 2.2 finfo_from content
        if (!$actual_mime_type && function_exists('finfo_open')) {
            $fi = @finfo_open(FILEINFO_MIME_TYPE);
            if ($fi) {
                $mt = @finfo_file($fi, $temp_file) ?: '';
                if (is_string($mt) && $mt !== '') $actual_mime_type = $mt;
                @finfo_close($fi);
            }
        }

        // 2.3 mime_content_type
        if (!$actual_mime_type && function_exists('mime_content_type')) {
            $actual_mime_type = @mime_content_type($temp_file) ?: '';
        }

        // 2.4 exif_imagetype / image_type_to_mime_type
        if (!$actual_mime_type && function_exists('exif_imagetype') && function_exists('image_type_to_mime_type')) {
            $image_type_const = @exif_imagetype($temp_file);
            if ($image_type_const) {
                $actual_mime_type = @image_type_to_mime_type($image_type_const) ?: '';
            }
        }

        // 2.5 getimagesize 兜底
        if (!$actual_mime_type) {
            $gi = @getimagesize($temp_file);
            if (is_array($gi)) {
                if (!empty($gi['mime'])) {
                    $actual_mime_type = (string) $gi['mime'];
                } elseif (isset($gi[2]) && function_exists('image_type_to_mime_type')) {
                    $actual_mime_type = @image_type_to_mime_type($gi[2]) ?: '';
                }
            }
        }

        // 2.6 由 MIME 反推扩展名
        if ($actual_mime_type) {
            switch ($actual_mime_type) {
                case 'image/webp': $actual_extension = $actual_extension ?: 'webp'; break;
                case 'image/jpeg': $actual_extension = 'jpg';  break;
                case 'image/png':  $actual_extension = 'png';  break;
                case 'image/gif':  $actual_extension = 'gif';  break;
                case 'image/avif': $actual_extension = 'avif'; break;
                case 'image/bmp':  $actual_extension = 'bmp';  break;
                default:
                    // 未知类型统一回退为 jpg
                    $actual_extension = 'jpg';
                    $actual_mime_type = 'image/jpeg';
            }
        }

        // 2.7 仍未知则最终兜底为 jpg
        if (!$actual_mime_type) {
            $actual_extension = 'jpg';
            $actual_mime_type = 'image/jpeg';
        }

        $result['mime'] = $actual_mime_type;

        // 3) 侧载到媒体库（绑定父子）
        $upload_dir_info = wp_upload_dir();
        // 多站点/权限诊断信息
        $blog_id = function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0;
        $is_ms   = function_exists('is_multisite') ? (is_multisite() ? 'yes' : 'no') : 'unknown';
        $basedir = $upload_dir_info['basedir'] ?? '';
        $path    = $upload_dir_info['path'] ?? '';
        $baseurl = $upload_dir_info['baseurl'] ?? '';
        $url     = $upload_dir_info['url'] ?? '';
        $subdir  = $upload_dir_info['subdir'] ?? '';
        $w_path  = ($path && @is_writable($path)) ? 'yes' : 'no';
        $w_base  = ($basedir && @is_writable($basedir)) ? 'yes' : 'no';
        $perm_path = $path ? substr(sprintf('%o', @fileperms($path) ?: 0), -4) : '----';
        $perm_base = $basedir ? substr(sprintf('%o', @fileperms($basedir) ?: 0), -4) : '----';
        $temp_size = @filesize($temp_file);
        Logger::debug('UploadDir: blog_id=' . $blog_id . ', ms=' . $is_ms . ', basedir=' . $basedir . ', path=' . $path . ', w_path=' . $w_path . ', w_base=' . $w_base . ', temp_size=' . ($temp_size !== false ? (int)$temp_size : -1), 'ImageSideload');

        if (!empty($upload_dir_info['error'])) {
            Logger::error('AttachImage: upload dir error: ' . $upload_dir_info['error'] . ' | blog_id=' . $blog_id, 'ImageSideload');
            @unlink($temp_file);
            $result['error'] = 'upload_dir_error:' . $upload_dir_info['error'];
            return $result;
        }

        // --- 阿里云 OSS 兼容逻辑：未配置 AccessKey 时，暂时跳过 OSS 缩略图上传钩子，避免 access key id is empty 致命错误 ---
        $oss_filters_removed = [];
        try {
            if (function_exists('get_option')) {
                $oss_options = get_option('oss_options');
                if (is_array($oss_options)) {
                    $oss_ak = trim((string)($oss_options['accessKeyId'] ?? ''));
                    if ($oss_ak === '') {
                        // 仅在检测到未配置 AccessKey 时，才对已注册的 OSS 相关钩子做一次性移除
                        $hooks = [
                            ['hook' => 'wp_handle_upload', 'callback' => 'oss_upload_attachments', 'priority' => 50],
                            ['hook' => 'wp_generate_attachment_metadata', 'callback' => 'oss_upload_thumbs', 'priority' => 100],
                            ['hook' => 'wp_save_image_editor_file', 'callback' => 'oss_save_image_editor_file', 'priority' => 101],
                        ];

                        foreach ($hooks as $h) {
                            if (function_exists($h['callback']) && has_filter($h['hook'], $h['callback'])) {
                                remove_filter($h['hook'], $h['callback'], $h['priority']);
                                $oss_filters_removed[] = $h;
                            }
                        }

                        if (!empty($oss_filters_removed)) {
                            Logger::debug('OSSCompat: skipping OSS upload hooks (empty accessKeyId)', 'ImageSideload');
                        }
                    }
                }
            }
        } catch (\Throwable $e) {
            // 兼容逻辑异常不影响主流程
            Logger::warning('OSSCompat: error checking OSS options: ' . $e->getMessage(), 'ImageSideload');
        }

        $unique_basename = wp_unique_filename($upload_dir_info['path'], uniqid('api_img_') . '.' . $actual_extension);
        $file_array = [
            'name'     => $unique_basename,
            'type'     => $actual_mime_type,
            'tmp_name' => $temp_file,
        ];

        // 确保允许 webp 等扩展
        $sideload_extensions_callback = function ($extensions) {
            $allowed = ['jpg', 'jpeg', 'gif', 'png', 'webp'];
            if (!is_array($extensions)) $extensions = [];
            foreach ($allowed as $ext) if (!in_array($ext, $extensions, true)) $extensions[] = $ext;
            return $extensions;
        };
        add_filter('image_sideload_extensions', $sideload_extensions_callback, 20);

        $check_filetype_callback = null;
        if ($actual_extension === 'webp') {
            $check_filetype_callback = function ($data, $file, $filename, $mimes) {
                if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) === 'webp') {
                    $data['ext']  = 'webp';
                    $data['type'] = 'image/webp';
                }
                return $data;
            };
            add_filter('wp_check_filetype_and_ext', $check_filetype_callback, 10, 4);
        }

        // 侧载前记录一次准备信息
        Logger::debug('AttachImage: sideload prepare: parent=' . ($post_id > 0 ? (int)$post_id : 0) . ', filename=' . $unique_basename . ', mime=' . $actual_mime_type, 'ImageSideload');
        $parent = $post_id > 0 ? $post_id : 0;
        $attachment_id = media_handle_sideload($file_array, $parent);

        remove_filter('image_sideload_extensions', $sideload_extensions_callback, 20);
        if ($check_filetype_callback) remove_filter('wp_check_filetype_and_ext', $check_filetype_callback, 10);

        // 恢复刚才为兼容 OSS 而移除的钩子，确保其它流程仍然受 OSS 管理
        if (!empty($oss_filters_removed)) {
            foreach ($oss_filters_removed as $h) {
                add_filter($h['hook'], $h['callback'], $h['priority']);
            }
        }

        // 清理临时文件（media_handle_sideload 成功/失败都会移动或清理，但稳妥起见再检查）
        if (file_exists($temp_file)) @unlink($temp_file);

        if (is_wp_error($attachment_id)) {
            Logger::error('AttachImage: media_handle_sideload failed: ' . $attachment_id->get_error_message() . ' | blog_id=' . $blog_id, 'ImageSideload');
            $result['error'] = $attachment_id->get_error_message();
            return $result;
        }

        $image_url = wp_get_attachment_url($attachment_id);
        if (!$image_url) {
            Logger::warning('AttachImage: wp_get_attachment_url returned empty, deleting attachment #' . (int)$attachment_id, 'ImageSideload');
            if (is_int($attachment_id)) wp_delete_attachment($attachment_id, true);
            $result['error'] = 'empty_attachment_url';
            return $result;
        }

        $result['attachment_id'] = (int) $attachment_id;
        $result['url'] = $image_url;

        // 4) 可选设为特色图
        if ($set_as_featured && $post_id > 0) {
            $set_ok = false;
            if (function_exists('set_post_thumbnail')) {
                $set_ok = @set_post_thumbnail($post_id, $attachment_id);
            } else {
                // 加载函数后重试
                if (defined('ABSPATH')) {
                    require_once ABSPATH . 'wp-admin/includes/post.php';
                }
                if (function_exists('set_post_thumbnail')) {
                    $set_ok = @set_post_thumbnail($post_id, $attachment_id);
                }
            }
            if (!$set_ok) {
                Logger::warning('AttachImage: set_post_thumbnail failed for post #' . (int)$post_id . ' with attachment #' . (int)$attachment_id, 'ImageSideload');
            }
            $result['featured_set'] = (bool) $set_ok;
        }

        $result['ok'] = true;
        // 成功侧载后的关键信息日志
        Logger::debug('AttachImage: sideload success: attachment_id=' . (int)$attachment_id . ', parent=' . ($post_id > 0 ? (int)$post_id : 0) . ', featured=' . ((bool)($result['featured_set'] ?? false) ? 'yes' : 'no'), 'ImageSideload');
        return $result;
    }
    
    /**
     * 递归查找设置数组中的指定键。
     * 某些设置分组结构较深（如 global_tabs / api_tabs 等），因此做深度搜索更稳妥。
     */
    private static function find_setting_value(array $settings, string $key)
    {
        if (array_key_exists($key, $settings)) {
            return $settings[$key];
        }
        foreach ($settings as $k => $v) {
            if (is_array($v)) {
                $found = self::find_setting_value($v, $key);
                if ($found !== null) return $found;
            }
        }
        return null;
    }

    /**
     * 解析“豆包文生图”尺寸配置，输出 [width, height]。
     * - 任务级下拉：支持 4:3 / 16:9 / 1:1，分别映射到 1024x768 / 1024x576 / 1024x1024
     * - 强制约束：宽高均在 [512, 2048] 区间；若越界则回退默认 1024x768
     *
     * @param array $settings 通常传入“当前任务”的设置数组（包含 doubao-imagegen-size-task）
     */
    public static function parse_imagegen_dimensions(array $settings): array
    {
        $DEFAULT = [1024, 768]; // 4:3 安全默认

        // 首选读取任务级字段
        $val = null;
        if (array_key_exists('doubao-imagegen-size-task', $settings)) {
            $val = $settings['doubao-imagegen-size-task'];
        } else {
            // 兼容：递归查找（如上层不直接传 task_settings）
            $val = self::find_setting_value($settings, 'doubao-imagegen-size-task');
        }

        $ratio = is_string($val) ? trim($val) : '';
        switch ($ratio) {
            case '4:3':
                $w = 1024; $h = 768; break;
            case '16:9':
                $w = 1024; $h = 576; break;
            case '1:1':
                $w = 1024; $h = 1024; break;
            default:
                // 也兼容直接传 "WxH" 的值
                if (preg_match('/^(\d{2,4})[xX\*](\d{2,4})$/', (string)$ratio, $m)) {
                    $w = (int)$m[1];
                    $h = (int)$m[2];
                } else {
                    return $DEFAULT;
                }
        }

        // 约束校验
        $w = (int)$w; $h = (int)$h;
        if ($w < 512 || $w > 2048 || $h < 512 || $h > 2048) {
            Logger::debug('VolcEngine: invalid size, fallback to 1024x768: ' . $w . 'x' . $h, 'ImageGen');
            return $DEFAULT;
        }
        return [$w, $h];
    }

    /**
     * 预留：通过火山引擎文生图生成图片，并返回可下载 URL。
     * 注意：此方法暂未实现实际调用，仅作为后续接入占位和统一入口。
     */
    public static function generate_image_via_volcengine(string $prompt, array $settings, ?array $task_settings = null)
    {
        // 解析尺寸与模型（尺寸优先取任务级配置）
        $ctxForSize = is_array($task_settings) && !empty($task_settings) ? $task_settings : $settings;
        [$width, $height] = self::parse_imagegen_dimensions($ctxForSize);
        $model = (string) (self::find_setting_value($settings, 'doubao-imagegen-model') ?? 'doubao-seedream-3-0-t2i-250415');

        // 特殊处理：seedream 4.x 模型要求最小像素数为 3686400（约 1920x1920）
        $pixelCount = (int)$width * (int)$height;
        if (in_array($model, ['doubao-seedream-4-5-251128', 'doubao-seedream-4-0-250828'], true) && $pixelCount > 0 && $pixelCount < 3686400) {
            $width  = 1920;
            $height = 1920;
            $pixelCount = $width * $height;
            Logger::debug('VolcEngine: auto-upscale to 1920x1920 for model ' . $model, 'ImageGen');
        }

        $size_str = sprintf('%dx%d', (int)$width, (int)$height);
        $endpoint = 'https://ark.cn-beijing.volces.com/api/v3/images/generations';

        // 选择凭据：从 settings['doubao-accounts'] 随机挑选一个账号的密钥
        $accounts = $settings['doubao-accounts'] ?? [];
        if (!is_array($accounts) || empty($accounts)) {
            Logger::error('VolcEngine: no doubao accounts configured', 'ImageGen');
            return new \WP_Error('no_account', 'No Doubao accounts configured.');
        }
        $index = rand(1, count($accounts));
        $account = $accounts[$index - 1];
        $apiKey = trim((string)($account['account-secret-key'] ?? ''));
        if ($apiKey === '') {
            Logger::error('VolcEngine: selected account has empty api key', 'ImageGen');
            return new \WP_Error('invalid_account', 'Selected Doubao account has empty API key.');
        }

        // 读取可选参数（若未来在设置中提供）
        $guidance_scale = self::find_setting_value($ctxForSize, 'doubao-imagegen-guidance-scale');
        $guidance_scale = $guidance_scale !== null ? (float)$guidance_scale : null;
        $seed = self::find_setting_value($ctxForSize, 'doubao-imagegen-seed');
        $seed = $seed !== null ? (int)$seed : null;
        // 默认关闭水印，除非设置显式开启
        $watermarkVal = self::find_setting_value($ctxForSize, 'doubao-imagegen-watermark');
        $watermark = $watermarkVal !== null ? (bool)$watermarkVal : false;

        // JSON 请求体（严格遵循官方字段名）
        $payload = [
            'model' => $model,
            'prompt' => (string)$prompt,
            'response_format' => 'url',
            'size' => $size_str,
            'watermark' => $watermark,
        ];
        if ($guidance_scale !== null) { $payload['guidance_scale'] = $guidance_scale; }
        if ($seed !== null) { $payload['seed'] = $seed; }

        // 调试：打印关键载荷参数（不打印完整 prompt 以免日志过大）
        $prompt_len = strlen((string)$prompt);
        Logger::debug('VolcEngine: request: model=' . $model . ' size=' . $size_str . ' prompt_len=' . $prompt_len, 'ImageGen');

        // 使用插件自带 CA 证书以避免 cURL error 60（与 ApiClientService 策略一致）
        $pluginRoot = \dirname(__DIR__, 2); // Service/Features -> 插件根
        $caFile = $pluginRoot . '/resources/cacert.pem';
        $caExists = @file_exists($caFile);

        // 以 cURL 直接调用，确保可设置 CA 路径
        $ch = \curl_init($endpoint);
        if ($ch === false) {
            return new \WP_Error('curl_init_failed', 'Failed to initialize cURL.');
        }
        $headers = [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $apiKey,
        ];
        $json = wp_json_encode($payload);
        \curl_setopt($ch, CURLOPT_POST, true);
        \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        \curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        \curl_setopt($ch, CURLOPT_TIMEOUT, 120);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        if ($caExists) {
            \curl_setopt($ch, CURLOPT_CAINFO, $caFile);
        }

        $response = \curl_exec($ch);
        $errno = \curl_errno($ch);
        $error = \curl_error($ch);
        $httpCode = (int) \curl_getinfo($ch, CURLINFO_HTTP_CODE);
        \curl_close($ch);

        if ($errno) {
            Logger::error('VolcEngine: cURL error (' . $errno . '): ' . $error, 'ImageGen');
            return new \WP_Error('curl_error', 'cURL error: ' . $error);
        }
        if ($httpCode < 200 || $httpCode >= 300) {
            Logger::error('VolcEngine: HTTP ' . $httpCode . ' body=' . substr((string)$response, 0, 500), 'ImageGen');
            return new \WP_Error('http_error', 'HTTP status ' . $httpCode);
        }

        $data = json_decode((string)$response, true);
        if (!is_array($data)) {
            Logger::error('VolcEngine: invalid JSON response', 'ImageGen');
            return new \WP_Error('bad_json', 'Invalid JSON response.');
        }
        if (!empty($data['error'])) {
            Logger::error('VolcEngine: api error: ' . (is_string($data['error']) ? $data['error'] : wp_json_encode($data['error'])), 'ImageGen');
            return new \WP_Error('api_error', is_string($data['error']) ? $data['error'] : 'API returned error.');
        }
        if (empty($data['data']) || !is_array($data['data'])) {
            Logger::error('VolcEngine: response has no data array', 'ImageGen');
            return new \WP_Error('no_data', 'Response has no data.');
        }
        Logger::debug('VolcEngine: response data_count=' . (is_array($data['data']) ? count($data['data']) : 0), 'ImageGen');
        $first = $data['data'][0] ?? null;
        $url = is_array($first) ? ($first['url'] ?? '') : '';
        if (!is_string($url) || $url === '') {
            Logger::error('VolcEngine: response data has no url', 'ImageGen');
            return new \WP_Error('no_url', 'No image URL in response.');
        }

        Logger::info('VolcEngine: success model=' . $model . ' size=' . $size_str, 'ImageGen');
        // 返回统一结构，供上层决定侧载或直接使用
        return [
            'url' => $url,
            'model' => $model,
            'size' => $size_str,
            'mime' => 'image/jpeg',
        ];
    }

    /**
     * 通过阿里云 DashScope 文生图生成图片，并返回可下载 URL。
     * 严格对齐官方参数：model、input.prompt、parameters.size(如 1024*1024)、parameters.n 等。
     * - qwen-image 支持 parameters.prompt_extend, parameters.watermark
     * - wanx-v1 支持 parameters.style
     * - wan2.2-t2i-flash 采用通用 parameters（size/n）
     * 返回统一结构：['url' => ..., 'model' => ..., 'size' => ..., 'mime' => 'image/jpeg'] 或 WP_Error
     */
    public static function generate_image_via_aliyun_dashscope(string $prompt, array $settings)
    {
        // 图像编辑专用模型（例如 wan2.5-i2i-preview）需要输入图片，当前流程仅支持“纯文生图”，因此直接拦截。
        // 若后续需要接入图像编辑，可单独实现新的入口与端点 image2image/image-synthesis。
        $globalModel = (string) (
            self::find_setting_value($settings, 'aliyun-imagegen-model-task')
            ?? self::find_setting_value($settings, 'aliyun-imagegen-model')
            ?? 'wan2.2-t2i-flash'
        );
        if ($globalModel === 'wan2.5-i2i-preview') {
            Logger::warning('Aliyun: model wan2.5-i2i-preview not supported in text-to-image workflow', 'ImageGen');
            return new \WP_Error(
                'unsupported_model',
                'wan2.5-i2i-preview 需要图像编辑输入（image2image/image-synthesis），当前 AI-Post 仅支持纯文生图流程。'
            );
        }

        // 读取任务级优先的设置（存在 *-task 则覆盖全局），严格对齐阿里云参数
        $model = $globalModel;
        // 优先读取“画幅比例”字段并映射到各模型合法尺寸；若无则回退到旧的尺寸字段
        if ($model === 'qwen-image') {
            $aspect = (string) (
                self::find_setting_value($settings, 'aliyun-imagegen-aspect-qwen-task')
                ?? ''
            );
            if ($aspect === '16:9') {
                $size = '1664*928';          // qwen-image 横图 16:9
            } elseif ($aspect === '4:3') {
                $size = '1472*1140';         // qwen-image 横图 4:3
            } elseif ($aspect === '1:1') {
                $size = '1328*1328';         // qwen-image 方图 1:1（推荐）
            } else {
                // 兼容旧字段或缺省
                $size = (string) (
                    self::find_setting_value($settings, 'aliyun-imagegen-size-qwen-task')
                    ?? '1328*1328'
                );
            }
        } else {
            $aspect = (string) (
                self::find_setting_value($settings, 'aliyun-imagegen-aspect-wan-task')
                ?? ''
            );
            if ($aspect === '16:9') {
                $size = '1024*576';          // WAN 横图 16:9（<=2MP 且边长合规）
            } elseif ($aspect === '4:3') {
                $size = '1024*768';          // WAN 横图 4:3
            } elseif ($aspect === '1:1') {
                $size = '1024*1024';         // WAN 方图 1:1（推荐）
            } else {
                // 兼容旧字段或缺省
                $size = (string) (
                    self::find_setting_value($settings, 'aliyun-imagegen-size-wan-task')
                    ?? '1024*1024'
                );
            }
        }
        $nRaw = (
            self::find_setting_value($settings, 'aliyun-imagegen-n-task')
            ?? 1
        );
        $n = (int) $nRaw; if ($n < 1) { $n = 1; }
        // 条件参数：qwen-image 专属
        $promptExtendVal = (
            self::find_setting_value($settings, 'aliyun-imagegen-prompt-extend-task')
            ?? self::find_setting_value($settings, 'aliyun-imagegen-prompt-extend')
        );
        $prompt_extend = $promptExtendVal !== null ? (bool)$promptExtendVal : true;
        $watermarkVal = (
            self::find_setting_value($settings, 'aliyun-imagegen-watermark-task')
            ?? self::find_setting_value($settings, 'aliyun-imagegen-watermark')
        );
        $watermark = $watermarkVal !== null ? (bool)$watermarkVal : true;
        // 条件参数：wanx-v1 专属
        $style = (string) (
            self::find_setting_value($settings, 'aliyun-imagegen-style-task')
            ?? self::find_setting_value($settings, 'aliyun-imagegen-style')
            ?? '<auto>'
        );

        // 模型尺寸约束与自动修正（严格对齐官方文档）
        // qwen-image 允许尺寸：1664*928,1472*1140,1328*1328,1140*1472,928*1664
        if ($model === 'qwen-image') {
            $allowed_sizes_qwen = ['1664*928', '1472*1140', '1328*1328', '1140*1472', '928*1664'];
            // 归一化分隔符：支持 1024x768 / 1024X768 / 1024×768 写法
            $size_norm = str_replace(['x', 'X', '×'], '*', trim((string)$size));
            if (!in_array($size_norm, $allowed_sizes_qwen, true)) {
                Logger::debug('Aliyun: invalid size for qwen-image: ' . $size . ', auto-correct to 1328*1328', 'ImageGen');
                $size = '1328*1328';
            } else {
                $size = $size_norm;
            }
            // qwen-image 要求每次仅生成 1 张图（num_images_per_prompt==1），强制修正 n=1
            if ($n !== 1) {
                Logger::debug('Aliyun: invalid n for qwen-image: ' . $n . ', auto-correct to 1', 'ImageGen');
                $n = 1;
            }
        }
        // qwen-image-edit 为图像编辑模型，当前 Text-to-Image 端点不支持，直接拦截
        elseif ($model === 'qwen-image-edit') {
            Logger::warning('Aliyun: model qwen-image-edit not supported by text-to-image endpoint', 'ImageGen');
            return new \WP_Error('unsupported_model', 'qwen-image-edit 暂不支持当前文生图流程（需要图像编辑端点）。');
        }
        // 万相系列（wan/wanx）：按 V2 文档允许范围校验（边长 512~1440，且总像素 <= 2,000,000）
        elseif (in_array($model, ['wan2.2-t2i-flash','wan2.2-t2i-plus','wanx2.1-t2i-plus','wanx2.1-t2i-turbo','wanx-v1','wan2.6-image'], true)) {
            $size_norm = str_replace(['x', 'X', '×'], '*', trim((string)$size));
            $valid = false;
            if (preg_match('/^(\d{2,4})\*(\d{2,4})$/', $size_norm, $m)) {
                $w = (int) $m[1];
                $h = (int) $m[2];
                $sides_ok = ($w >= 512 && $w <= 1440 && $h >= 512 && $h <= 1440);
                $pixels_ok = ((float)$w * (float)$h <= 2000000.0);
                if ($sides_ok && $pixels_ok) {
                    $valid = true;
                }
            }
            if (!$valid) {
                Logger::debug('Aliyun: invalid size for ' . $model . ': ' . $size . ', auto-correct to 1024*1024', 'ImageGen');
                $size = '1024*1024';
            } else {
                $size = $size_norm;
            }
        }

        // 选择一个阿里百炼密钥（与设置页一致）
        $accounts = $settings['bai-lian-accounts'] ?? [];
        if (!is_array($accounts) || empty($accounts)) {
            Logger::error('Aliyun: no bai-lian accounts configured', 'ImageGen');
            return new \WP_Error('no_account', 'No Aliyun (BaiLian) accounts configured.');
        }
        $apiKey = '';
        foreach ($accounts as $acc) {
            $k = isset($acc['account-secret-key']) ? trim((string)$acc['account-secret-key']) : '';
            if ($k !== '') { $apiKey = $k; break; }
        }
        if ($apiKey === '') {
            return new \WP_Error('invalid_account', 'Aliyun account API key is empty.');
        }

        // 端点选择：
        // - 旧万相系列：沿用 text2image/image-synthesis
        // - wan2.6-image：根据官方文档，异步推荐端点为 image-generation/generation
        if ($model === 'wan2.6-image') {
            $endpoint = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/image-generation/generation';
        } else {
            $endpoint = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis';
        }
        // 不同模型的 input 结构：
        // - 旧版 qwen-image / wan 系列：沿用 input.prompt
        // - wan2.6-image：根据「图像生成与编辑2.6」文档，要求使用 input.messages 结构
        if ($model === 'wan2.6-image') {
            $input = [
                'messages' => [
                    [
                        'role' => 'user',
                        'content' => [
                            ['text' => (string)$prompt],
                        ],
                    ],
                ],
            ];
        } else {
            $input = [ 'prompt' => (string)$prompt ];
        }

        $payload = [
            'model' => $model,
            'input' => $input,
            'parameters' => array_filter([
                'size' => $size,
                'n'    => $n,
                // qwen-image 专属
                'prompt_extend' => ($model === 'qwen-image') ? $prompt_extend : null,
                'watermark'     => ($model === 'qwen-image') ? $watermark : null,
                // wanx-v1 专属
                'style'         => ($model === 'wanx-v1') ? $style : null,
                // wan2.6-image 专属：允许纯文本 messages，不强制最后一条包含 image
                'enable_interleave' => ($model === 'wan2.6-image') ? true : null,
            ], function($v){ return $v !== null; })
        ];

        $prompt_len = strlen((string)$prompt);
        Logger::debug('Aliyun: request: model=' . $model . ' size=' . $size . ' n=' . $n . ' prompt_len=' . $prompt_len, 'ImageGen');

        // 使用插件自带 CA
        $pluginRoot = \dirname(__DIR__, 2);
        $caFile = $pluginRoot . '/resources/cacert.pem';
        $caExists = @file_exists($caFile);

        $headers = [
            'Content-Type: application/json',
            'Accept: application/json',
            'Authorization: Bearer ' . $apiKey,
            // 阿里云百炼部分账号不支持同步调用，需启用异步头
            'X-DashScope-Async: enable',
        ];
        $json = wp_json_encode($payload);

        $ch = \curl_init($endpoint);
        if ($ch === false) {
            return new \WP_Error('curl_init_failed', 'Failed to initialize cURL.');
        }
        \curl_setopt($ch, CURLOPT_POST, true);
        \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        \curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        \curl_setopt($ch, CURLOPT_TIMEOUT, 120);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        if ($caExists) { \curl_setopt($ch, CURLOPT_CAINFO, $caFile); }

        $response = \curl_exec($ch);
        $errno = \curl_errno($ch);
        $error = \curl_error($ch);
        $httpCode = (int) \curl_getinfo($ch, CURLINFO_HTTP_CODE);
        \curl_close($ch);

        if ($errno) {
            Logger::error('Aliyun: cURL error (' . $errno . '): ' . $error, 'ImageGen');
            return new \WP_Error('curl_error', 'cURL error: ' . $error);
        }
        if ($httpCode < 200 || $httpCode >= 300) {
            Logger::error('Aliyun: HTTP ' . $httpCode . ' body=' . substr((string)$response, 0, 500), 'ImageGen');
            return new \WP_Error('http_error', 'HTTP status ' . $httpCode);
        }

        $data = json_decode((string)$response, true);
        if (!is_array($data)) {
            return new \WP_Error('bad_json', 'Invalid JSON response.');
        }

        // 异步模式：优先从 output.task_id 提取并轮询查询
        $taskId = '';
        if (!empty($data['output']) && is_array($data['output'])) {
            $taskId = (string)($data['output']['task_id'] ?? '');
        }

        // 某些返回结构也可能在顶层附带 task_id
        if ($taskId === '' && isset($data['task_id'])) {
            $taskId = (string)$data['task_id'];
        }

        if ($taskId !== '') {
            Logger::debug('Aliyun: async accepted, task_id=' . $taskId, 'ImageGen');
            // 轮询任务接口，直到 SUCCEEDED/FAILED/TIMEOUT
            $pollInterval = 2;  // 每次轮询间隔（秒）
            $maxWait = 240;     // 最大等待总时长（秒），适配 wan2.6-image 队列耗时
            $deadline = time() + $maxWait;
            $taskEndpoint = 'https://dashscope.aliyuncs.com/api/v1/tasks/' . rawurlencode($taskId);

            $taskHeaders = [
                'Accept: application/json',
                'Authorization: Bearer ' . $apiKey,
            ];

            $finalUrl = '';
            $lastStatus = '';
            while (time() < $deadline) {
                $tc = \curl_init($taskEndpoint);
                if ($tc === false) {
                    return new \WP_Error('curl_init_failed', 'Failed to init cURL for task polling.');
                }
                \curl_setopt($tc, CURLOPT_HTTPGET, true);
                \curl_setopt($tc, CURLOPT_HTTPHEADER, $taskHeaders);
                \curl_setopt($tc, CURLOPT_RETURNTRANSFER, true);
                \curl_setopt($tc, CURLOPT_TIMEOUT, 30);
                \curl_setopt($tc, CURLOPT_SSL_VERIFYPEER, true);
                \curl_setopt($tc, CURLOPT_SSL_VERIFYHOST, 2);
                if ($caExists) { \curl_setopt($tc, CURLOPT_CAINFO, $caFile); }

                $tresp = \curl_exec($tc);
                $terrno = \curl_errno($tc);
                $terror  = \curl_error($tc);
                $thttp   = (int) \curl_getinfo($tc, CURLINFO_HTTP_CODE);
                \curl_close($tc);

                if ($terrno) {
                    Logger::warning('Aliyun: task poll cURL error (' . $terrno . '): ' . $terror, 'ImageGen');
                    sleep($pollInterval);
                    continue;
                }
                if ($thttp < 200 || $thttp >= 300) {
                    Logger::warning('Aliyun: task poll HTTP ' . $thttp, 'ImageGen');
                    sleep($pollInterval);
                    continue;
                }

                $tdata = json_decode((string)$tresp, true);
                if (!is_array($tdata)) {
                    Logger::warning('Aliyun: task poll bad json', 'ImageGen');
                    sleep($pollInterval);
                    continue;
                }

                // 读取任务状态
                $status = '';
                if (!empty($tdata['output']) && is_array($tdata['output'])) {
                    $status = (string)($tdata['output']['task_status'] ?? '');
                } elseif (isset($tdata['task_status'])) {
                    $status = (string)$tdata['task_status'];
                }

                if ($status && $status !== $lastStatus) {
                    $lastStatus = $status;
                    Logger::debug('Aliyun: task ' . $taskId . ' status=' . $status, 'ImageGen');
                }

                if (strcasecmp($status, 'SUCCEEDED') === 0) {
                    // 提取图片 URL
                    $out = $tdata['output'] ?? [];
                    if (is_array($out)) {
                        // 优先适配 wan2.6-image 新版结构：output.choices[0].message.content[*].image
                        if ($model === 'wan2.6-image' && !empty($out['choices']) && is_array($out['choices'])) {
                            $choice0 = $out['choices'][0] ?? null;
                            if (is_array($choice0) && !empty($choice0['message']) && is_array($choice0['message'])) {
                                $contentArr = $choice0['message']['content'] ?? [];
                                if (is_array($contentArr)) {
                                    foreach ($contentArr as $item) {
                                        if (is_array($item) && !empty($item['image']) && is_string($item['image'])) {
                                            $finalUrl = (string)$item['image'];
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        // 兼容旧版 text2image 结构：output.results[0].url / image_url
                        if ($finalUrl === '' && !empty($out['results']) && is_array($out['results'])) {
                            $first = $out['results'][0] ?? null;
                            if (is_array($first)) {
                                $finalUrl = (string)($first['url'] ?? ($first['image_url'] ?? ''));
                            }
                        }

                        // 兼容 output.images: [url, ...]
                        if ($finalUrl === '' && !empty($out['images']) && is_array($out['images'])) {
                            $finalUrl = (string)($out['images'][0] ?? '');
                        }
                    }

                    if (!is_string($finalUrl) || $finalUrl === '') {
                        Logger::error('Aliyun: task ' . $taskId . ' succeeded but no image url', 'ImageGen');
                        return new \WP_Error('no_url', 'Task succeeded but no image URL.');
                    }

                    Logger::info('Aliyun: success model=' . $model . ' size=' . $size, 'ImageGen');
                    return [
                        'url' => $finalUrl,
                        'model' => $model,
                        'size' => $size,
                        'mime' => 'image/jpeg',
                    ];
                }

                if (in_array(strtoupper($status), ['FAILED', 'CANCELED', 'TIMEOUT'], true)) {
                    Logger::error('Aliyun: task ' . $taskId . ' final status=' . $status, 'ImageGen');
                    return new \WP_Error('task_failed', 'Aliyun task final status: ' . $status);
                }

                // 继续轮询
                sleep($pollInterval);
            }

            return new \WP_Error('task_timeout', 'Aliyun task polling timeout after ' . $maxWait . ' seconds.');
        }

        // 若未返回 task_id，则尝试兼容同步返回（极少数场景）
        // 兼容多种返回结构
        $url = '';
        if (!$url && !empty($data['data']) && is_array($data['data'])) {
            $first = $data['data'][0] ?? null;
            if (is_array($first)) {
                $url = (string) ($first['url'] ?? ($first['image_url'] ?? ''));
            }
        }
        if (!$url && !empty($data['output']) && is_array($data['output'])) {
            $imgs = $data['output']['images'] ?? [];
            if (is_array($imgs) && !empty($imgs)) {
                $url = (string) $imgs[0];
            }
        }
        if (!is_string($url) || $url === '') {
            Logger::error('Aliyun: response missing image url and no task_id', 'ImageGen');
            return new \WP_Error('no_url', 'No image URL or task_id in response.');
        }
        Logger::info('Aliyun: success (sync) model=' . $model . ' size=' . $size, 'ImageGen');
        return [
            'url' => $url,
            'model' => $model,
            'size' => $size,
            'mime' => 'image/jpeg',
        ];
    }

    /**
     * 根据标题生成英文的文生图指令（Prompt）。
     * 要求：
     * - 始终英文输出
     * - 不设置特色图偏好，仅生成文本指令
     * - 失败时回退为基于标题的安全英文指令
     *
     * v2.9.1 改进：
     * - 移除对屏幕/UI/网站等技术元素的无差别禁止（技术文章需要这些元素）
     * - 仅保留"不渲染文字"核心约束
     * - 支持接收 AI 预生成的场景描述以提升相关性
     */
    public static function generate_image_prompt_from_title(string $title, array $settings, string $ai_scene_hint = ''): string
    {
        $title = trim((string)$title);
        if ($title === '') {
            $title = 'A high-quality 4:3 photo on the topic';
        }

        // 允许外部通过过滤器自定义提示词生成（若站点已集成其他文本API，可在此接管）。
        $filtered = apply_filters('ai_post_generate_image_prompt', null, $title, $settings);
        if (is_string($filtered) && $filtered !== '') {
            return $filtered;
        }

        // 若调用方已通过 AI 预生成了场景描述，直接使用（仅追加"不渲染文字"约束）
        $ai_scene_hint = trim((string)$ai_scene_hint);
        if ($ai_scene_hint !== '') {
            $prompt = $ai_scene_hint .
                " Absolutely no text, letters, numbers, characters, watermarks or logos rendered in the image. High quality, 4:3 aspect ratio.";
            Logger::debug('prompt-from-ai-hint ready (len=' . strlen($prompt) . ')', 'ImageGen');
            return $prompt;
        }

        // 将标题转换为"主题关键词"，避免把整句标题当成需要绘制的文字
        $theme_keywords = trim(preg_replace('/[\p{P}\p{S}\s]+/u', ' ', $title));
        if ($theme_keywords === '') {
            $theme_keywords = 'general lifestyle topic';
        }

        // 静态兜底模板：引导模型生成与主题相关的具体场景
        $base = "Create a vivid, detailed image that directly illustrates the following article topic. " .
                "Focus on the core subject matter — show realistic objects, tools, interfaces, environments, or scenarios " .
                "that a reader would immediately associate with this topic. " .
                "Describe specific subjects, setting, style, lighting, and mood.";

        // 仅禁止渲染文字/水印，不禁止与主题相关的场景元素
        $constraints = " Aspect ratio 4:3. High quality, professional, clean composition. " .
            "CRITICAL: Do not render any text, letters, numbers, written characters, watermarks or logos in the image. " .
            "All surfaces should be blank or show abstract/blurred content instead of readable text.";

        $prompt = $base . " Article topic: " . $theme_keywords . "." . $constraints;

        // 兜底：若生成异常，退回到基于标题的安全英文指令
        if (!is_string($prompt) || trim($prompt) === '') {
            $prompt = 'A high-quality 4:3 photorealistic image illustrating: ' . $title;
        }

        Logger::debug('prompt-from-title-static ready (len=' . strlen($prompt) . ')', 'ImageGen');
        return $prompt;
    }
    /**
     * 处理文章内容中的图片，将本地图片转换为 WebP 格式。
     *
     * @param string &$content HTML 内容字符串 (通过引用传递)。
     * @param array  $settings 插件的设置数组。
     * @return void
     */
    public static function convert_content_images_to_webp(&$content, array $settings)
    {
        // Get setting from the correct location and check against '1'
        $enable_webp_value = $settings['global_tabs']['enable-webp-conversion'] ?? '0'; // default to '0'
        $enable_webp = ($enable_webp_value === '1'); // Explicitly check for '1'
        
        Logger::debug('WebP: start enabled=' . ($enable_webp ? 'YES' : 'NO') . ' content_len=' . strlen($content), 'ImageWebP');

        if ($enable_webp && !empty(trim($content))) { // Check trimmed content
            $gd_enabled = \extension_loaded('gd') && \function_exists('imagewebp');
            $imagick_enabled = \extension_loaded('imagick');
            
            Logger::debug('WebP: GD+WebP=' . ($gd_enabled ? 'YES' : 'NO') . ' Imagick=' . ($imagick_enabled ? 'YES' : 'NO'), 'ImageWebP');
            if ($gd_enabled || $imagick_enabled) {
                libxml_use_internal_errors(true);
                $dom = new \DOMDocument();

                $content_utf8 = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8');
                // 使用 <div> 包裹内容，避免引入 XML 声明导致后续 saveHTML 残留
                @$dom->loadHTML('<div>' . $content_utf8 . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
                libxml_clear_errors();

                $xpath = new \DOMXPath($dom);
                $images = $xpath->query('//img');
                
                Logger::debug('WebP: found ' . $images->length . ' img tags', 'ImageWebP');
                $upload_dir = wp_upload_dir();
                
                // 修改：同时支持http和https的baseurl
                $upload_baseurl = $upload_dir['baseurl'];
                $upload_baseurl_alt = str_replace('http://', 'https://', $upload_baseurl);
                $upload_basedir = $upload_dir['basedir'];

                foreach ($images as $img) {
                    $src = $img->getAttribute('src');
                    if (empty($src)) {
                        Logger::debug('WebP: skip (empty src)', 'ImageWebP');
                        continue;
                    }
                    
                    Logger::debug('WebP: found image: ' . $src, 'ImageWebP');
                    
                    $img_class = $img->getAttribute('class');
                    
                    // 修改：改进本地图片检测逻辑
                    $is_local = strpos($src, $upload_baseurl) === 0 || 
                               strpos($src, $upload_baseurl_alt) === 0;
                    if (!$is_local) {
                        Logger::debug('WebP: skip (not local): ' . $src, 'ImageWebP');
                        continue;
                    }
                    
                    $is_not_webp = !preg_match('/\.webp$/i', $src);
                    if (!$is_not_webp) {
                        Logger::debug('WebP: skip (already webp): ' . $src, 'ImageWebP');
                        continue;
                    }
                    
                    if ($is_local && $is_not_webp) {
                        // 构建文件路径 - 使用上传目录的 baseurl 而不是站点 URL
                        $relative_path = str_replace([$upload_baseurl, $upload_baseurl_alt], '', $src);
                        $original_path = $upload_basedir . $relative_path;
                        
                        Logger::debug('WebP: processing: ' . $src, 'ImageWebP');

                        if (file_exists($original_path)) {
                            $path_info = pathinfo($original_path);
                            // 检查目录是否可写
                            if (!is_writable($path_info['dirname'])) {
                                Logger::warning('WebP: skip (dir not writable): ' . $path_info['dirname'], 'ImageWebP');
                                continue;
                            }

                            $webp_path = $path_info['dirname'] . '/' . $path_info['filename'] . '.webp';
                            $webp_url = str_replace(wp_basename($src), $path_info['filename'] . '.webp', $src);
                            
                            // 如果 WebP 文件已存在，直接使用
                            if (file_exists($webp_path)) {
                                $img->setAttribute('src', $webp_url);
                                Logger::debug('WebP: used existing: ' . $src, 'ImageWebP');
                                continue;
                            }
                            
                            Logger::debug('WebP: converting: ' . $original_path, 'ImageWebP');
                            $image_converted = false;
                            // 兼容无 EXIF 扩展的环境：优先 exif_imagetype，不可用则回退 getimagesize
                            $image_type = false;
                            if (function_exists('exif_imagetype')) {
                                $image_type = @exif_imagetype($original_path);
                            }
                            if (!$image_type) {
                                $gi = @getimagesize($original_path);
                                if (is_array($gi) && isset($gi[2])) {
                                    $image_type = $gi[2];
                                }
                            }

                            if ($image_type) {
                                try {
                                    if ($gd_enabled) {
                                        $image = null;
                                        switch ($image_type) {
                                            case IMAGETYPE_JPEG: 
                                                $image = @imagecreatefromjpeg($original_path); 
                                                break;
                                            case IMAGETYPE_PNG:
                                                $image = @imagecreatefrompng($original_path);
                                                if ($image) {
                                                    imagepalettetotruecolor($image);
                                                    imagealphablending($image, true);
                                                    imagesavealpha($image, true);
                                                }
                                                break;
                                            case IMAGETYPE_GIF: 
                                                $image = @imagecreatefromgif($original_path); 
                                                break;
                                        }
                                        
                                        if ($image) {
                                            // 设置WebP质量
                                            $quality = 80;
                                            if (@imagewebp($image, $webp_path, $quality)) {
                                                $image_converted = true;
                                            }
                                            imagedestroy($image);
                                        }
                                    } elseif ($imagick_enabled) {
                                        $imagick = new \Imagick($original_path);
                                        $imagick->setImageFormat('webp');
                                        $imagick->setImageCompressionQuality(80);
                                        $imagick->stripImage();
                                        if ($imagick->writeImage($webp_path)) {
                                            $image_converted = true;
                                        }
                                        $imagick->clear();
                                        $imagick->destroy();
                                    }
                                } catch (\Exception $e) {
                                    Logger::error('WebP: convert exception: ' . $e->getMessage(), 'ImageWebP');
                                }
                            }

                            if ($image_converted) {
                                $img->setAttribute('src', $webp_url);
                                // 删除原图
                                @unlink($original_path);
                                Logger::debug('WebP: converted: ' . $src . ' -> ' . $webp_url, 'ImageWebP');
                            } else {
                                Logger::warning('WebP: conversion failed: ' . $src, 'ImageWebP');
                            }
                        } else {
                            Logger::warning('WebP: source file not found: ' . $src, 'ImageWebP');
                        }
                    }
                }

                // 优先从我们注入的 wrapper <div> 中提取内部 HTML
                $wrapper = $dom->getElementsByTagName('div')->item(0);
                if ($wrapper) {
                    $intermediate_content = '';
                    foreach ($wrapper->childNodes as $childNode) {
                        $intermediate_content .= $dom->saveHTML($childNode);
                    }
                    $content = html_entity_decode($intermediate_content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                } else {
                    // 回退：尝试 body->firstChild
                    $bodyNode = $xpath->query('//body')->item(0);
                    if ($bodyNode && $bodyNode->firstChild) {
                        $intermediate_content = '';
                        foreach ($bodyNode->firstChild->childNodes as $childNode) {
                            $intermediate_content .= $dom->saveHTML($childNode);
                        }
                        $content = html_entity_decode($intermediate_content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                    } else {
                        // 最终回退：全量保存并清理 XML/DOCTYPE 头
                        $intermediate_content = $dom->saveHTML();
                        $intermediate_content = preg_replace('/^<\?xml.*?\?>\s*/', '', $intermediate_content);
                        $intermediate_content = preg_replace('/^<!DOCTYPE.*?>\s*/i', '', $intermediate_content);
                        $content = html_entity_decode(trim($intermediate_content), ENT_QUOTES | ENT_HTML5, 'UTF-8');
                    }
                }
            } else {
                Logger::warning('WebP: no encoder available (gd/imagick missing)', 'ImageWebP');
            }
        } elseif (!$enable_webp) {
            Logger::debug('WebP: feature disabled by settings', 'ImageWebP');
        } else {
            Logger::debug('WebP: skip empty content', 'ImageWebP');
        }
    }

    /**
     * 根据文章标题和设置生成特色图片。
     *
     * @param string $post_title 文章标题。
     * @param array $task_settings 特定任务的设置。
     * @param string $plugin_file_path 插件文件路径 (PLUGIN_FILE)。
     * @param callable $image_key_generator_callback 用于生成图片主文件名的回调函数。
     * @return array|null 包含附件信息的数组，如果失败则返回 null。
     */
    public static function generate_thumbnail_with_settings(string $post_title, array $task_settings, string $plugin_file_path, callable $image_key_generator_callback): ?array
    {
        $post_title = trim($post_title);
        $api = new Article_With_Pictures_Util(1024, 768); // 4:3 默认尺寸

        // === 新增：全局设置兜底 ===
        // 当任务级未提供方式/颜色/字体等时，从全局设置 (option: ai-post) 中递归查找对应键值
        $global_settings = get_option('ai-post');
        $global_settings = is_array($global_settings) ? $global_settings : [];
        $getv = function(string $key) use ($task_settings, $global_settings) {
            if (array_key_exists($key, $task_settings)) { return $task_settings[$key]; }
            return ImageProcessor::find_setting_value($global_settings, $key);
        };

        $radio_resolved  = (string)($getv('post-image-url-2-radio') ?? '');
        $colors_resolved = (string)($getv('post-image-url-2-color') ?? '');
        $bgcolor_single  = (string)($getv('post-image-background-color') ?? '');
        $font_color      = (string)($getv('post-image-font-color') ?? '');
        $bg_dir_resolved = (string)($getv('post-image-url-2-background') ?? '');
        $font_selection  = (string)($getv('font-selection') ?? 'WeiRuanYaHei.ttf');
        $font_size_val   = (int)($getv('post-image-font-size') ?? 16);

        // 编辑器标题图：支持显式指定 4:3 及自定义尺寸
        $w_override = (int)($getv('title-image-width') ?? 0);
        $h_override = (int)($getv('title-image-height') ?? 0);
        if ($w_override > 0 && $h_override > 0) {
            try {
                $api->setWidth($w_override);
                $api->setHeight($h_override);
            } catch (\Throwable $e) {
                // ignore
            }
        }

        // 调试：输出最终解析后的关键配置（含兜底）
        Logger::debug('TitleImage: radio=' . ($radio_resolved !== '' ? $radio_resolved : 'NOT_SET') . ' colors_len=' . strlen($colors_resolved), 'TitleImage');

        // 添加缩略图文字（增强：按需解析字体路径）
        $selected = $font_selection;
        $resolved = self::resolve_font_path($selected, $plugin_file_path);
        if ($resolved !== '') {
            $api->setFontFile($resolved);
            $api->setText($post_title);
            $api->setReduceLineTextNum(3); // 每行文字限制
            $api->setMaxLineNum(4);       // 行数限制
            $api->setFontSize($font_size_val); // 使用解析后的字号
            $api->setIsMultiLine(true);   // 单行还是多行
        } else {
            Logger::warning('TitleImage: font file not found for method-2. selection=' . (string)$selected, 'TitleImage');
        }

        //背景图片
        Logger::debug('TitleImage: checking background type...', 'TitleImage');
        if ($radio_resolved === 'diy-background' && !empty($bg_dir_resolved)) {
            Logger::debug('TitleImage: entering diy-background branch', 'TitleImage');
            $background_folder = $bg_dir_resolved;
            $images = glob(ABSPATH . $background_folder . '/*.{jpg,jpeg,png,gif}', GLOB_BRACE);

            if (!empty($images)) {
                $random_image_path = $images[array_rand($images)];
                if (file_exists($random_image_path)) {
                    $bg_image_data = file_get_contents($random_image_path);
                    if ($bg_image_data !== false) {
                        $bg_image = @imagecreatefromstring($bg_image_data);
                        if ($bg_image) {
                            $width = imagesx($bg_image);
                            $height = imagesy($bg_image);
                            // Update Util's dimensions based on the actual background image
                            $api->setHeight($height);
                            $api->setWidth($width);
                            $api->setBackgroundImg($bg_image); // This method might need to accept the image resource directly
                        }
                    }
                }
            }
            // If background image is chosen but fails, it might fall back to default or color

            // Text color for DIY background image should be configurable and applied here
            if (!empty($task_settings['post-image-font-color'])) {
                $text_rgb = $api->getRGB(trim($task_settings['post-image-font-color']));
                if (!empty($text_rgb)) {
                    $api->setTextRGB($text_rgb);
                }
            }
        }
        // 背景颜色
        elseif (($task_settings['post-image-url-2-radio'] ?? '') == 'diy-background-color') {
            Logger::debug('TitleImage: entering diy-background-color branch', 'TitleImage');
            // 修复：即使 post-image-url-2-color 为空，也进入此分支，使用默认颜色列表
            $background_colors_str = (string)($getv('post-image-url-2-color') ?? '');
            
            // 如果配置为空，使用默认颜色列表
            if (empty(trim($background_colors_str))) {
                $background_colors_str = "#5b8982|#ffffff\n#45545f|#cec6b6\n#d47655|#e1f8e1\n#7379b0|#c6edec";
                Logger::debug('TitleImage: using default color list', 'TitleImage');
            }
            
            // 兼容不同换行符与分隔符（\r\n/\n/\r/逗号/分号）
            $background_colors = preg_split('/\r\n|\r|\n|,|;/', $background_colors_str);
            $valid_colors = [];
            if (is_array($background_colors)) {
                foreach ($background_colors as $color_line) {
                    $line = trim((string)$color_line);
                    if ($line === '') { continue; }
                    $parts = explode('|', $line);
                    $bg_hex = isset($parts[0]) ? trim($parts[0]) : '';
                    $tx_hex = isset($parts[1]) ? trim($parts[1]) : '';
                    if (preg_match('/^#[a-f0-9]{6}$/i', $bg_hex)) {
                        // 文本色可选：若存在且合法则一起保留
                        if ($tx_hex !== '' && !preg_match('/^#[a-f0-9]{6}$/i', $tx_hex)) {
                            // 非法文本色则忽略文本色，仅保留背景色
                            $tx_hex = '';
                        }
                        $valid_colors[] = $bg_hex . ($tx_hex !== '' ? ('|' . $tx_hex) : '');
                    }
                }
            }

            if (!empty($valid_colors)) {
                shuffle($valid_colors);
                $chosen_color_line = $valid_colors[0];
                $tmp = explode('|', $chosen_color_line);
                $text_color_applied = false;
                $background_rgb = $api->getRGB(trim($tmp[0]));
                if (!empty($background_rgb)) {
                    $api->setBackgroundRGB($background_rgb);
                    Logger::debug('TitleImage: diy-bg-color chosen bg=' . trim($tmp[0]), 'TitleImage');
                }

                if (count($tmp) == 2) {
                    $text_rgb = $api->getRGB(trim($tmp[1]));
                    if (!empty($text_rgb)) {
                        $api->setTextRGB($text_rgb);
                        Logger::debug('TitleImage: diy-bg-color chosen text=' . trim($tmp[1]), 'TitleImage');
                        $text_color_applied = true;
                    }
                }
            } else {
                // 当解析不到有效颜色时，仍在本分支内应用一组默认的非黑白配色，避免退回到安全黑白
                Logger::debug('TitleImage: diy-bg-color no valid lines, applying default palette', 'TitleImage');
                $default_bg = '#5b8982';
                $default_tx = '#ffffff';
                $bg_rgb = $api->getRGB($default_bg);
                if (!empty($bg_rgb)) { $api->setBackgroundRGB($bg_rgb); }
                $tx_rgb = $api->getRGB($default_tx);
                if (!empty($tx_rgb)) { $api->setTextRGB($tx_rgb); }
                Logger::debug('TitleImage: default palette applied bg=' . $default_bg . ' text=' . $default_tx, 'TitleImage');
            }
            // Fallback text color if not defined by background_color line
            if (empty($text_color_applied) && !empty($font_color)) {
                 $fallback_text_rgb = $api->getRGB(trim((string)$font_color));
                 if (!empty($fallback_text_rgb)) {
                     $api->setTextRGB($fallback_text_rgb);
                     Logger::debug('TitleImage: diy-bg-color fallback text=' . trim((string)$font_color), 'TitleImage');
                 }
            }
        }
        else {
            // 默认/随机配色：当既未选择自定义背景图，也未选择自定义背景色
            Logger::debug('TitleImage: entering default/random branch, colors_pool_len=' . strlen($colors_resolved), 'TitleImage');
            $applied = false;
            $colors_pool_str = (string)$colors_resolved;
            if ($colors_pool_str !== '') {
                $lines = preg_split('/\r\n|\r|\n|,|;/', $colors_pool_str);
                $valid_colors = [];
                if (is_array($lines)) {
                    foreach ($lines as $line) {
                        $line = trim((string)$line);
                        if ($line === '') { continue; }
                        $parts = explode('|', $line);
                        $bg_hex = isset($parts[0]) ? trim($parts[0]) : '';
                        $tx_hex = isset($parts[1]) ? trim($parts[1]) : '';
                        if (preg_match('/^#[a-f0-9]{6}$/i', $bg_hex)) {
                            if ($tx_hex !== '' && !preg_match('/^#[a-f0-9]{6}$/i', $tx_hex)) { $tx_hex = ''; }
                            $valid_colors[] = $bg_hex . ($tx_hex !== '' ? ('|' . $tx_hex) : '');
                        }
                    }
                }
                if (!empty($valid_colors)) {
                    shuffle($valid_colors);
                    $chosen = $valid_colors[0];
                    $parts = explode('|', $chosen);
                    $bg_rgb = $api->getRGB(trim($parts[0]));
                    if (!empty($bg_rgb)) {
                        $api->setBackgroundRGB($bg_rgb);
                        Logger::debug('TitleImage: default/random bg=' . trim($parts[0]), 'TitleImage');
                        $applied = true;
                    }
                    if (count($parts) === 2) {
                        $tx_rgb = $api->getRGB(trim($parts[1]));
                        if (!empty($tx_rgb)) {
                            $api->setTextRGB($tx_rgb);
                            Logger::debug('TitleImage: default/random text=' . trim($parts[1]), 'TitleImage');
                        }
                    }
                }
            }
            if (!$applied) {
                $bg_hex = (string)$bgcolor_single;
                $tx_hex = (string)$font_color;
                if ($bg_hex !== '' && preg_match('/^#[a-f0-9]{6}$/i', $bg_hex)) {
                    $bg_rgb = $api->getRGB($bg_hex);
                    if (!empty($bg_rgb)) { $api->setBackgroundRGB($bg_rgb); }
                    Logger::debug('TitleImage: fallback bg=' . $bg_hex, 'TitleImage');
                }
                if ($tx_hex !== '' && preg_match('/^#[a-f0-9]{6}$/i', $tx_hex)) {
                    $tx_rgb = $api->getRGB($tx_hex);
                    if (!empty($tx_rgb)) { $api->setTextRGB($tx_rgb); }
                    Logger::debug('TitleImage: fallback text=' . $tx_hex, 'TitleImage');
                }
                if ($bg_hex === '' && $tx_hex === '') {
                    $bg_rgb = $api->getRGB('#222222');
                    $tx_rgb = $api->getRGB('#ffffff');
                    if (!empty($bg_rgb)) { $api->setBackgroundRGB($bg_rgb); }
                    if (!empty($tx_rgb)) { $api->setTextRGB($tx_rgb); }
                    Logger::debug('TitleImage: applied safe defaults bg=#222222 text=#ffffff', 'TitleImage');
                }
            }
        }

        $upload_dir = wp_upload_dir();
        if (!is_writable($upload_dir['path'])) {
            return null;
        }

        // 文件名
        $img_filename_base = call_user_func($image_key_generator_callback, $post_title);
        if (empty($img_filename_base)) {
            return null;
        }

        // 文件扩展
        $post_mime_type = 'image/png'; // Defaulting to PNG as per original logic
        $img_filename = $img_filename_base . '.png';

        // 获取一个在上传目录中唯一的文件名路径
        $unique_filename = wp_unique_filename( $upload_dir['path'], $img_filename );
        $full_save_path = $upload_dir['path'] . '/' . $unique_filename;
        // 更新 img_filename 以反映可能由 wp_unique_filename 做的更改
        $img_filename = $unique_filename; 

        $result = $api->saveImage($full_save_path);

        if (empty($result) || !file_exists($full_save_path)) {
            return null;
        }

        // --- START: 将生成的图片注册为媒体库附件 ---
        $filetype = wp_check_filetype(basename($full_save_path), null);
        $post_mime_type = $filetype['type'] ?: 'image/png'; // 更新MIME类型

        // 准备附件数据
        $attachment_data = array(
            'guid'           => $upload_dir['url'] . '/' . basename($img_filename), // 直接URL到文件
            'post_mime_type' => $post_mime_type,
            'post_title'     => preg_replace('/\\.[^.]+$/', '', basename($img_filename)), // 使用文件名作为标题
            'post_content'   => '', // 可选：添加描述
            'post_status'    => 'inherit'
        );

        // 将文件插入媒体库
        $attachment_id = wp_insert_attachment($attachment_data, $full_save_path, 0); // 0表示没有父帖子

        if (is_wp_error($attachment_id)) {
            return null;
        }

        // 加载WordPress图像处理函数 (如果尚未加载)
        if (!function_exists('wp_generate_attachment_metadata')) {
            require_once(ABSPATH . 'wp-admin/includes/image.php');
        }

        // 生成附件元数据 (例如缩略图)
        $attachment_meta = wp_generate_attachment_metadata($attachment_id, $full_save_path);
        if (is_wp_error($attachment_meta) || empty($attachment_meta)) {
        } else {
            wp_update_attachment_metadata($attachment_id, $attachment_meta);
        }
        // --- END: 将生成的图片注册为媒体库附件 ---

        // 确保我们使用从媒体库获取的最终URL
        $guid_from_wp_get_attachment_url = wp_get_attachment_url($attachment_id);
        $final_attachment_info = [
            'id'             => $attachment_id,
            'guid'           => $guid_from_wp_get_attachment_url,
            'file'           => $full_save_path,
            'type'           => $post_mime_type,
            // 保留旧结构中可能被依赖的字段，尽管它们现在不直接用于媒体库操作
            'post_mime_type' => $post_mime_type, 
            'post_title'     => $post_title, // 原始传入的标题
            'post_content'   => '',
            'post_status'    => 'inherit',
        ];

        return $final_attachment_info;
    }

    /**
     * 从远程URL下载图片到WordPress媒体库，并返回HTML img标签。
     *
     * @param string $remote_image_url 远程图片的URL。
     * @param bool   $add_alt_attribute 是否应包含 alt 属性。
     * @return string 成功时返回HTML img标签，失败时返回空字符串。
     */
    public static function download_image_and_get_html_tag(string $remote_image_url, bool $add_alt_attribute, int $post_id = 0, bool $set_as_featured = false): string
    {
        // 统一走标准化流程，保持向后兼容：默认不绑定父子关系且不设为特色图
        $res = self::attach_remote_image_to_post($post_id, $remote_image_url, $set_as_featured, '', 60);
        if (!$res['ok'] || empty($res['url'])) {
            // 保底策略：侧载失败则直接使用远程 URL，保证文章可见图片
            Logger::warning('AttachImage: sideload failed, fallback to remote IMG: ' . $remote_image_url . ' | error=' . ($res['error'] ?? 'unknown'), 'ImageSideload');
            $alt_attr_str = $add_alt_attribute ? ' alt=""' : '';
            return sprintf('<img src="%s"%s />', esc_url($remote_image_url), $alt_attr_str);
        }
        Logger::debug('AttachImage: return img tag, attachment_id=' . ($res['attachment_id'] ?? 'n/a'), 'ImageSideload');
        $alt_attr_str = $add_alt_attribute ? ' alt=""' : '';
        return sprintf('<img src="%s"%s />', esc_url($res['url']), $alt_attr_str);
    }

    /**
     * Processes and updates alt attributes for images within HTML content.
     *
     * @param string &$content The HTML content (passed by reference).
     * @param string $base_alt_text The base text for alt attributes.
     * @param bool $auto_alt_enabled_setting Whether automatic alt text generation/incrementing is enabled.
     * @return void
     */
    public static function process_and_update_alt_attributes(string &$content, string $base_alt_text, bool $auto_alt_enabled_setting): void
    {
        // --- BEGIN FIX: Check if the setting is enabled before proceeding --- 
        if (!$auto_alt_enabled_setting) {
            // If the setting to automatically add/update alt attributes is disabled, do nothing.
            return;
        }
        // --- END FIX ---
        
        if (empty(trim($content)) || empty(trim($base_alt_text))) {
            return;
        }

        $alt_counter = 1; // Initialize counter locally

        // Regex to add/update ALT attributes for images that might already have one
        $content = preg_replace_callback(
            '/<img((?!alt=)[^>]*?)(alt=(["\'])?)(.*?)\3?((?:\s+[^>]*)?)>/i',
            function($matches) use ($base_alt_text, &$alt_counter, $auto_alt_enabled_setting) {
                $new_alt_text = $base_alt_text;
                if ($auto_alt_enabled_setting) {
                    // Ensure NumberUtils is accessible. If it's in the same Features namespace, direct call is fine.
                    // Otherwise, ensure HaoZiTeam\AIPost\Service\Features\NumberUtils is used or imported.
                    $chineseNum = NumberUtils::numberToChinese($alt_counter);
                    $new_alt_text = $base_alt_text . ' ' . $chineseNum;
                    $alt_counter++;
                }
                return '<img' . $matches[1] . 'alt=' . $matches[3] . esc_attr($new_alt_text) . $matches[3] . $matches[5] . '>';
            },
            $content
        );

        // Regex to add ALT attributes for images that DO NOT have one
        $content = preg_replace_callback(
            '/<img((?:(?!alt=).)*?)>/i', // Matches img tags without an alt attribute
            function($matches) use ($base_alt_text, &$alt_counter, $auto_alt_enabled_setting) {
                $new_alt_text = $base_alt_text;
                if ($auto_alt_enabled_setting) {
                    $chineseNum = NumberUtils::numberToChinese($alt_counter);
                    $new_alt_text = $base_alt_text . ' ' . $chineseNum;
                    $alt_counter++;
                }
                // WordPress's esc_attr() should be available globally.
                return '<img' . $matches[1] . ' alt="' . esc_attr($new_alt_text) . '">';
            },
            $content
        );
        // error_log is a global PHP function, so it can be used directly.
    }

    /**
     * 通过智谱 AI 文生图 API 生成图片，并返回可下载 URL。
     * 支持模型：cogview-3-flash（免费）、cogview-4、cogview-4-250304、glm-image
     * 端点：POST https://open.bigmodel.cn/api/paas/v4/images/generations
     * 文档：https://docs.bigmodel.cn/api-reference/模型-api/图像生成
     *
     * @param string $prompt 图片描述文本
     * @param array  $settings 插件全局设置（含 zhipu-accounts 等）
     * @param array|null $task_settings 任务级设置（可覆盖模型/尺寸）
     * @return array|WP_Error 成功返回 ['url'=>..., 'model'=>..., 'size'=>..., 'mime'=>'image/jpeg']，失败返回 WP_Error
     */
    public static function generate_image_via_zhipu(string $prompt, array $settings, ?array $task_settings = null)
    {
        // 合并设置：任务级优先
        $ctx = is_array($task_settings) && !empty($task_settings) ? $task_settings : $settings;

        // 读取模型（任务级 > 全局级 > 默认）
        $model = (string)(
            self::find_setting_value($ctx, 'zhipu-imagegen-model-task')
            ?? self::find_setting_value($settings, 'zhipu-imagegen-model')
            ?? 'cogview-3-flash'
        );

        // 读取尺寸
        $size = '';
        if ($model === 'glm-image') {
            // glm-image 推荐尺寸：1280x1280(默认), 1568x1056, 1056x1568, 1472x1088, 1088x1472, 1728x960, 960x1728
            $aspect = (string)(self::find_setting_value($ctx, 'zhipu-imagegen-aspect-task') ?? '');
            if ($aspect === '16:9') {
                $size = '1728x960';
            } elseif ($aspect === '4:3') {
                $size = '1472x1088';
            } elseif ($aspect === '1:1') {
                $size = '1280x1280';
            } else {
                $size = (string)(self::find_setting_value($ctx, 'zhipu-imagegen-size-task') ?? '1280x1280');
            }
        } else {
            // cogview 系列推荐尺寸：1024x1024(默认), 768x1344, 864x1152, 1344x768, 1152x864, 1440x720, 720x1440
            $aspect = (string)(self::find_setting_value($ctx, 'zhipu-imagegen-aspect-task') ?? '');
            if ($aspect === '16:9') {
                $size = '1440x720';
            } elseif ($aspect === '4:3') {
                $size = '1152x864';
            } elseif ($aspect === '1:1') {
                $size = '1024x1024';
            } else {
                $size = (string)(self::find_setting_value($ctx, 'zhipu-imagegen-size-task') ?? '1024x1024');
            }
        }

        // 归一化尺寸分隔符
        $size = str_replace(['X', '×', '*'], 'x', trim($size));

        // 选择 API Key：从 zhipu-accounts 中随机选取
        $accounts = $settings['zhipu-accounts'] ?? [];
        if (!is_array($accounts) || empty($accounts)) {
            Logger::error('Zhipu: no zhipu accounts configured', 'ImageGen');
            return new \WP_Error('no_account', 'No Zhipu accounts configured.');
        }
        $account = $accounts[array_rand($accounts)];
        $apiKey = trim((string)($account['account-secret-key'] ?? ''));
        if ($apiKey === '') {
            Logger::error('Zhipu: selected account has empty api key', 'ImageGen');
            return new \WP_Error('invalid_account', 'Selected Zhipu account has empty API key.');
        }

        // 构建请求体
        $endpoint = 'https://open.bigmodel.cn/api/paas/v4/images/generations';
        $payload = [
            'model'  => $model,
            'prompt' => (string)$prompt,
            'size'   => $size,
        ];

        // 可选参数
        $qualityVal = self::find_setting_value($ctx, 'zhipu-imagegen-quality-task');
        if (is_string($qualityVal) && in_array($qualityVal, ['hd', 'standard'], true)) {
            $payload['quality'] = $qualityVal;
        }
        // 水印控制（默认关闭）
        $watermarkVal = self::find_setting_value($ctx, 'zhipu-imagegen-watermark');
        $payload['watermark'] = $watermarkVal !== null ? (bool)$watermarkVal : false;

        $prompt_len = strlen((string)$prompt);
        Logger::debug('Zhipu: request: model=' . $model . ' size=' . $size . ' prompt_len=' . $prompt_len, 'ImageGen');

        // 使用插件自带 CA 证书
        $pluginRoot = \dirname(__DIR__, 2);
        $caFile = $pluginRoot . '/resources/cacert.pem';
        $caExists = @file_exists($caFile);

        $ch = \curl_init($endpoint);
        if ($ch === false) {
            return new \WP_Error('curl_init_failed', 'Failed to initialize cURL.');
        }
        $headers = [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $apiKey,
        ];
        $json = wp_json_encode($payload);
        \curl_setopt($ch, CURLOPT_POST, true);
        \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        \curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        \curl_setopt($ch, CURLOPT_TIMEOUT, 120);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        \curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        if ($caExists) {
            \curl_setopt($ch, CURLOPT_CAINFO, $caFile);
        }

        $response = \curl_exec($ch);
        $errno = \curl_errno($ch);
        $error = \curl_error($ch);
        $httpCode = (int)\curl_getinfo($ch, CURLINFO_HTTP_CODE);
        \curl_close($ch);

        if ($errno) {
            Logger::error('Zhipu: cURL error (' . $errno . '): ' . $error, 'ImageGen');
            return new \WP_Error('curl_error', 'cURL error: ' . $error);
        }
        if ($httpCode < 200 || $httpCode >= 300) {
            Logger::error('Zhipu: HTTP ' . $httpCode . ' body=' . substr((string)$response, 0, 500), 'ImageGen');
            return new \WP_Error('http_error', 'HTTP status ' . $httpCode);
        }

        $data = json_decode((string)$response, true);
        if (!is_array($data)) {
            Logger::error('Zhipu: invalid JSON response', 'ImageGen');
            return new \WP_Error('bad_json', 'Invalid JSON response.');
        }
        if (!empty($data['error'])) {
            $errMsg = is_string($data['error']) ? $data['error'] : (is_array($data['error']) ? ($data['error']['message'] ?? wp_json_encode($data['error'])) : 'API error');
            Logger::error('Zhipu: api error: ' . $errMsg, 'ImageGen');
            return new \WP_Error('api_error', $errMsg);
        }

        // 解析返回的图片 URL：{ data: [{ url: "..." }] }
        $url = '';
        if (!empty($data['data']) && is_array($data['data'])) {
            $first = $data['data'][0] ?? null;
            if (is_array($first)) {
                $url = (string)($first['url'] ?? '');
            }
        }
        if (!is_string($url) || $url === '') {
            Logger::error('Zhipu: response has no image url', 'ImageGen');
            return new \WP_Error('no_url', 'No image URL in response.');
        }

        Logger::info('Zhipu: success model=' . $model . ' size=' . $size, 'ImageGen');
        return [
            'url'   => $url,
            'model' => $model,
            'size'  => $size,
            'mime'  => 'image/jpeg',
        ];
    }
} 