<?php

namespace HaoZiTeam\AIPost\Service;

defined('ABSPATH') || exit;

use Exception;
use PicturesUtil\Article_With_Pictures_Util;
use HaoZiTeam\ChatGPT\V2;

// 新增：用于处理DOM
use DOMDocument;
use DOMXPath;
use DOMElement;

use HaoZiTeam\AIPost\Service\Features\ImageProcessor;
use HaoZiTeam\AIPost\Service\Features\ApiClientService; // Added this line
use HaoZiTeam\AIPost\Service\Features\LanguageUtils; 
use HaoZiTeam\AIPost\Service\Features\TimeUtils;      // Added
use HaoZiTeam\AIPost\Service\Features\Markdown;       // Added
use HaoZiTeam\AIPost\Service\Features\StyleUtils;     // Added
use HaoZiTeam\AIPost\Service\Features\MetaTags;       // Added
use HaoZiTeam\AIPost\Service\Features\HtmlUtils; // Added
use HaoZiTeam\AIPost\Service\Features\CronUtils;      // Added

class Base
{
	private $settings;
	private $currentKey;
	private $headBuffering = false;

	public function __construct()
{
		// 方案A：统一任务读取为 ai-post-tasks（兼容回退到 ai-post.tasks）
		try {
			$tasks_opt = get_option('ai-post-tasks', []);
			if (is_array($tasks_opt) && !empty($tasks_opt)) {
				// 优先使用独立任务表
				$this->settings = get_option('ai-post');
				if (!is_array($this->settings)) { $this->settings = []; }
				$this->settings['tasks'] = $tasks_opt;
				\error_log('AI Post Debug: 使用 ai-post-tasks 作为任务源，count=' . count($tasks_opt));
			} elseif (is_array($this->settings) && !empty($this->settings['tasks']) && is_array($this->settings['tasks'])) {
				// 回退：旧结构，存在于 ai-post.tasks
				$this->settings = get_option('ai-post');
				\error_log('AI Post Debug: 检测到旧结构（ai-post.tasks）作为任务源，count=' . count($this->settings['tasks']));
			} else {
				// 无任务配置
				$this->settings = get_option('ai-post');
				\error_log('AI Post Debug: 未检测到任务配置（ai-post-tasks 与 ai-post.tasks 均为空）');
			}

		} catch (\Throwable $e) {
			\error_log('AI Post Debug: 读取任务源时发生异常: ' . $e->getMessage());
		}

		// 加载媒体库
		if (!function_exists('media_sideload_image')) {
			require_once(ABSPATH . 'wp-admin/includes/media.php');
			require_once(ABSPATH . 'wp-admin/includes/file.php');
			require_once(ABSPATH . 'wp-admin/includes/image.php');
		}


		// 注册定时任务
		add_action('init', array($this, 'plugin_init'));
		add_action('ai_post_cron', array($this, 'ai_post_cron'));

		// 新增头部CSS输出
		add_action('wp_head', [$this, 'add_global_css_to_head']);

		// 新增页脚JS输出 (用于TOC)
		add_action('wp_footer', [$this, 'add_toc_script_to_footer']);

		// 方案改为“缓冲并注入”，不打乱主题顺序：
		// 1) 在 wp_head 最早开启缓冲
		add_action('wp_head', [$this, 'start_head_buffer'], 0);
		// 2) 在 wp_head 最后关闭缓冲并把 AI-Post 的 meta 插在主题 description/keywords 之后；若不存在，则插在 </title> 之后
		add_action('wp_head', [$this, 'end_head_buffer_and_inject_meta'], 9999);

        // 新增Schema.org JSON-LD输出到页脚
        add_action('wp_footer', [$this, 'output_schema_json_ld'], 20); // Add schema output hook

        // 新增自定义CSS设置项注册
        add_filter('ai_post_settings', [$this, 'register_custom_css_setting']);

        // 统一策略：数据库直接存储 HTML 十进制实体，前端无需解码过滤
        // 因此不再注册 the_content/the_excerpt 的 Unicode 解码过滤器
        // 后台编辑器也无需“\u→实体”转换过滤器

		// --- START: 添加 language_attributes 过滤器 ---
		add_filter('language_attributes', ['\HaoZiTeam\AIPost\Service\Features\LanguageUtils', 'add_html_lang_attribute']);
		// --- END: 添加 language_attributes 过滤器 ---

		// 注册更新
		// new Update();

		// 注册文章保存钩子
//		add_action('save_post', [$this, 'add_internal_links'], 20, 3);

		// 注册自定义图片尺寸
		add_image_size('ai_custom_thumbnail', 300, 225, false);

	}

    // ---- Head 注入方案：缓冲开始 ----
    public function start_head_buffer(): void
    {
        if (is_admin()) { return; }
        if (!$this->headBuffering) {
            $this->headBuffering = true;
            ob_start();
        }
    }

    // ---- Head 注入方案：缓冲结束并注入 AI-Post Meta ----
    public function end_head_buffer_and_inject_meta(): void
    {
        if (!$this->headBuffering) { return; }
        $this->headBuffering = false;
        $out = ob_get_clean();
        if (!is_string($out) || $out === '') { echo $out; return; }

        $ai_meta = $this->build_ai_meta_tags_html();
        if ($ai_meta === '') { echo $out; return; }

        $lastPos = -1; $lastLen = 0;
        $patterns = [
            '/<meta[^>]+name=["\']description["\'][^>]*>/i',
            '/<meta[^>]+name=["\']keywords["\'][^>]*>/i',
        ];
        foreach ($patterns as $re) {
            if (preg_match_all($re, $out, $m, PREG_OFFSET_CAPTURE)) {
                $last = end($m[0]);
                if ($last && is_array($last)) { $pos = (int)$last[1]; $len = strlen($last[0]);
                    if ($pos >= 0 && ($pos + $len) > ($lastPos + $lastLen)) { $lastPos = $pos; $lastLen = $len; }
                }
            }
        }

        $insertAt = -1;
        if ($lastPos >= 0) {
            $insertAt = $lastPos + $lastLen;
        } else {
            $tPos = strripos($out, '</title>');
            if ($tPos !== false) { $insertAt = $tPos + strlen('</title>'); }
        }

        if ($insertAt >= 0) {
            $new = substr($out, 0, $insertAt) . "\n" . $ai_meta . substr($out, $insertAt);
            echo $new;
        } else {
            echo $out . "\n" . $ai_meta;
        }
    }

    // 构建 AI-Post 的 Meta 片段（不直接 echo），供缓冲注入使用
    private function build_ai_meta_tags_html(): string
    {
        if (is_admin()) { return ''; }
        global $post;
        if (!is_single() || !isset($post->ID)) { return ''; }

        $desc_method = $this->settings['global_tabs']['meta_description_generation_method'] ?? 'extract';
        $keywords_enabled = $this->settings['global_tabs']['seo_keywords_enabled'] ?? false;

        $meta_keywords = get_post_meta($post->ID, '_ai_post_keywords', true);
        $meta_description = get_post_meta($post->ID, '_ai_post_description', true);

        $buf = "\n<!-- AI Post SEO Meta (injected) -->\n";

        $add_publisher_meta = $this->settings['global_tabs']['schema_enable_publisher'] ?? true;
        if ($add_publisher_meta) {
            $publisher_name = get_bloginfo('name');
            if ($publisher_name) { $buf .= '<meta name="publisher" content="' . esc_attr($publisher_name) . '">' . "\n"; }
        }

        $author_name = get_the_author_meta('display_name', $post->post_author);
        if ($author_name) { $buf .= '<meta name="author" content="' . esc_attr($author_name) . '">' . "\n"; }

        if ($keywords_enabled) {
            if (empty($meta_keywords)) {
                $post_tags = get_the_tags($post->ID);
                if ($post_tags) { $meta_keywords = MetaTags::format_tags_as_keywords_string($post_tags, 5); }
            }
            if (!empty($meta_keywords)) { $buf .= '<meta name="keywords" content="' . esc_attr($meta_keywords) . '">' . "\n"; }
        }

        if ($desc_method === 'ai' || $desc_method === 'extract') {
            if (!empty($meta_description)) {
                $min_length = 25;
                $is_valid_description = mb_strlen($meta_description) >= $min_length && !preg_match('/^[\pP\s]+$/u', $meta_description);
                if ($is_valid_description) {
                    $buf .= '<meta name="description" content="' . esc_attr($meta_description) . '">';
                } else if (isset($post->post_content)) {
                    $fallback_desc = mb_substr(wp_strip_all_tags($post->post_content), 0, 160);
                    if (!empty($fallback_desc)) { $buf .= '<meta name="description" content="' . esc_attr($fallback_desc) . '">'; }
                }
            } else if (isset($post->post_content)) {
                $fallback_desc = mb_substr(wp_strip_all_tags($post->post_content), 0, 160);
                if (!empty($fallback_desc)) { $buf .= '<meta name="description" content="' . esc_attr($fallback_desc) . '">'; }
            }
        }

        $trimmed = trim(str_replace(['<!-- AI Post SEO Meta (injected) -->'], '', $buf));
        if ($trimmed === '') { return ''; }
        return $buf;
    }

    /**
     * 在 <head> 输出全局自定义 CSS（若已配置）。
     * 安全兜底：若未配置则不输出，避免影响现有主题与页面。
     */
    public function add_global_css_to_head(): void
    {
        try {
            $css = '';
            // 多路径兼容：优先读取设置数组中的常见键位，其次读取独立选项
            if (is_array($this->settings)) {
                $css = $this->settings['custom_css']
                    ?? ($this->settings['global_tabs']['custom_css'] ?? '')
                    ?? ($this->settings['appearance']['custom_css'] ?? '');
                $css = (string) $css;
            }
            if ($css === '') {
                $opt = get_option('ai_post_custom_css');
                if (is_string($opt)) { $css = $opt; }
            }
            $css = trim((string)$css);
            if ($css === '') { return; }
            echo "\n<style id=\"ai-post-global-css\">\n" . $css . "\n</style>\n";
        } catch (\Throwable $e) {
            // 静默失败，避免影响前端渲染
        }
    }

    /**
     * 根据全局设定，决定是否将发布的标题与正文以 Unicode 转义形式（\\uXXXX）入库。
     * 默认保持 UTF-8 文本；当设置为 unicode 时，执行转义。
     *
     * @param string &$title
     * @param string &$content
     * @return void
     */
    private function maybe_apply_publish_encoding(string &$title, string &$content): void
    {
        try {
            // 根据设置切换：html-entity => 正文转为 &#NNNN;；utf8 => 保持原样
            $mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
            if ($mode === 'html-entity') {
                $content = $this->encode_html_entities_for_publish($content);
            }
            return;
        } catch (\Throwable $e) {
            // 静默失败，避免影响发布
        }
    }

    /**
     * 将给定 UTF-8 字符串转为 Unicode 转义文本（使用双反斜杠前缀），例如 “你好/测试” => "\\\\u4f60\\\\u597d/\\\\u6d4b\\\\u8bd5"。
     * 说明：WordPress 在入库/输出过程中会多次 addslashes/stripslashes/esc_attr，
     * 为了确保页面最终仍能看到单个反斜杠形式的 \uXXXX，需要在存储时写入双反斜杠。
     * 若编码失败，返回原文。
     */
    private function encode_unicode_for_publish(string $s): string
    {
        // 将非 ASCII 字符转为 \uXXXX（或代理项对），保留 ASCII（包括 < > / 等 HTML 标签字符）
        $out = '';
        $len = mb_strlen($s, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $ch = mb_substr($s, $i, 1, 'UTF-8');
            // ASCII 直接保留
            if (strlen($ch) === 1) { // 单字节，必为 ASCII
                $out .= $ch;
                continue;
            }
            $u4 = @iconv('UTF-8', 'UCS-4BE', $ch);
            if ($u4 === false || $u4 === null) {
                // 回退原字符
                $out .= $ch;
                continue;
            }
            $codepoint = unpack('N', $u4)[1];
            // 保留常见的 CJK 全角标点与引号/括号/省略号/破折号，不进行 \uXXXX 转义
            // 这样可以避免“所有内容拼成一串”而丢失可视标点
            $preserve_map = [
                0x3000, // IDEOGRAPHIC SPACE　
                0x3001, // 、
                0x3002, // 。
                0xFF0C, // ，
                0xFF1B, // ；
                0xFF1A, // ：
                0xFF1F, // ？
                0xFF01, // ！
                0x2014, // —
                0x2026, // …
                0x300A, // 《
                0x300B, // 》
                0xFF08, // （
                0xFF09, // ）
                0x3008, // 〈
                0x3009, // 〉
                0x3010, // 【
                0x3011, // 】
                0x201C, // “
                0x201D, // ”
                0x2018, // ‘
                0x2019, // ’
            ];
            if (in_array($codepoint, $preserve_map, true)) {
                $out .= $ch; // 标点保留原样（UTF-8）
                continue;
            }
            if ($codepoint <= 0xFFFF) {
                // 使用双反斜杠，避免在多次转义后反斜杠被吃掉
                $out .= sprintf('\\\\u%04x', $codepoint);
            } else {
                // 生成 UTF-16 代理项对
                $cp = $codepoint - 0x10000;
                $high = 0xD800 | (($cp >> 10) & 0x3FF);
                $low  = 0xDC00 | ($cp & 0x3FF);
                // 同样使用双反斜杠
                $out .= sprintf('\\\\u%04x\\\\u%04x', $high, $low);
            }
        }
        return $out;
    }

    /**
     * 将 UTF-8 文本中的非 ASCII 字符编码为 HTML 十进制实体（&#NNNN;）。
     * - 标题不处理，仅正文在发布前处理；
     * - 保留常见 CJK 标点原样，避免视觉/SEO影响；
     * - 不会破坏 HTML 标签/ASCII 内容；
     * - 已存在的实体因为是 ASCII 序列（如 & # 3 0 4 6 ;）会被原样保留，不会二次编码。
     */
    private function encode_html_entities_for_publish(string $s): string
    {
        $out = '';
        $len = mb_strlen($s, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $ch = mb_substr($s, $i, 1, 'UTF-8');
            if ($ch === '') { continue; }
            if (strlen($ch) === 1) { // ASCII
                $out .= $ch;
                continue;
            }
            $u4 = @iconv('UTF-8', 'UCS-4BE', $ch);
            if ($u4 === false || $u4 === null) {
                $out .= $ch; // 兜底：保留原样
                continue;
            }
            $codepoint = unpack('N', $u4)[1];
            // 保留常见中文全角标点
            $preserve_map = [
                0x3000, 0x3001, 0x3002, 0xFF0C, 0xFF1B, 0xFF1A, 0xFF1F, 0xFF01,
                0x2014, 0x2026, 0x300A, 0x300B, 0xFF08, 0xFF09, 0x3008, 0x3009,
                0x3010, 0x3011, 0x201C, 0x201D, 0x2018, 0x2019,
            ];
            if (in_array($codepoint, $preserve_map, true)) {
                $out .= $ch;
                continue;
            }
            // 其它非 ASCII 字符编码为十进制实体
            $out .= '&#' . $codepoint . ';';
        }
        return $out;
    }

    /**
     * 前端渲染阶段把 \uXXXX 或 \\uXXXX 形式的转义还原为 UTF-8 文本。
     * - 仅当全局设置 publish-content-encoding=unicode 时启用；
     * - 支持 UTF-16 代理项对（高低位组合）；
     * - 只处理安全的文本范围，不改动 HTML 标签边界（正则仅匹配普通文本中的序列）。
     */
    public function decode_unicode_on_render(string $html): string
    {
        try {
            $mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
            if ($mode !== 'unicode') {
                return $html;
            }
            if ($html === '') { return $html; }

            // 将双反斜杠规范化为单反斜杠再进行解析（显示链路可能带来 \\u）
            $norm = str_replace('\\\\u', '\\u', $html);

            // 先处理代理项对：\uD800-\uDBFF + \uDC00-\uDFFF
            $norm = preg_replace_callback('/\\u(d[89ab][0-9a-f]{2})\\u(d[cdef][0-9a-f]{2})/i', function ($m) {
                $high = hexdec($m[1]);
                $low  = hexdec($m[2]);
                $cp = (($high - 0xD800) << 10) + ($low - 0xDC00) + 0x10000;
                return $this->codepoint_to_utf8($cp);
            }, $norm);

            // 再处理基本多文种平面的 \uXXXX
            $norm = preg_replace_callback('/\\u([0-9a-f]{4})/i', function ($m) {
                $cp = hexdec($m[1]);
                return $this->codepoint_to_utf8($cp);
            }, $norm);

            return $norm;
        } catch (\Throwable $e) {
            return $html; // 静默失败
        }
    }

    /** 将码点转为 UTF-8 */
    private function codepoint_to_utf8(int $cp): string
    {
        if ($cp <= 0x7F) {
            return chr($cp);
        } elseif ($cp <= 0x7FF) {
            return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
        } elseif ($cp <= 0xFFFF) {
            return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        } elseif ($cp <= 0x10FFFF) {
            return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        }
        return '';
    }

    /**
     * 后台编辑器：将 \uXXXX/\\uXXXX 显示为 HTML 十进制实体（例如 &#30446;），便于可视化预览。
     * 仅在 publish-content-encoding===unicode 时执行。
     */
    public function unicode_to_entities_for_editor(string $content): string
    {
        try {
            $mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
            if ($mode !== 'unicode' || $content === '') { return $content; }

            // 规范成单反斜杠形式再匹配
            $norm = str_replace('\\\\u', '\\u', $content);

            // 代理项对 -> 码点
            $norm = preg_replace_callback('/\\u(d[89ab][0-9a-f]{2})\\u(d[cdef][0-9a-f]{2})/i', function ($m) {
                $high = hexdec($m[1]);
                $low  = hexdec($m[2]);
                $cp = (($high - 0xD800) << 10) + ($low - 0xDC00) + 0x10000;
                return '&#' . $cp . ';';
            }, $norm);

            // 基本平面 -> 十进制实体
            $norm = preg_replace_callback('/\\u([0-9a-f]{4})/i', function ($m) {
                $cp = hexdec($m[1]);
                return '&#' . $cp . ';';
            }, $norm);

            // 一些环境下，编辑器预处理会对文本再次加反斜杠，导致出现 "\&#30446;" 之类情况。
            // 仅移除“出现在 HTML 实体（十进制/十六进制/命名）前的单个反斜杠”，不影响其它位置。
            $norm = preg_replace('/\\\\(?=&(?:#[0-9]{2,7}|#x[0-9a-fA-F]+|[A-Za-z][A-Za-z0-9]+);)/', '', $norm);

            return $norm;
        } catch (\Throwable $e) {
            return $content;
        }
    }

    // 占位的 add_toc_script_to_footer()/output_schema_json_ld() 已移除，避免与后文完整实现重复声明

    // 占位的 register_custom_css_setting() 已移除，避免与后文完整实现重复声明

    /**
     * 仅执行豆包文生图“配图方式一(分散)”插入，不触发标题图生成流程
     *
     * @param string $content 原文内容
     * @param string $alt_text 通常为标题
     * @param array  $attr 可选img属性
     * @return string 修改后的内容
     */
    public function insert_doubao_images_distributed($content, $alt_text, $attr = [])
    {
        $key = $this->currentKey;
        try {
            $task_settings = $this->settings['tasks'][$key] ?? [];
            $enable_doubao_first = (bool)($task_settings['enable-doubao-imagegen'] ?? false);
            if ($enable_doubao_first) {
                \error_log('[AI-Post][InsertImage] distributed-only: task_key=' . (string)$key);

                // 提供商选择：默认豆包，兼容未来扩展
                $provider = (string)($task_settings['imagegen-provider'] ?? 'doubao');

                // 按提供商读取数量：阿里云独立于豆包
                if ($provider === 'aliyun') {
                    $img_count = (int)($task_settings['aliyun-imagegen-n-task'] ?? 1);
                    // 模型数量约束
                    $aliyun_model = (string)($task_settings['aliyun-imagegen-model-task'] ?? '');
                    if ($aliyun_model === 'qwen-image') {
                        $img_count = 1; // qwen 强制单张
                    } else {
                        if ($img_count < 1) { $img_count = 1; }
                        if ($img_count > 4) { $img_count = 4; } // WAN 系列常用上限
                    }
                } else {
                    // 豆包使用自身数量配置
                    $img_count = (int)($task_settings['doubao-imagegen-image-count'] ?? 1);
                    if ($img_count < 1) { $img_count = 1; }
                    if ($img_count > 4) { $img_count = 4; }
                }
                \error_log('[AI-Post][InsertImage] distributed-only: provider=' . $provider . ' img_count=' . $img_count);

                // 生成一次 Prompt
                $prompt = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_prompt_from_title((string)$alt_text, $this->settings);
                $download_to_media = (bool)($task_settings['doubao-imagegen-download-to-media'] ?? true);

                $figures = [];
                for ($i = 0; $i < $img_count; $i++) {
                    if ($provider === 'aliyun') {
                        $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_aliyun_dashscope($prompt, $this->settings);
                    } else {
                        $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
                    }
                    if (is_wp_error($gen)) {
                        \error_log('[AI-Post][InsertImage] distributed-only: gen failed #' . ($i+1) . ' provider=' . $provider . ' => ' . $gen->get_error_message());
                        continue;
                    }
                    $final_url = '';
                    if (is_array($gen) && !empty($gen['url'])) {
                        if ($download_to_media) {
                            $attach = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::attach_remote_image_to_post(0, (string)$gen['url'], false, '', 60);
                            if (is_array($attach) && !empty($attach['ok']) && !empty($attach['url'])) {
                                $final_url = (string)$attach['url'];
                            } else {
                                $final_url = (string)$gen['url'];
                            }
                        } else {
                            $final_url = (string)$gen['url'];
                        }
                    }
                    if ($final_url === '') { continue; }

                    // 构建 <figure><img/></figure>
                    $class2 = 'img-featured img-responsive';
                    if (!empty($attr['class'])) {
                        $class2 = $attr['class'];
                    }
                    $auto_alt_enabled2 = $this->settings['global_tabs']['auto-alt-enabled'] ?? false;
                    $img2 = '<img class="' . $class2 . ' wp-post-image" src="' . esc_url($final_url) . '"';
                    if ($auto_alt_enabled2) {
                        $img2 .= ' alt="' . esc_attr($alt_text) . '"';
                    }
                    if (strpos($class2, 'retina') !== false) {
                        $img2 .= ' data-src="' . esc_url($final_url) . '"';
                    }
                    if (!empty($attr['loading'])) {
                        $img2 .= ' loading="lazy"';
                    }
                    $figures[] = str_replace('{img}', $img2 . ' />', '<figure class="wp-block-image size-full aligncenter">{img}</figure>');
                }

                if (!empty($figures)) {
                    $content = $this->insert_images_distributed($content, $figures);
                    \error_log('[AI-Post][InsertImage] distributed-only: inserted ' . count($figures) . ' Doubao image(s).');
                } else {
                    \error_log('[AI-Post][InsertImage] distributed-only: no valid Doubao images generated.');
                }
            } else {
                \error_log('[AI-Post][InsertImage] distributed-only: Doubao switch OFF, skip. task_key=' . (is_scalar($key) ? (string)$key : gettype($key)));
            }
        } catch (\Throwable $e) {
            \error_log('[AI-Post][InsertImage] distributed-only: exception: ' . $e->getMessage());
        }

        return $content;
    }

	/**
	 * 注册定时任务
	 *
	 * @return void
	 */
    public function plugin_init(): void
    {
        // 循环注册定时任务
        if (empty($this->settings['tasks'])) {
            \error_log('AI Post Cron: 未发现可用任务（settings.tasks 为空），跳过注册');
            return;
        }
        foreach ($this->settings['tasks'] as $key => $task) {
            // 未开启下，跳过
            if (empty($task['task-status'])) {
                // 任务已关闭时，清理可能遗留的单次调度，避免继续触发
                if (wp_next_scheduled('ai_post_cron', array($key))) {
                    wp_clear_scheduled_hook('ai_post_cron', array($key));
                    error_log("AI Post Cron: 检测到任务已关闭，已取消后续调度: {$key}");
                } else { // frequency 模式汇总日志
                    $freq = isset($task['task-frequency']) ? (int)$task['task-frequency'] : 10;
                    $last_run_key = "ai-post_last_run_{$key}";
                    $last_run = get_option($last_run_key, 0);
                    $time_diff = floor((time() - $last_run) / 60);
                    $summary = sprintf(
                        'AI Post Cron: Frequency decision | task=%s | last_run=%s | diff=%sm | freq=%sm | decision=run',
                        (string)$key,
                        $last_run ? date('Y-m-d H:i:s', $last_run) : 'never',
                        (string)$time_diff,
                        (string)$freq
                    );
                    error_log($summary);
                }
                continue;
            }
            $freq = isset($task['task-frequency']) ? (int)$task['task-frequency'] : 10;
            $scheduled = wp_next_scheduled('ai_post_cron', array($key));
            if (!$scheduled) {
                // 若无排程，按频率注册一次
                $delay = max(1, $freq) * 60;
                $next = time() + $delay;
                wp_schedule_single_event($next, 'ai_post_cron', array($key));
                error_log(sprintf('AI Post Cron: 已为任务 %s 注册单次调度，delay=%ss，next=%s', (string)$key, (string)$delay, date('Y-m-d H:i:s', $next)));
                // 新增：在本次请求内主动触发一次 WP-Cron（节流30秒），避免等待“下一次访问”才执行
                try {
                    $spawn_guard = 'aipost_spawn_guard_' . md5((string)$key);
                    if (!function_exists('get_transient') || !get_transient($spawn_guard)) {
                        if (function_exists('set_transient')) { set_transient($spawn_guard, 1, 30); }
                        if (function_exists('spawn_cron')) {
                            // 触发最近一次应执行的 Cron；传入当前时间+1秒
                            @spawn_cron(time() + 1);
                            error_log(sprintf('AI Post Cron: 已主动触发 spawn_cron() 以加速任务 %s 执行', (string)$key));
                        }
                    }
                } catch (\Throwable $e) {
                    // 忽略 spawn 错误，避免影响主流程
                }
            } else {
                // 若存在排程但长时间未跑，进行一次补发（增加防抖：60s内只补发一次，避免重复补发）
                if (\HaoZiTeam\AIPost\Service\Features\CronUtils::due_for_catchup((string)$key, max(1, $freq))) {
                    $guard_key = 'ai_post_catchup_' . md5((string)$key);
                    if (!get_transient($guard_key)) {
                        set_transient($guard_key, 1, 60);
                        $补发 = time() + 5;
                        wp_schedule_single_event($补发, 'ai_post_cron', array($key)); // 5秒后补发，避免与当前请求竞争
                        error_log(sprintf('AI Post Cron: 任务 %s 检测到长时间未跑，已补发一次执行（5s 后）', (string)$key));
                        // 新增：补发后也主动触发一次 WP-Cron（节流30秒）
                        try {
                            $spawn_guard2 = 'aipost_spawn_guard2_' . md5((string)$key);
                            if (!function_exists('get_transient') || !get_transient($spawn_guard2)) {
                                if (function_exists('set_transient')) { set_transient($spawn_guard2, 1, 30); }
                                if (function_exists('spawn_cron')) {
                                    @spawn_cron(time() + 1);
                                    error_log(sprintf('AI Post Cron: 补发后已主动触发 spawn_cron() 以加速任务 %s 执行', (string)$key));
                                }
                            }
                        } catch (\Throwable $e) {
                            // 忽略 spawn 错误
                        }
                    } else {
                        error_log(sprintf('AI Post Cron: 任务 %s 补发已在短期内安排，跳过重复补发', (string)$key));
                    }
                } else {
                    error_log(sprintf('AI Post Cron: 任务 %s 已存在排程，next=%s', (string)$key, date('Y-m-d H:i:s', (int)$scheduled)));
                }
            }
        }

    }

	/**
	 * 获取文章对应的唯一文件名
	 *
	 * @param string $post_title
	 *
	 * @return string
	 */
	public function get_image_key($post_title)
	{
		$key = $this->currentKey;

		return md5('文章配图-' . $post_title . '-' . json_encode($key));
	}

	/**
	 * 生成文章缩略图
	 *
	 * @param string $post_title 文章标题
	 * @param int $post_id 文章ID
	 * @param bool $force_title_mode 当为 true 时强制走标题图生成流程（跳过豆包文生图）
	 *
	 * @return bool|array
	 */
	public function generate_thumbnail($post_title, $force_title_mode = false)
	{
		$key = $this->currentKey;
		$task_settings = $this->settings['tasks'][$key] ?? [];

		// 入口日志：方便追踪是否进入缩略图生成流程（方案B 验证）
		\error_log('[AI Post Debug][generate_thumbnail] invoked: task_key=' . (string)$key . ' | title="' . (string)$post_title . '" | force_title_mode=' . ($force_title_mode ? 'true' : 'false'));

		// 优先：如启用豆包文生图，则先尝试基于标题生成英文指令并调用文生图
		$enable_doubao = (bool)($task_settings['enable-doubao-imagegen'] ?? false);
		\error_log('[ImageGen] 开关状态 enable-doubao-imagegen=' . ($enable_doubao ? 'ON' : 'OFF') . ' | task_key=' . (string)$key . ' | title="' . (string)$post_title . '"');
		if ($enable_doubao && !$force_title_mode) {
			try {
				$prompt = ImageProcessor::generate_image_prompt_from_title((string)$post_title, $this->settings);
				$prompt_log = mb_substr((string)$prompt, 0, 160);
				\error_log('[ImageGen] 已生成提示词，长度=' . strlen((string)$prompt) . ' | 预览="' . $prompt_log . (strlen((string)$prompt) > 160 ? '…' : '') . '"');
				$provider = (string)($task_settings['imagegen-provider'] ?? 'doubao');
				$gen = ($provider === 'aliyun')
					? ImageProcessor::generate_image_via_aliyun_dashscope($prompt, $this->settings)
					: ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
				if (is_wp_error($gen)) {
					error_log('[ImageGen] Failed to generate (provider=' . $provider . '), falling back to local thumbnail: ' . $gen->get_error_message());
				} elseif (is_array($gen)) {
					// 约定：返回 ['url' => ...]，根据任务设置决定是否侧载到媒体库
					\error_log('[ImageGen] API returned structure (provider=' . $provider . '): ' . json_encode(array_keys($gen)) . ' | url=' . (string)($gen['url'] ?? '')); 
					if (!empty($gen['url'])) {
						$download_to_media = (bool)($task_settings['doubao-imagegen-download-to-media'] ?? true);
						\error_log('[ImageGen] Decision (provider=' . $provider . '): ' . ($download_to_media ? 'download and sideload to media' : 'return remote URL'));
						if ($download_to_media) {
							// 侧载为未附加附件（post_id=0，不设为特色图），失败则回退使用远程 URL
							$attach = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::attach_remote_image_to_post(0, (string)$gen['url'], false, '', 60);
							if (is_array($attach) && !empty($attach['ok']) && !empty($attach['url'])) {
								\error_log('[ImageGen] Sideload successful (provider=' . $provider . '), attachment_id=' . ((int)($attach['attachment_id'] ?? 0)) . ', url=' . (string)$attach['url']);
								$attachment_id = (int)($attach['attachment_id'] ?? 0);

								// 新增：根据任务开关在本地图片上叠加标题（而不是让AI生成文字）
								$enable_title_overlay = (bool)($task_settings['enable-ai-title-overlay'] ?? false);
								\error_log('[AI-Post][TitleOverlay] Switch enable-ai-title-overlay=' . ($enable_title_overlay ? 'ON' : 'OFF') . ' | attachment_id=' . $attachment_id);
								if ($enable_title_overlay && $attachment_id > 0) {
									$file_path = get_attached_file($attachment_id);
									if (is_string($file_path) && $file_path !== '') {
										$ok = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::overlay_title_on_file($file_path, (string)$post_title, $task_settings, AIPOST_PLUGIN_PATH);
										if ($ok) {
											// 重新生成并更新附件元数据，确保尺寸/缩略图同步
											if (function_exists('wp_generate_attachment_metadata')) {
												$meta = wp_generate_attachment_metadata($attachment_id, $file_path);
												if (!is_wp_error($meta) && is_array($meta)) {
													wp_update_attachment_metadata($attachment_id, $meta);
												}
											}
											\error_log('[AI-Post][TitleOverlay] Overlay successful for attachment_id=' . $attachment_id);
										} else {
											\error_log('[AI-Post][TitleOverlay] Overlay failed for attachment_id=' . $attachment_id);
										}
									}
								}

								return [
									'id'   => $attachment_id,
									'guid' => $attach['url'],
								];
							}
							\error_log('[ImageGen] Sideload failed, falling back to remote URL');
						}
						// 不下载或侧载失败，直接返回远程 URL
						return [
							'id'   => 0,
							'guid' => (string)$gen['url'],
							'file' => $gen['file'] ?? '',
							'type' => $gen['mime'] ?? 'image/jpeg',
						];
					}
					// 数组返回但没有url字段
					\error_log('[ImageGen] Array returned without url field (provider=' . $provider . '), falling back to local thumbnail');
				} else {
					// 其它意外返回类型
					\error_log('[ImageGen] Unexpected return type (provider=' . $provider . '): ' . gettype($gen) . ', falling back to local thumbnail');
				}
			}
            catch (\Throwable $e) {
                error_log('[ImageGen] Exception occurred (provider=' . ($task_settings['imagegen-provider'] ?? 'doubao') . '), falling back to local thumbnail: ' . $e->getMessage());
            }
        }

            // Define a callback for image key generation
            $image_key_generator = function($title) {
                return $this->get_image_key($title);
            };

			// Call the new static method in ImageProcessor
			$attachment = ImageProcessor::generate_thumbnail_with_settings(
				$post_title,
				$task_settings,
				AIPOST_PLUGIN_PATH, // 使用插件目录常量，确保拼接字体路径正确
				$image_key_generator
			);

			// The original method returned false on failure, the new one returns null.
			// We can convert null to false here if strict adherence to the old return type is needed,
			// or update calling code to expect null.
			// For now, let's return null if $attachment is null, or the $attachment array.
			// 返回前记录结果，便于在日志中快速定位生成结果来源
			if (is_array($attachment)) {
				\error_log('[AI Post Debug][generate_thumbnail] return attachment array: id=' . (string)($attachment['id'] ?? '') . ' | guid=' . (string)($attachment['guid'] ?? '') . ' | file=' . (string)($attachment['file'] ?? '') . ' | type=' . (string)($attachment['type'] ?? ''));
			} else {
				\error_log('[AI Post Debug][generate_thumbnail] return null/false');
			}
			return $attachment; // If $attachment is null, it will return null (evaluates to false in boolean contexts)
		}

	/**
	 * 文章内容添加缩略图
	 * @param $content
	 * @return string
	 */
	public function the_content($content, $alt_text, &$alt_counter = 1, $attr = [])
	{
		if (!empty($content)) { // 这里可能需要检查 $alt_text 是否为空更合适
			$key = $this->currentKey;
			// 追加调试：记录 currentKey 与内容长度
			\error_log('[AI-Post][InsertImage] the_content: invoked. currentKey=' . (is_scalar($key) ? (string)$key : gettype($key)) . ' | content_len=' . strlen((string)$content) . ' | alt_text_preview="' . mb_substr((string)$alt_text, 0, 60) . (mb_strlen((string)$alt_text) > 60 ? '…' : '') . '"');

			            // --- 改造：当任务开启豆包文生图时，按“配图方式一(分散)”插入，支持多图 ---
            try {
                $task_settings = $this->settings['tasks'][$key] ?? [];
                $enable_doubao_first = (bool)($task_settings['enable-doubao-imagegen'] ?? false);
                if ($enable_doubao_first) {
                    \error_log('[AI-Post][InsertImage] the_content: Doubao distributed insert ENABLED. task_key=' . (string)$key);

                    // 提供商选择：默认豆包，兼容未来扩展
                    $provider = (string)($task_settings['imagegen-provider'] ?? 'doubao');
                    // 读取数量：阿里云独立，豆包使用自身字段
                    if ($provider === 'aliyun') {
                        $img_count = (int)($task_settings['aliyun-imagegen-n-task'] ?? 1);
                        $aliyun_model = (string)($task_settings['aliyun-imagegen-model-task'] ?? '');
                        if ($aliyun_model === 'qwen-image') {
                            $img_count = 1; // qwen 强制单张
                        } else {
                            if ($img_count < 1) { $img_count = 1; }
                            if ($img_count > 4) { $img_count = 4; }
                        }
                    } else {
                        $img_count = (int)($task_settings['doubao-imagegen-image-count'] ?? 1);
                        if ($img_count < 1) { $img_count = 1; }
                        if ($img_count > 4) { $img_count = 4; }
                    }
                    $download_to_media = (bool)($task_settings['doubao-imagegen-download-to-media'] ?? true);
                    \error_log('[AI-Post][InsertImage] the_content: provider=' . $provider . ' img_count=' . $img_count . ' download_to_media=' . ($download_to_media ? 'ON' : 'OFF'));

                    // 生成一次 Prompt，复用以提升一致性
                    $prompt = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_prompt_from_title((string)$alt_text, $this->settings);

                    $figures = [];
                    for ($i = 0; $i < $img_count; $i++) {
                        if ($provider === 'aliyun') {
                            // 合并任务级配置，确保优先采用任务级参数
                            $effective_settings = is_array($task_settings) ? array_merge($this->settings, $task_settings) : $this->settings;
                            $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_aliyun_dashscope($prompt, $effective_settings);
                        } else {
                            // 关键修复：传入 $task_settings 以使用任务级尺寸/模型等参数
                            $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
                        }
                        if (is_wp_error($gen)) {
                            \error_log('[AI-Post][InsertImage] the_content: gen failed #' . ($i+1) . ' provider=' . $provider . ' => ' . $gen->get_error_message());
                            continue;
                        }
                        $final_url = '';
                        if (is_array($gen) && !empty($gen['url'])) {
                            if ($download_to_media) {
                                $attach = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::attach_remote_image_to_post(0, (string)$gen['url'], false, '', 60);
                                if (is_array($attach) && !empty($attach['ok']) && !empty($attach['url'])) {
                                    $final_url = (string)$attach['url'];
                                } else {
                                    $final_url = (string)$gen['url'];
                                }
                            } else {
                                $final_url = (string)$gen['url'];
                            }
                        }
                        if ($final_url === '') { continue; }

                        // 构建 <figure><img/></figure>
                        $class2 = 'img-featured img-responsive';
                        if (!empty($attr['class'])) {
                            $class2 = $attr['class'];
                        }
                        $auto_alt_enabled2 = $this->settings['global_tabs']['auto-alt-enabled'] ?? false;
                        $img2 = '<img class="' . $class2 . ' wp-post-image" src="' . esc_url($final_url) . '"';
                        if ($auto_alt_enabled2) {
                            $img2 .= ' alt="' . esc_attr($alt_text) . '"';
                        }
                        if (strpos($class2, 'retina') !== false) {
                            $img2 .= ' data-src="' . esc_url($final_url) . '"';
                        }
                        if (!empty($attr['loading'])) {
                            $img2 .= ' loading="lazy"';
                        }
                        $figures[] = str_replace('{img}', $img2 . ' />', '<figure class="wp-block-image size-full aligncenter">{img}</figure>');
                    }

                    if (!empty($figures)) {
                        $content = $this->insert_images_distributed($content, $figures);
                        \error_log('[AI-Post][InsertImage] the_content: inserted ' . count($figures) . ' Doubao image(s) via distributed method-1.');
                        // 关键修复：分散插图已完成时，直接返回，避免后续标题图流程再次插入导致多一张
                        return $content;
                    } else {
                        \error_log('[AI-Post][InsertImage] the_content: no valid Doubao images generated, skip distributed insert.');
                    }
                }
            } catch (\Throwable $e) {
                \error_log('[AI-Post][InsertImage] the_content: exception during Doubao distributed insert: ' . $e->getMessage());
            }
            // --- 改造结束 ---

			// 当由“配图方式二”触发时，强制走标题图流程，避免误用豆包文生图
			$attachment = $this->generate_thumbnail($alt_text, true);
			// 新增日志和检查
			if (!$attachment || !is_array($attachment) || empty($attachment['guid'])) { // Check if attachment is valid and has guid
			    error_log("[AI-Post][InsertImage] the_content: Invalid or missing GUID from generate_thumbnail. alt_text=" . $alt_text . ", abort insert.");
			    return $content; // Return original content if attachment is invalid
			}

			if ($attachment && isset($attachment['guid'])) { // This check is now somewhat redundant but safe to keep
				error_log("[AI-Post][InsertImage] the_content: Got attachment GUID=" . $attachment['guid'] . ", alt_text=" . $alt_text);
			} elseif (!$attachment) {
				error_log("[AI-Post][InsertImage] the_content: generate_thumbnail returned null/false. alt_text=" . $alt_text);
				// 如果 $attachment 为空，则后续代码会出错，需要处理
				return $content; // 或者其他错误处理
			}

			// 获取是否开启alt自动添加设置
			// $auto_alt = $this->settings['auto-alt-enabled'] ?? true; // Moved to _process_alt_attributes

			// 构建alt文本
			// $alt = $auto_alt ? esc_attr($alt_text) . $alt_counter : esc_attr($alt_text); // Moved to _process_alt_attributes

			// 返回插件生成的缩略图HTML
			$class = 'img-featured img-responsive';
			if (!empty($attr['class'])) {
				$class = $attr['class'];
			}

            // --- BEGIN REVISED FIX 2: Explicitly build attributes --- 
            $auto_alt_enabled = $this->settings['global_tabs']['auto-alt-enabled'] ?? false;
            
            // Start building the img tag
            $img_html = '<img class="' . $class . ' wp-post-image" src="' . esc_url($attachment['guid']) . '"'; // Close src attribute correctly

            // Add alt attribute ONLY if enabled
            if ($auto_alt_enabled) {
                $img_html .= ' alt="' . esc_attr($alt_text) . '"';
            }
            
            // Add other attributes
			if (strpos($class, 'retina') !== false) {
				$img_html .= ' data-src="' . esc_url($attachment['guid']) . '"';
			}
			if (!empty($attr['loading'])) {
				$img_html .= ' loading="lazy"';
			}
            // --- END REVISED FIX 2 ---

            // $img_html now contains the complete set of attributes before closing the tag

            // $image_html = $img_html . ' />'; // Close the img tag (Moved down)

			// $image_html = $img_html . ' />'; // Commented out duplicate/old line
			if (!empty($img_html)) { // Check if img tag string was built

				$image_html = $img_html . ' />'; // Close the img tag HERE
				$image_html = str_replace('{img}', $image_html, '<figure class="wp-block-image size-full aligncenter">{img}</figure>');

				// 日志：记录插入位置设置
				$position_setting = (int)($this->settings['tasks'][$key]['enable-image-url-position'] ?? 0);
				error_log('[AI-Post][InsertImage] the_content: position_setting=' . $position_setting . ' (1=head, 3=tail, 0/2=auto)');

				// --- 优化开始: 添加表格保护机制 ---
				// 保存原始内容，用于后续恢复表格
				$original_content = $content;
				
				// 表格保护机制：将表格替换为占位符
				$table_placeholders = [];
				$placeholder_index = 0;
				
				// 1. 保护HTML标准表格
				$content_with_placeholders = preg_replace_callback(
					'/<table\b[^>]*>.*?<\/table>/is', // 更稳健：匹配完整 <table ...> ... </table>
					function($matches) use (&$table_placeholders, &$placeholder_index) {
						$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
						$table_placeholders[$placeholder] = $matches[0]; // 存储原始表格HTML
						return $placeholder;
					},
					$content
				);
				
				// 2. 保护DIV表格（通常使用display:table或wp-block-table的div结构）
				if ($content_with_placeholders) {
					$content_with_placeholders = preg_replace_callback(
						'/<div[^>]*class=["\'][^"\']*(?:table|wp-block-table)[^"\']*["\'][^>]*>.*?<\/div>/is', // 单层 div 结束，覆盖更多实际结构
						function($matches) use (&$table_placeholders, &$placeholder_index) {
							$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
							$table_placeholders[$placeholder] = $matches[0]; // 存储原始表格HTML
							return $placeholder;
						},
						$content_with_placeholders
					);
				}
				
				// 3. 保护Markdown格式表格（通常以|开头的连续多行）
				if ($content_with_placeholders) {
					$content_with_placeholders = preg_replace_callback(
						'/(\|[^\n]+\|\n)(\|[\s\-:]+\|\n)(\|[^\n]+\|\n)+/s', // 匹配Markdown表格格式（以竖线开头）
						function($matches) use (&$table_placeholders, &$placeholder_index) {
							$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
							$table_placeholders[$placeholder] = $matches[0]; // 存储原始Markdown表格
							return $placeholder;
						},
						$content_with_placeholders
					);
				}
				
				// 4. 保护可能的其他表格结构（如带有data-table属性的元素）
				if ($content_with_placeholders) {
					$content_with_placeholders = preg_replace_callback(
						'/<[^>]*data-table[^>]*>.*?<\/[^>]*>/is', // 查找带有data-table属性的元素
						function($matches) use (&$table_placeholders, &$placeholder_index) {
							$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
							$table_placeholders[$placeholder] = $matches[0]; // 存储原始HTML
							return $placeholder;
						},
						$content_with_placeholders
					);
				}
				
				// 只有在成功替换表格后才使用带占位符的内容
				if (!empty($table_placeholders)) {
					$content = $content_with_placeholders;
					error_log("AI Post Debug: 图片插入前保护了 " . count($table_placeholders) . " 个表格结构");
				} else {
					error_log("AI Post Debug: 未检测到需保护的表格结构，跳过占位");
				}
				// --- 表格保护机制结束 ---

				switch ($this->settings['tasks'][$key]['enable-image-url-position']) {
					case 1:
						$content = $image_html . $content;
						error_log('[AI-Post][InsertImage] the_content: inserted at head (position=1).');
						break;
					case 3:
						$content = $content . $image_html;
						error_log('[AI-Post][InsertImage] the_content: inserted at tail (position=3).');
						break;
					default:
						$image_start_num = 0;
						$did_insert = false; // 记录是否通过回调实际插入
						if (preg_match_all('/<(p|br|div)[^>]*>/i', $content, $mat)) {
							$p_nums = count($mat[0]);
							error_log('[AI-Post][InsertImage] the_content: auto mode candidate tag count=' . $p_nums);

							if ($this->settings['tasks'][$key]['enable-image-url-position'] == 2) {
								// --- 优化: 改进中间位置计算逻辑 ---
								$image_start_num = intval(($p_nums - 1) / 2);
								error_log('[AI-Post][InsertImage] the_content: middle position computed index=' . $image_start_num);
							} else {
								$image_start_num = mt_rand(0, max(0, $p_nums - 1));
								error_log('[AI-Post][InsertImage] the_content: random position chosen index=' . $image_start_num);
							}
						} else {
							// 若没有可用标签，直接头部兜底
							error_log('[AI-Post][InsertImage] the_content: no candidate tags found, fallback to head prepend.');
							$content = $image_html . $content;
							break;
						}
						
						// --- 优化: 改进图片插入逻辑，确保在完整标签闭合后插入 ---
						$content = preg_replace_callback(
							'/(<\/p>|<\/div>|<\/ul>|<\/ol>|<\/h[1-6]>|<\/section>|<\/article>|<\/blockquote>)/i', 
							function ($matches) use ($image_start_num, $image_html, &$did_insert) {
								static $i = -1;
								$i++;
								if ($i == $image_start_num) {
									$did_insert = true;
									return $matches[0] . $image_html;
								}
								return $matches[0];
							}, 
							$content
						);

						// 若正则未实际插入，增加兜底（头部）
						if (!$did_insert) {
							error_log('[AI-Post][InsertImage] the_content: regex pass did not insert, fallback to head prepend.');
							$content = $image_html . $content;
						}
				}
				
				// --- 优化: 恢复表格结构 ---
				if (!empty($table_placeholders)) {
					$content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
					error_log('[AI-Post][InsertImage] the_content: restored ' . count($table_placeholders) . ' protected table blocks after insertion.');
				}
				// --- 恢复表格结构结束 ---
			}
		}

		return $content;
	}


	/**
	 * 运行定时任务
	 *
	 * @param $key
	 *
	 * @return void
	 */
	public function ai_post_cron($key)
    {
		$is_rewrite_type = null; // 新增：AI改写类型标记
       $did_run = false; // 频率门控：标记本次是否真正执行了任务

       // === 任务互斥锁，防止并发执行 ===
       if (!\HaoZiTeam\AIPost\Service\Features\CronUtils::acquire_lock((string)$key, 900)) { // 15 分钟锁
           error_log("AI Post Debug: 任务 {$key} 已在执行中，跳过本次触发");
           return;
       }

       // 关键：将当前任务键写入实例，供 the_content()/generate_thumbnail()/insert_doubao_images_distributed() 等读取
       $this->currentKey = $key;
       \error_log('[AI Post Debug] currentKey set for run: ' . (string)$key);

        // 确保任何返回路径都能释放锁、记录最后一次运行时间并安排下一次执行
        register_shutdown_function(function() use ($key, &$did_run) {
            try {
                if ($did_run) {
                    \HaoZiTeam\AIPost\Service\Features\CronUtils::update_last_run((string)$key);
                }
            } catch (\Throwable $e) {}
            try {
                $settings = get_option('ai-post');
                $task = $settings['tasks'][$key] ?? [];
                $enabled = !empty($task['task-status']);
                if ($enabled) {
                    $schedule_type = $task['task-schedule-type'] ?? 'frequency';
                    $last = \HaoZiTeam\AIPost\Service\Features\CronUtils::get_last_run((string)$key);
                    $target_next_ts = null;
                    if ($schedule_type === 'frequency') {
                        $freq = isset($task['task-frequency']) ? (int)$task['task-frequency'] : 10;
                        $freq = max(1, $freq);
                        $freq_sec = $freq * 60;
                        $base_ts = $did_run ? time() : ($last > 0 ? $last : time());
                        $target_next_ts = $base_ts + $freq_sec;
                    } else {
                        // timespan：精确计算下次触发点（在窗口外：下一窗口开始；在窗口内：last+span_freq，超过窗口则下一窗口开始）
                        $now_local = function_exists('current_time') ? current_time('timestamp') : time();
                        $minute_of_day = (int)date('H', $now_local) * 60 + (int)date('i', $now_local);
                        $local_midnight = $now_local - ($minute_of_day * 60);
                        $to_ts = function(string $hi) use ($local_midnight): int {
                            list($h,$m) = explode(':', $hi);
                            return $local_midnight + ((int)$h * 60 + (int)$m) * 60;
                        };
                        $now_hi = function_exists('current_time') ? current_time('H:i') : date('H:i', $now_local);
                        $in_window = false; $span_start_ts = null; $span_end_ts = null; $span_freq = null;
                        $spans = (is_array($task['task-timespans'] ?? null)) ? $task['task-timespans'] : [];
                        foreach ($spans as $span) {
                            $s = $span['start-time'] ?? ''; $e = $span['end-time'] ?? '';
                            if (!$s || !$e) { continue; }
                            if ($this->is_time_between($now_hi, $s, $e)) {
                                $in_window = true;
                                $span_start_ts = $to_ts($s);
                                $span_end_ts   = $to_ts($e);
                                // 跨天窗口：如果 end < start，则结束应在次日
                                if ($span_end_ts < $span_start_ts) { $span_end_ts += 86400; }
                                $span_freq = max(1, (int)($span['timespan-frequency'] ?? 1)) * 60;
                                break;
                            }
                        }
                        if ($in_window && $span_freq !== null) {
                            $base = $did_run ? $now_local : ($last > 0 ? $last : $now_local);
                            $candidate = $base + $span_freq;
                            if ($candidate <= $span_end_ts) {
                                $target_next_ts = $candidate;
                            } else {
                                // 本窗口放不下，改为下一窗口的开始
                                $target_next_ts = null; // 先置空，走到“寻找下一窗口”逻辑
                            }
                        }
                        if ($target_next_ts === null) {
                            // 寻找下一个窗口的开始时间（今天剩余窗口或次日最早窗口）
                            $candidates = [];
                            foreach ($spans as $span) {
                                $s = $span['start-time'] ?? ''; if (!$s) continue;
                                $start_ts_today = $to_ts($s);
                                if ($start_ts_today > $now_local) { $candidates[] = $start_ts_today; }
                            }
                            if (empty($candidates)) {
                                // 次日最早窗口
                                $tomorrow_midnight = $local_midnight + 86400;
                                foreach ($spans as $span) {
                                    $s = $span['start-time'] ?? ''; if (!$s) continue;
                                    list($h,$m) = explode(':', $s);
                                    $candidates[] = $tomorrow_midnight + ((int)$h * 60 + (int)$m) * 60;
                                }
                            }
                            if (!empty($candidates)) {
                                sort($candidates);
                                $target_next_ts = $candidates[0];
                            } else {
                                // 没有有效窗口定义，兜底 5 分钟后检查
                                $target_next_ts = time() + 300;
                            }
                        }

                        // 如果已经存在未来的调度则不重复排队，减少日志噪音与时间“递增”观感
                        $existing = wp_next_scheduled('ai_post_cron', array($key));
                        if ($existing && $existing >= ($target_next_ts - 5)) {
                            // 已有排队且时间不早于目标，无需重复安排
                        } else {
                            // 若存在比目标更早的旧排队，清掉再按目标重新安排
                            if ($existing && $existing < $target_next_ts) {
                                wp_unschedule_event($existing, 'ai_post_cron', array($key));
                            }
                            wp_schedule_single_event($target_next_ts, 'ai_post_cron', array($key));
                            $next_human = function_exists('date_i18n') ? date_i18n('Y-m-d H:i:s', $target_next_ts) : date('Y-m-d H:i:s', $target_next_ts);
                            error_log("AI Post Cron: 已排队 task={$key} | 下次执行时间: {$next_human} (调度=" . $schedule_type . ")");
                        }
                    }
                    }
                    if (!$enabled) {
                        // 任务关闭时清理已有调度
                        while ($ts = wp_next_scheduled('ai_post_cron', array($key))) {
                            wp_unschedule_event($ts, 'ai_post_cron', array($key));
                        }
                        error_log("AI Post Cron: 任务已关闭，已清理调度: {$key}");
                    }
                } catch (\Throwable $e) {}
                try {
                    \HaoZiTeam\AIPost\Service\Features\CronUtils::release_lock((string)$key);
                } catch (\Throwable $e) {}
        });

        // ==== 调度门控：根据调度类型分别处理 ====
        $task = $this->settings['tasks'][$key] ?? [];
        $schedule_type = $task['task-schedule-type'] ?? 'frequency';
        try {
            if ($schedule_type === 'frequency') {
                // 传统按频率：尊重 task-frequency
                $freq = isset($task['task-frequency']) ? (int)$task['task-frequency'] : 10; // 单位：分钟
                $freq = max(1, $freq);
                $last = \HaoZiTeam\AIPost\Service\Features\CronUtils::get_last_run((string)$key);
                if ($last > 0) {
                    $elapsed = (time() - $last) / 60;
                    if ($elapsed < $freq) {
                        $next_eta = $freq - $elapsed;
                        $last_h = function_exists('date_i18n') ? date_i18n('Y-m-d H:i:s', $last) : date('Y-m-d H:i:s', $last);
                        error_log(sprintf('AI Post Cron: 未到时间，跳过本次执行 | task=%s | last_run=%s | elapsed=%.1fm | freq=%dm | eta=%.1fm', (string)$key, $last_h, $elapsed, $freq, $next_eta));
                        return;
                    }
                }
            } else {
                // 按时间段：仅在任何一个时间段内，并满足该时间段的 timespan-frequency 时才执行
                $current_hi = function_exists('current_time') ? current_time('H:i') : date('H:i');
                $in_window = false;
                $active_span_freq = null; // 当前命中的时间段频率（分钟）
                if (!empty($task['task-timespans']) && is_array($task['task-timespans'])) {
                    foreach ($task['task-timespans'] as $span) {
                        $start_time = $span['start-time'] ?? '';
                        $end_time   = $span['end-time'] ?? '';
                        if ($start_time && $end_time && $this->is_time_between($current_hi, $start_time, $end_time)) {
                            $in_window = true;
                            $active_span_freq = max(1, (int)($span['timespan-frequency'] ?? 1));
                            break;
                        }
                    }
                }
                if (!$in_window) {
                    error_log('AI Post Cron: 不在任何时间段内，跳过本次执行 | task=' . (string)$key . ' | now=' . $current_hi);
                    return;
                }
                // 在窗口内时，按该窗口的频率判断是否到点
                $last = \HaoZiTeam\AIPost\Service\Features\CronUtils::get_last_run((string)$key);
                if ($last > 0) {
                    $elapsed = (time() - $last) / 60;
                    if ($elapsed < $active_span_freq) {
                        $eta = $active_span_freq - $elapsed;
                        $last_h = function_exists('date_i18n') ? date_i18n('Y-m-d H:i:s', $last) : date('Y-m-d H:i:s', $last);
                        error_log(sprintf('AI Post Cron: 时间段内但未到频率，跳过 | task=%s | last_run=%s | elapsed=%.1fm | span_freq=%dm | eta=%.1fm', (string)$key, $last_h, $elapsed, $active_span_freq, $eta));
                        return;
                    }
                }
                // 命中时间窗口且到点：允许执行
            }
        } catch (\Throwable $e) {
            // 门控异常不影响主流程
        }

        // 通过频率门控，标记本次为真正执行
        $did_run = true;

        // === 使用新的 ApiClientService 创建 API 客户端 ===
        $chat_gpt = ApiClientService::create_api_client($this->settings, $key, $task);
        if (!$chat_gpt) {
            error_log("AI Post Debug: (Base) API client creation failed for task {$key}. Aborting cron.");
            return; // 客户端创建失败，中止任务
        }
        error_log("AI Post Debug: (Base) API client successfully created via ApiClientService for task {$key}. Model: " . $chat_gpt->getModel());
        // === API 客户端创建结束 ===

            // 新增：读取 system-prompt（优先任务级，次全局）
            $system_prompt = $this->settings['tasks'][$key]['system-prompt'] ?? $this->settings['global_tabs']['system-prompt'] ?? '';

			// 默认文章参数
			$content = '';
			$title = '';
			$generated_description = ''; // 新增：用于存储生成的描述
			$post_status = $task['post-final-status'] ?? 'publish'; // 读取任务设置的最终状态，默认为发布
			$ai_generated_meta_description = ''; // 新增：用于存储AI生成的Meta Description

			$enable_image_url = $this->settings['tasks'][$key]['enable-image-url'] ?? true; // 获取配图一开关状态，默认值为 true
			$pending_image_urls = []; // 新增：用于存储方式一待侧载的远程图片URL（推迟到获得$post_id后再入库）
			$auto_alt_enabled_global = $this->settings['global_tabs']['auto-alt-enabled'] ?? false; // 记录是否添加空alt

			if ($enable_image_url) {
				$image_url = $this->settings['tasks'][$key]['post-image-url'] ?? ''; // 获取用户输入的网址
				$image_count = 2; // 默认获取图片次数

				// 获取用户设置的获取图片次数
				if (isset($this->settings['tasks'][$key]['post-image-url-n'])) {
					$image_count = (int)$this->settings['tasks'][$key]['post-image-url-n'];
					// 确保图片数量在有效范围内
					$image_count = max(1, min(5, $image_count)); // 限制在1到5之间
				}

				// --- 修改：仅收集待侧载URL，推迟到拿到$post_id后再入库并生成HTML ---
				if (!empty($image_url)) {
					for ($i = 0; $i < $image_count; $i++) {
						$pending_image_urls[] = $image_url;
					}
				}
				// --- 结束修改 ---
				
				// $add_images = ''; // 不再需要此变量
				// 这里可以继续添加获取图片的逻辑，例如调用 API 获取图片等
				// ...
			}
			// 开始问答
			$task_before = $this->settings['tasks'][$key]['custom-before-content'] ?? ''; // 确保有默认值
			foreach ($this->settings['tasks'][$key]['post-steps'] as $k => $step) {
				try {
					// 每步AI调用前，清空上下文，避免前一步消息干扰当前步骤
					if (method_exists($chat_gpt, 'clearMessages')) {
						$chat_gpt->clearMessages();
						error_log("AI Post Debug: Task {$key}, step {$k} - 已清空对话上下文");
					}
					// 每步AI调用前，注入 system-prompt
					if ($system_prompt) {
						$chat_gpt->addMessage($system_prompt, 'system');
					}
					// 检查用户选择的接收类型
					if (!empty($step['receive-type']) && $step['receive-type'] === 'title') {
						// 定义内置指令
						$user_language = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
						$language_map = LanguageUtils::get_language_map(); // New
						$display_language = $language_map[$user_language] ?? '中文简体';
						$is_chinese = in_array($user_language, ['tags-zh', 'tags-zh-fan']);
						$limit_setting = $is_chinese ?
							($this->settings['zh-title-limit'] ?? 96) . '字节' :
							($this->settings['en-title-limit'] ?? 65) . '字符';

						// 获取自定义示例或使用默认
						// $custom_examples = $this->settings['custom-title-examples'] ?? ''; // 旧的错误路径
						$custom_examples = $this->settings['global_tabs']['custom-title-examples'] ?? ''; // 使用正确的路径
						$example_titles = array_filter(explode("\n", $custom_examples)); // Splits into an array and removes empty lines

						// 如果没有自定义示例，则使用默认示例
						if (empty($example_titles)) {
							$example_titles = [
								'老师现场提了一个问题，同学的回答亮了',
								'那些整天熬夜加班的人注意了',
								'这个小技巧，99%的人都不知道',
								'掌握这3点，轻松玩转母婴行业私域运营',
								'我用了2个月，做坏了6次热点营销',
								'上海和深圳对比，未来你更看好谁?'
							];
						}

						// 从示例标题中随机选择一个作为风格参考
						$random_index = array_rand($example_titles);
						$random_title = trim($example_titles[$random_index]);
						
						error_log("AI Post Debug: Task {$key}, step {$k} - 随机选择的示例标题: {$random_title}");

						// 获取用户的指令并优先使用首行作为主要关键词
						$raw_send = (string)($step['send'] ?? '');
						$cont = explode("\n", $raw_send);
						$first_line = isset($cont[0]) ? trim($cont[0]) : '';
						if ($first_line === '' && $k === 0) {
							// 关键词耗尽：自动关闭任务并停止
							error_log("AI Post Debug: Task {$key}, step {$k} - 检测到关键词已耗尽，自动关闭任务并停止本次执行");
							try {
								$settings_opt = get_option('ai-post');
								if (isset($settings_opt['tasks'][$key])) {
									$settings_opt['tasks'][$key]['task-status'] = false; // 关闭任务（使用布尔false，避免UI回退默认true）
									$updated_ok = update_option('ai-post', $settings_opt, false);
									error_log('AI Post Cron: 已关闭任务开关（关键词耗尽）: ' . ($updated_ok ? 'OK' : 'FAIL'));
								}
								// 清理后续调度
								wp_clear_scheduled_hook('ai_post_cron', array($key));
								error_log("AI Post Cron: 已清理调度，防止任务再次触发: {$key}");
							} catch (\Throwable $e) {
								error_log('AI Post Cron: 关闭任务或清理调度时出现异常: ' . $e->getMessage());
							}
							return; // 立即结束本次 cron
						}
						$main_instruction = $first_line;

						// 只在第一个步骤 (k=0) 预备消耗首行关键词；稍后在成功生成标题后再持久化到数据库
						$remaining_content = '';
						$prepared_consume = false;
						if ($k === 0) {
							if ($first_line !== '') {
								array_shift($cont); // 预备消耗首行
							}
							$remaining_content = implode("\n", $cont);
							// 为保证本次流程后续步骤读取到的是最新内容，这里先更新到内存
							$this->settings['tasks'][$key]['post-steps'][$k]['send'] = $remaining_content;
							$prepared_consume = ($first_line !== '');
							if ($prepared_consume) {
								error_log("AI Post Debug: Task {$key}, step {$k} - 预备消费首行关键词，等待标题生成成功后再持久化");
							}
						}

						// 构建单一示例标题风格参考部分
						$example_section = "\n风格参考标题：\n{$random_title}\n";

						$builtin_instruction = "\n围绕关键词或句子：\n{$main_instruction}\n"
											 . "生成一个爆款{$display_language}标题。\n"
											 . "{$example_section}；参考标题只是提供风格参考，请忽略其语言，最终生成的标题必须是围绕{$main_instruction}生成的 **{$display_language}** 语言标题。\n"
											 . "要求：\n"
											 . "1. 重要：标题语言必须使用{$display_language}\n"
											 . "2. {$display_language}标题限制在{$limit_setting}之内\n"
											 . "3. 如果涉及年份，请以（{$this->settings['global_tabs']['time-line']}）为时间线\n"
											 . "4. 标题请以纯文本方式输出\n"
											 . "5. 请不要使用有序列表或数字序号\n"
											 . "6. **重要：请严格模仿示例标题的语义风格、语气和结构，包括是否使用疑问句、数字等元素**\n"
											 . "7. **重要：绝对禁止直接复制或使用示例标题，必须围绕'{$main_instruction}'创作新标题**\n";

						// 直接使用构建好的指令
						$full_instruction = $builtin_instruction;

						// 添加日志记录完整的指令内容
						error_log("Full instruction being sent: " . $full_instruction);

						$temp = $chat_gpt->ask($full_instruction); // 使用合并后的指令

						// 添加 Token 使用量日志 (标题生成)
						if (isset($temp['usage'])) {
							error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (Title - Task {$key}): " . json_encode($temp['usage']));
						}

						// 处理标题
						$title = preg_replace('/^(Title：|标题：)/i', '', $temp['answer']);
						$title = preg_replace('/[《》【】*#"""""「」『』〔〕（）()<>『』〈〉]/u', '', $title);

						// 在成功生成非空标题后，才将“已消费的首行关键词”持久化写回到 options，避免失败时误删
						if ($k === 0 && $prepared_consume && !empty($title)) {
							$this->settings['tasks'][$key]['post-steps'][$k]['send'] = $remaining_content; // 再次确保内存为最新
							$ok = \update_option('ai-post', $this->settings, false);
							error_log('AI Post Debug: Task ' . $key . ', step ' . $k . ' - 首行关键词已消费并持久化保存: ' . ($ok ? 'OK' : 'FAIL'));
						}

						// 不再因为关键词用尽而修改任务状态，避免影响后续步骤与标题生成

						// --- START: 优先级逻辑 - 提取上下文信息 ---
							$context_info = '';
							$context_type = 'title'; // Default context type

							// 1. 尝试获取内容摘要
							$content_excerpt_raw = '';
							if (!empty($content)) { // Ensure $content has been populated by previous steps
								$content_excerpt_raw = wp_strip_all_tags($content); // Strip HTML tags
								$content_excerpt_raw = trim(preg_replace('/\s+/', ' ', $content_excerpt_raw)); // Normalize whitespace
							}
							$min_context_length = 50; // Minimum meaningful length
							$max_context_length = 400; // Max length to pass to AI

							if (mb_strlen($content_excerpt_raw) >= $min_context_length) {
								$context_info = mb_substr($content_excerpt_raw, 0, $max_context_length);
								$context_type = 'excerpt';
								error_log("AI Post Debug: Task {$key} - Using content excerpt for Meta Description context (Length: " . mb_strlen($context_info) . ").");
							} else {
								 error_log("AI Post Debug: Task {$key} - Content excerpt too short or empty (Length: " . mb_strlen($content_excerpt_raw) . "). Trying first paragraph.");
								// 2. 尝试获取第一段内容 (如果摘要不可用)
								$first_paragraph_text = '';
								if (!empty($content) && preg_match('/<p>(.*?)<\/p>/is', $content, $matches)) {
									 $first_paragraph_html = $matches[1];
									 // Further strip potential nested tags within the first paragraph
									 $first_paragraph_text = wp_strip_all_tags($first_paragraph_html);
									 $first_paragraph_text = trim(preg_replace('/\s+/', ' ', $first_paragraph_text));

									 if (mb_strlen($first_paragraph_text) >= $min_context_length) {
										 $context_info = mb_substr($first_paragraph_text, 0, $max_context_length);
										 $context_type = 'paragraph';
										 error_log("AI Post Debug: Task {$key} - Using first paragraph for Meta Description context (Length: " . mb_strlen($context_info) . ").");
									 } else {
										error_log("AI Post Debug: Task {$key} - First paragraph too short or empty (Length: " . mb_strlen($first_paragraph_text) . "). Falling back to title only.");
									 }
								} else {
									error_log("AI Post Debug: Task {$key} - Could not find first paragraph. Falling back to title only.");
								}
							}
							// --- END: 优先级逻辑 - 提取上下文信息 ---

							// --- START: 动态构建提示词 ---
							$prompt_base = "请根据以下信息，写一段吸引人的、**内容稍详细的** {$display_language} Meta Description，用于搜索引擎结果展示。要求：\n"
										 . "                     1. **重要：目标长度必须在 200 到 220 个字符之间。请尽量生成接近此范围的内容，不要过短。**\n"
										 . "                     2. 突出文章核心内容和吸引点。\n"
										 . "                     3. 包含主要关键词：{$main_instruction}。\n"
										 . "                     4. 拟人化、口语化，直接输出描述文字，不要添加任何额外说明或标签。\n";

							$prompt_context = '';
							switch ($context_type) {
								case 'excerpt':
									$prompt_context = "文章标题：\"{$title}\"\n内容摘要：\"{$context_info}...\"";
									break;
								case 'paragraph':
									$prompt_context = "文章标题：\"{$title}\"\n第一段内容：\"{$context_info}...\"";
									break;
								default: // title only
									$prompt_context = "文章标题：\"{$title}\"";
									break;
							}

							$meta_desc_prompt = $prompt_base . $prompt_context;
							error_log("AI Post Debug: Task {$key} - Meta Description Prompt (using {$context_type} context): " . $meta_desc_prompt);
							// --- END: 动态构建提示词 ---

							try {
								$meta_desc_temp = $chat_gpt->ask($meta_desc_prompt);
								if (isset($meta_desc_temp['usage'])) {
									error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (Meta Desc - Task {$key}): " . json_encode($meta_desc_temp['usage']));
								}
								if (!empty($meta_desc_temp['answer'])) {
									$ai_generated_meta_description = trim($meta_desc_temp['answer']);
									// 做一次简单的长度截断，以防万一AI超出限制
									$ai_generated_meta_description = mb_substr($ai_generated_meta_description, 0, 160);
									error_log("AI Post Debug: Task {$key} - AI 生成 Meta Description 成功: " . $ai_generated_meta_description);
								} else {
									 error_log("AI Post Debug: Task {$key} - AI 未返回 Meta Description 内容");
									// $ai_generated_meta_description will remain empty or unset
								}
							} catch (Exception $e) {
								error_log("AI Post Debug: Task {$key} - AI 生成 Meta Description 失败: " . $e->getMessage());
								 // $ai_generated_meta_description will remain empty or unset
							// 注意：此处仅关闭 catch 块，不应额外结束上层的 receive-type==='title' 分支
						}
						// --- END: AI 生成 Meta Description ---

						// 标题长度限制与AI改写超长标题逻辑整合
						$is_chinese = in_array($user_language, ['tags-zh', 'tags-zh-fan']);
						$max_length = $is_chinese
							? ($this->settings['zh-title-limit'] ?? 96)
							: ($this->settings['en-title-limit'] ?? 65);
						$title_rewrite_enabled = $this->settings['title-rewrite-enabled'] ?? true;
						if (mb_strlen($title) > $max_length) {
							if ($title_rewrite_enabled) {
								// 从示例标题数组中随机获取一个示例标题
								$custom_examples = $this->settings['global_tabs']['custom-title-examples'] ?? '';
								$example_titles = array_filter(explode("\n", $custom_examples));
								
								if (empty($example_titles)) {
									$example_titles = [
										'老师现场提了一个问题，同学的回答亮了',
										'那些整天熬夜加班的人注意了',
										'这个小技巧，99%的人都不知道',
										'掌握这3点，轻松玩转母婴行业私域运营',
										'我用了2个月，做坏了6次热点营销',
										'上海和深圳对比，未来你更看好谁?'
									];
								}
								
								$random_index = array_rand($example_titles);
								$random_title = trim($example_titles[$random_index]);
								
								// AI改写超长标题
								$rewrite_prompt = "请将以下标题改写为不超过{$max_length}字的简洁版本：\n\"{$title}\"\n"
                  ."要求：\n"
                  ."1. 保留标题的核心主题和关键信息\n"
                  ."2. 精简冗余词汇，但保持原标题的语气和风格\n"
                  ."3. 如果包含数字，尽量保留这些数字\n"
                  ."4. 请参考以下示例标题的风格和结构，包括是否使用疑问句、数字等元素：\n   \"{$random_title}\"\n"
                  ."5. 确保改写后的标题仍然吸引人且有点击欲\n"
                  ."6. 避免使用《》【】()等特殊符号\n"
                  ."7. **重要：** 处理包含数字范围的句子（例如 '5-12岁、1990-2005年'）时，请确保其保持完整，勿拆分或格式化为列表。\n";

								$rewrite_result = $chat_gpt->ask($rewrite_prompt);
								$new_title = trim($rewrite_result['answer'] ?? '');
								if ($new_title !== '') {
									$title = $new_title;
									$is_rewrite_type = 'length'; // 记录为超长改写
								} else {
									$title = mb_substr($title, 0, $max_length);
								}
							} else {
								// 只截断
								$title = mb_substr($title, 0, $max_length);
							}
						}

						// --- 新增：根据标题生成描述 --- 
						// $should_generate_desc = $this->settings['global_tabs']['generate-description-from-title'] ?? false; // 旧的独立开关检查
						$enable_long_text = $this->settings['global_tabs']['enable_long_text_generation'] ?? false;
						// $should_generate_desc_setting = $this->settings['global_tabs']['generate-description-from-title'] ?? false; // 不再需要检查这个开关

						// 仅当长文本模式开启时才执行
						// if ($enable_long_text && $should_generate_desc_setting && !empty($title)) { // 旧条件
						if ($enable_long_text && !empty($title)) { // 新条件：只检查长文本模式
							error_log("AI Post Debug: Task {$key} - 触发根据标题生成描述功能（长文本模式激活）");
							$desc_prompt = "请根据以下文章标题，写一段350字以内的{$display_language}描述，作为文章的引言或摘要。描述应该简洁、吸引人，并概括文章核心内容。请直接输出描述文字，不要添加任何额外说明或标题。标题：\"{$title}\"";
							try {
								$desc_temp = $chat_gpt->ask($desc_prompt);
								if (isset($desc_temp['usage'])) {
									error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (Description - Task {$key}): " . json_encode($desc_temp['usage']));
								}
								$generated_description = Markdown::markdownToHtml(trim($desc_temp['answer'] ?? ''));
								// 使用多字节安全的截断，防止中文在日志预览中被截断导致乱码
								$__desc_preview = function_exists('mb_substr') ? mb_substr($generated_description, 0, 100) : substr($generated_description, 0, 100);
								error_log("AI Post Debug: Task {$key} - 成功生成描述: " . $__desc_preview . "...");
							} catch (Exception $e) {
								error_log("AI Post Debug: Task {$key} - 根据标题生成描述失败: " . $e->getMessage());
							}
						}
						// --- 结束：根据标题生成描述 --- 

						} if (!empty($step['receive-type']) && $step['receive-type'] === 'content') {
							// 获取用户选择的分类ID
							$category_id = $this->settings['tasks'][$key]['post-category'] ?? null;

						// 获取分类名称
						$category_name = '';
						if ($category_id) {
							$category = get_term($category_id, 'category'); // 获取分类对象
							if (!is_wp_error($category) && $category) {
								$category_name = $category->name; // 获取分类名称
							}
						}
						
						// 获取用户选择的语言
						$user_language = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
						$language_map = LanguageUtils::get_language_map();
						
						// 如果是自定义语言，则使用用户提供的语言描述
						if ($user_language === 'custom') {
							$custom_language_code = $this->settings['tasks'][$key]['custom-language-code'] ?? '';
							$custom_language_description = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
							$display_language = !empty($custom_language_description) ? $custom_language_description : '自定义语言';
						} else {
							$display_language = $language_map[$user_language] ?? '中文简体'; // 默认中文
						}

						// 处理主体内容
						// 定义内置指令以禁止使用有序列表和总结性结尾
						$instruction_content = "\n生成内容要求：\n"
						                       . "1. 文章语言请严格使用标准的{$display_language}，无论提示词是什么语言，请按照标准{$display_language}生成，用说人话的方式写作，像和朋友聊天一样\n"
						                       . "2. 请不要在文章开头使用<h1>标题\n"
						                       . "3. 不要使用开场白和总结性结尾，那样很官方，请直入主题，保持口语化创作\n"
											   . "4. 请生成和（{$category_name}）分类相关的文章内容\n"
						                       . "5. 千万不要使用总结性结尾，避免使用总结、展望、结尾的思考等相关的词语收尾\n"
						                       . "6. 以Markdown格式输出，确保排版清晰，如果需要列出要点，请务必使用标准的 Markdown 列表格式，例如：\n- 要点一\n- 要点二\n或者：\n1. 第一点\n2. 第二点\n绝对禁止直接输出 <li> HTML 标签。\n"
						                       . "7. **关于文章结构**：\n"
                                               . "   - 整篇文章最多使用2-3个二级标题(H2)进行主要分段\n"
                                               . "   - 每个二级标题下内容至少500字，进行充分展开\n" 
                                               . "   - 只在必要时才使用三级标题(H3)，且每个二级标题下最多包含2-3个三级标题\n"
                                               . "   - 避免过多的标题层级和频繁切换话题，保持内容连贯性\n"
                                               . "   - 每个段落至少4-5句话，避免过短段落\n"
                                               . "8. **重要：** 处理包含数字范围的句子（例如 '5-12岁、1990-2005年'）时，请确保其保持完整，勿拆分或格式化为列表。\n"
											   . "9. 如果有必要，请在文章中插入权威站点的参考链接，以提高文章的可信度，但是链接一定要加上nofollow标签\n"
                                               . "10. **写作风格与内容框架**：\n"
                                               . "    - 严格遵循E-E-A-T框架（经验、专业知识、权威、信任）\n"
                                               . "    - 经验(Experience)：穿插自己或身边人的实操经历，如'去年帮朋友的美食博客调整了标题，3个月内流量涨了50%'\n"
                                               . "    - 专业知识(Expertise)：分步骤讲清楚逻辑，别直接甩结论，如'为什么标题要放关键词？因为谷歌机器人先看标题判断内容是否匹配搜索词'\n"
                                               . "    - 权威(Authority)：引用1-2个可信来源，如'谷歌官方博客曾提到，优质内容要让读者看完后觉得有帮助'\n"
                                               . "    - 信任(Trust)：提供可验证的建议，如'写完文章后，可以用谷歌站长工具检查有没有错别字或死链'\n"
                                               . "11. **文章结构模式**：\n"
                                               . "    - 开篇：用痛点引入+解决方案，如'你有没有过这种情况？辛辛苦苦写了博客，结果搜关键词时根本找不到自己的文章？'\n"
											   . "    -不要用*今天我们将深入探讨*、*我们将深入解析*等官方的开场白，直入主题，用说人话方式开场'\n"
                                               . "    - 直接给承诺，如'今天分享一套我自己用过的笨办法，没学过SEO也能跟着做，亲测有效'\n"
                                               . "    - 正文：分步骤讲具体操作，讲人话技巧，如'想想你自己搜东西时会搜什么词'\n"
                                               . "    - 穿插经验分享，如'我之前写XX阅读量很低，后来改成XX，点击量翻了3倍'\n"
                                               . "    - 用大白话解释专业概念\n"
                                               . "    - 结尾引导互动，如'如果你按这些方法试了，欢迎回来告诉我效果！'\n"
                                               . "12. **语气和表达**：\n"
                                               . "    - 多用'我'和'你'拉近距离，如'我建议你试试'、'你可能会发现'\n"
                                               . "    - 避免绝对化表达，不说'必须这样做'，改用'亲测这个方法比较有效'、'可以优先考虑'\n"
                                               . "    - 口语化表达，像在和朋友聊天，避免官方、生硬的表达\n"; // 修改指令

						// --- 修改：检查表格生成设置 --- 
						$enable_tables = $this->settings['global_tabs']['enable_table_generation'] ?? false;
						$table_instruction = ''; // 初始化为空

						if ($enable_tables) {
							// 只有当总开关开启时，才读取格式选项
							$table_format = $this->settings['global_tabs']['table_generation_format'] ?? 'html'; // 默认 html

							switch ($table_format) {
								case 'div':
									$table_instruction = <<<INSTR
7. 在内容需要的段落中插入{$display_language}表格，表格请用 **div** 方式生成excel形式的表格，并且带深浅不同的行背景色，要求包含以下要素：
   - 表头：明确列名称（**不要超过5列**）。
   - 行和列：用style属性布局行和列，比如三行五列，保持15px行和列间距，做到标签闭合规范。
   - 文本对齐方式：默认**居中对齐**。
   - 样式：为表格行添加**不同的背景色**以美化，列与列之间保持适当间距。
   - 数据规范：每行数据对应表头且内容简洁。
   - 必要时添加表格标题或注释。
INSTR;
										error_log("AI Post Debug: Task {$key} - 已启用详细表格生成指令 (DIV方式)");
									break;
								case 'html':
									$table_instruction = <<<INSTR
7. 在内容需要的段落中插入{$display_language}表格，请使用标准的 **HTML `<table>` 标签** 来生成excel形式带实线边框的表格，用深浅不同的背景区分行，要求包含以下要素：
   - 结构：使用 `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` 标签。
   - 表头：`<th>` 标签应包含明确的列名称（**不要超过5列**）。
   - 对齐方式：建议通过内联 style 或 class 实现内容**居中对齐**。
   - 数据规范：每行 `<tr>` 内的 `<td>` 数据对应表头且内容简洁。
   - 必要时在表格外部添加标题或注释。
INSTR;
										error_log("AI Post Debug: Task {$key} - 已启用表格生成指令 (HTML <table>方式)");
									break;
								case 'markdown':
									$table_instruction = <<<INSTR
7. 在内容需要的段落中插入{$display_language}表格，表格请使用 **Markdown 表格语法** 来生成excel形式的表格，要求包含以下要素：
   - 表头：明确列名称（**不要超过5列**）。
   - 分隔符：正确使用 `|---|---|...` 分隔表头和内容。
   - 对齐方式：如果可能，使用冒号 `:` 控制列的对齐方式（例如 `|:---:|` 居中）。
   - 数据规范：每行数据对应表头且内容简洁。
INSTR;
										error_log("AI Post Debug: Task {$key} - 已启用表格生成指令 (Markdown方式)");
									break;
							}
						} // 结束 if ($enable_tables)

						if (!empty($table_instruction)) {
							$instruction_content .= "\n" . $table_instruction; // 添加换行确保指令清晰
						}
						// --- 结束修改 ---

						// --- 新增：检查并添加长文本生成指令 ---
						$enable_long_text = $this->settings['global_tabs']['enable_long_text_generation'] ?? false;
						if ($enable_long_text) {
							$long_text_instruction = <<<INSTR
**长文本生成模式要求**：
	- "1. 文章语言请严格使用标准的{$display_language}，无论提示词是什么语言，请按照标准{$display_language}生成，用说人话的方式写作，像和朋友聊天一样\n"
   - 2. 采用自然的问答或论述方式组织段落，避免生硬过渡，完全口述拟人化创作。
   - 3. **文章结构要求**：
     * 整篇文章最多使用2-3个二级标题(H2)进行主要分段
     * 每个二级标题下内容至少800字，进行充分展开和深入分析
     * 只在必要时才使用三级标题(H3)，且每个二级标题下最多包含2-3个三级标题
     * 避免过多的标题层级和频繁切换话题，保持内容连贯性
     * 每个段落至少8-10句话，内容丰富且连贯，避免过短段落
   - 4. **严格遵循E-E-A-T框架**：
     * 经验(Experience)：穿插真实或虚构的实操经历，如"去年我帮一个客户做了这个调整，效果提升了50%"
     * 专业知识(Expertise)：详细解释每个步骤背后的原理和逻辑，不只告诉读者做什么，还要解释为什么这样做
     * 权威(Authority)：引用1-2个业内公认的权威来源支持你的观点，增加可信度
     * 信任(Trust)：提供读者可以立即实践的具体建议和验证方法
   - 5. **内容结构模式**：
     * 开篇：用痛点引入+解决方案，直接给承诺
     * 正文：分步骤详细讲解，用读者熟悉的例子和场景，穿插个人经验分享
     * 用大白话解释复杂概念，避免过多专业术语
     * 结尾：引导读者互动或尝试你提供的方法
   - 6. **语气和表达**：
     * 多用"我"和"你"拉近与读者距离，创造对话感
     * 避免绝对化表达，使用"通常"、"可能"、"亲测有效"等灵活表达
     * 保持友好、平等的交流语气，像在帮助朋友解决问题
   - 7. **重要：** 包含数字范围的句子（如 '5-12岁、1990-2025年'）应保持完整，勿拆分或格式化为列表。
INSTR;
							$instruction_content .= "\n" . $long_text_instruction;
							error_log("AI Post Debug: Task {$key} - 已启用长文本生成指令");
						}

						// 显式注入标题和分类，确保与标题强相关
						$context_prefix = '';
						if (!empty($title)) {
							$context_prefix .= "请严格围绕以下文章标题生成正文，并确保与标题高度相关、一致：\n标题：\"{$title}\"\n";
						}
						if (!empty($category_name)) {
							$context_prefix .= "文章分类（用于限定话题范围）：{$category_name}\n";
						}
						// 将用户的指令与内置指令结合
						$full_instruction_content = $context_prefix . $step['send'] . $instruction_content;

						$temp = $chat_gpt->ask($full_instruction_content); // 使用合并后的指令

						// 添加 Token 使用量日志 (内容生成)
						if (isset($temp['usage'])) {
							error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (Content - Task {$key}): " . json_encode($temp['usage']));
						}

						// 添加日志记录 AI 返回的原始答案
						error_log('AI Raw Answer for Content: ' . print_r($temp['answer'] ?? '[No Answer Found]', true));

						$content .= $temp['answer'];

						// 添加日志记录追加后的内容
						// error_log('Content after appending AI answer: ' . $content); // Commented out as it can be very verbose

						// 如果标题为空，尝试从文章内容（content）中提取第一个出现的<h1>, <h2>或<h3>标签的内容作为标题，并且移除这个HTML块以优化内容。
 						if (empty($title) && preg_match('/(<h[1-3][^>]*>.*?<\/h[1-3]>)/i', $content, $matches)) {
 							$title = strip_tags($matches[1]);
							$content = str_replace($matches[0], '', $content);
						}
					}
				}
				catch (\Exception $e) {
					// 记录详细错误信息，指明失败的步骤
					error_log("AI Post task [{$key}], step [{$k}] failed: " . $e->getMessage());
					// 可以选择记录更详细的堆栈跟踪
					// error_log("Stack Trace: " . $e->getTraceAsString());
					
					// 跳过当前失败的步骤，继续下一个
					continue; 
					// throw $e; // 不再直接抛出异常中断整个任务
				}
			}

			// --- 修改：在处理其他内容前，先将生成的描述添加到内容开头 --- 
			if (!empty($generated_description)) {
				// 添加 <p> 标签进行基本格式化
				$content = '<p>' . $generated_description . '</p>' . $content;
				error_log("AI Post Debug: Task {$key} - 已将生成的描述添加到内容开头");
			}
			// --- 结束修改 ---

			$content = preg_replace('/^\s*.*\R/', '', $content, 1);
			$content = preg_replace('/^\s*\R/m', '', $content);

            $content = Markdown::markdownToHtml($content);
            $content = str_replace('&nbsp;', '', $content);

            // --- 新增：如果启用了长文本模式，则追加 FQA 内容 ---
            $enable_long_text = $this->settings['global_tabs']['enable_long_text_generation'] ?? false;
            if ($enable_long_text && !empty($title) && !empty($content)) {
                error_log("AI Post Debug: Task {$key} - 长文本模式已启用，尝试生成并追加 FQA 内容");
                try {
                    // 获取语言设置用于 FQA
                    $user_language_faq = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
                    $language_map_faq = LanguageUtils::get_language_map();
                    
                    // 如果是自定义语言，则使用用户提供的语言描述
                    if ($user_language_faq === 'custom') {
                        $custom_language_code_faq = $this->settings['tasks'][$key]['custom-language-code'] ?? '';
                        $custom_language_description_faq = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
                        $display_language_faq = !empty($custom_language_description_faq) ? $custom_language_description_faq : '自定义语言';
                    } else {
                        $display_language_faq = $language_map_faq[$user_language_faq] ?? '中文简体';
                    }

                    // 提取 H2/H3 作为提纲，生成≤500字本地摘要
                    $outline_items = [];
                    try {
                        libxml_use_internal_errors(true);
                        $dom_for_outline = new \DOMDocument();
                        $wrapped = '<div>' . $content . '</div>';
                        $encoded = mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8');
                        $dom_for_outline->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
                        libxml_clear_errors();
                        $xp = new \DOMXPath($dom_for_outline);
                        foreach (['h2','h3'] as $hx) {
                            $nodes = $xp->query('//' . $hx);
                            foreach ($nodes as $n) {
                                $t = trim($n->textContent);
                                if ($t !== '') { $outline_items[] = $t; }
                            }
                        }
                    } catch (\Exception $e) {
                        error_log("AI Post Debug: Task {$key} - 解析提纲失败: " . $e->getMessage());
                    }

                    $outline_text = '';
                    if (!empty($outline_items)) {
                        // 使用简单行列表，减少 token
                        $outline_text = implode("\n", array_map(function($s){ return $s; }, $outline_items));
                    }
                    $summary_500 = mb_substr(wp_strip_all_tags($content), 0, 500);

                    try {
                        // 构建精简 FQA 请求指令（一次调用直出 HTML）
                        $faq_prompt = "你是内容编辑。基于下述\"标题、提纲、摘要\"，输出3-5组与主题高度相关的FQA，标题为：本文常见问题（FQA）（{$display_language_faq}）。\n"
                                    . "仅输出HTML片段，禁止输出<pre><code>标签，不要任何解释：每组为 <h3>问题</h3><p>回答段1…</p><p>回答段2…</p>。\n"
                                    . "要求：自然口语化；不用列表/序号；避免总结性结尾；包含数字范围的句子保持完整。\n\n"
                                    . "【标题】\n{$title}\n\n"
                                    . (empty($outline_text) ? "" : "【提纲（H2/H3）】\n{$outline_text}\n\n")
                                    . "【摘要（≤500字）】\n{$summary_500}\n";

                        $faq_temp = $chat_gpt->ask($faq_prompt);

                        // 记录 Token 使用量 (FQA 生成)
                        if (isset($faq_temp['usage'])) {
                            error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (FQA - Task {$key}): " . json_encode($faq_temp['usage']));
                        }

                        if (!empty($faq_temp['answer'])) {
                            // 直接作为 HTML 片段使用
                            $faq_content_html = trim($faq_temp['answer']);
                            if (stripos($faq_content_html, '<h3') !== false) {
                                $content .= "\n<hr>\n" . $faq_content_html; // 直接追加 FQA HTML
                                error_log("AI Post Debug: Task {$key} - 成功生成并追加了原始 FQA 内容");
                            } else {
                                error_log("AI Post Debug: Task {$key} - AI 返回的内容不含 H3，已跳过。片段: " . substr(wp_strip_all_tags($faq_content_html), 0, 200));
                            }
                        } else {
                            error_log("AI Post Debug: Task {$key} - AI 未返回 FQA 内容");
                        }
                    } catch (\Throwable $e) {
                        error_log("AI Post Debug: Task {$key} - 生成 FQA 内容失败: " . $e->getMessage());
                        // FAQ 生成失败不应中断整个流程
                    }
                } catch (\Throwable $e) {
                    error_log("AI Post Debug: Task {$key} - 长文本FQA流程异常: " . $e->getMessage());
                }
            }
            // 设置分类和标签（原有代码）

            // 新增：最终清理残留符号，注释掉，否则影响保留表格

			// 定义一个数组，包含所有要移除的词组
			// --- 原来的硬编码数组 (注释掉) ---
			/*
			$words_to_remove = array(
				'首先，',
				'嗨！',
				'导语，',
				// ... (所有原来的词语) ...
				'总结一下，'
			);
			*/

			// --- 新增：读取外部文件 ---
			$builtin_words_file = plugin_dir_path(__FILE__) . 'words_to_remove-zh.txt'; // .txt 文件与 Base.php 在同一目录
			$words_to_remove = []; // 初始化为空数组

			if (file_exists($builtin_words_file) && is_readable($builtin_words_file)) {
				$file_content = file_get_contents($builtin_words_file);
				if ($file_content !== false) {
					// 按行分割，去除每个词的前后空格，并过滤掉空行
					$words_to_remove = array_filter(array_map('trim', explode("\n", $file_content)));
				} else {
					// 文件读取失败的处理，可以记录错误日志
					error_log('AI Post Error: Could not read the filter words file: ' . $builtin_words_file);
				}
			} else {
				// 文件不存在或不可读的处理，可以记录错误日志
				error_log('AI Post Error: Filter words file not found or not readable: ' . $builtin_words_file);
			}

			// 获取用户输入的屏蔽词
			$user_input = $this->settings['tasks'][$key]['post-content-shield'] ?? '';

			// 将用户输入按行分割成数组，并去除空行和多余空格
			$user_words = array_filter(array_map('trim', explode("\n", $user_input))); // 去除空行和多余空格

			// 合并内置词组和用户输入的词组到移除词组数组中
			$words_to_remove = array_merge($words_to_remove, $user_words);

			// 移除重复的词，避免不必要的处理
			$words_to_remove = array_unique($words_to_remove);

			// 遍历数组，使用正则表达式移除每个词组及其后的常见标点和多余空格
			foreach ($words_to_remove as $word) {
				if (empty($word)) { // 跳过可能的空字符串
					continue;
				}
				// 1. 转义正则表达式特殊字符，防止 $word 中的字符干扰正则模式
				$escaped_word = preg_quote($word, '/');

				// 2. 构建正则表达式模式：
				//    - \s* : 匹配词语前面可能存在的零个或多个空格
				//    - $escaped_word : 匹配转义后的词语本身
				//    - \s* : 匹配词语后面可能存在的零个或多个空格
				//    - [:：,，.。;；?？！]? : 匹配一个可选的中英文标点符号 (冒号、逗号、句号、分号、问号、感叹号)
				//       '?' 表示前面的标点字符组是可选的（出现 0 次或 1 次）
				//    - /u : 使用 UTF-8 模式，确保正确处理中文字符
				$pattern = '/\s*' . $escaped_word . '\s*[:：,，.。;；?？！]?\s*/u';

				// 3. 执行替换：将匹配到的模式替换为单个空格
				//    替换为单个空格是为了防止原本被移除词语隔开的两个词粘连在一起
				$content = preg_replace($pattern, ' ', $content);
			}

			// 清理可能产生的连续多个空格，替换为单个空格
			$content = preg_replace('/\s{2,}/u', ' ', $content);
			// 清理开头和结尾可能多余的空格
			$content = trim($content);

			// 将Markdown转换为HTML
			$content = Markdown::markdownToHtml($content);
			$content = str_replace('&nbsp;', '', $content);

			// 新增：最终清理残留符号，注释掉，否则影响保留表格
			// MARKER: HTML_QUOTE_REMOVAL_START
			// $content = preg_replace('/(["\'`])/', '', $content); // 修复：简化正则，匹配常见引号和反引号
			// MARKER: HTML_QUOTE_REMOVAL_END

			// 在内容处理流程中添加
			$content = preg_replace('/`+/', '', $content); // 修复：匹配一个或多个反引号

			// --- 新增：清理表格内无效的 P 标签 ---
			$content = preg_replace_callback('/<table(.*?)<\/table>/is', function($matches) {
				// 移除 table 内部的 <p> 和 </p> 标签，保留内容
				$cleaned_table_content = preg_replace('/<\/?p>/i', '', $matches[0]);
				return $cleaned_table_content;
			}, $content);
			// --- 结束清理 ---

			// --- START: Prepare custom insertions (but don't insert yet) ---
			$global_before_html = '';
			$task_before_html = '';
			$global_middle_html = '';
			$task_middle_html = ''; // ADDED: Prepare task middle content
			$task_after_html = '';
			$global_after_html = '';

			// Prepare Global Before
			$global_before_content = $this->settings['global_tabs']['global_before_content'] ?? '';
			if (!empty($global_before_content)) {
				// 处理随机段落
				$global_before_content = $this->process_random_paragraphs($global_before_content);
				$global_before_html = '<!-- AI-GLOBAL-START --><div class="custom-insert-global" data-charset="utf8">' . $global_before_content . '</div><!-- AI-GLOBAL-END -->';
			}

			// 检查任务自定义内容开关是否启用
			$enable_custom_content = $this->settings['tasks'][$key]['enable-custom-content'] ?? false;
			
			// 只有当开关启用时，才准备任务自定义内容
			if ($enable_custom_content) {
				// 任务级别自定义内容存储在custom-content-tabs结构中
				$custom_content_tabs = $this->settings['tasks'][$key]['custom-content-tabs'] ?? [];
				
				// Prepare Task Before
				$task_before_content = $custom_content_tabs['custom-before-content'] ?? '';
				if (!empty($task_before_content)) {
					// 处理随机段落
					$task_before_content = $this->process_random_paragraphs($task_before_content);
					$task_before_html = '<!-- AI-TASK-START --><div class="custom-insert-task">' . $task_before_content . '</div><!-- AI-TASK-END -->';
				}
	
				// Prepare Task Middle
				$task_middle_content = $custom_content_tabs['custom-middle-content'] ?? '';
				if (!empty($task_middle_content)) {
					// 处理随机段落
					$task_middle_content = $this->process_random_paragraphs($task_middle_content);
					$task_middle_html = '<!-- AI-TASK-MIDDLE-START --><div class="custom-insert-task-middle">' . $task_middle_content . '</div><!-- AI-TASK-MIDDLE-END -->';
				}
	
				// Prepare Task After
				$task_after_content = $custom_content_tabs['custom-after-content'] ?? '';
				if (!empty($task_after_content)) {
					// 处理随机段落
					$task_after_content = $this->process_random_paragraphs($task_after_content);
					$task_after_html = '<!-- AI-TASK-END-START --><div class="custom-insert-task-end">' . $task_after_content . '</div><!-- AI-TASK-END-END -->';
				}
				
				// 调试日志，记录已加载的任务级自定义内容
				if (!empty($task_before_content) || !empty($task_middle_content) || !empty($task_after_content)) {
					error_log("AI Post Debug: Task {$key} - 已加载任务级自定义内容");
				}
			} else {
				error_log("AI Post Debug: Task {$key} - 任务自定义内容插入功能未启用");
			}

			// Prepare Global Middle
			$global_middle_content = $this->settings['global_tabs']['global_middle_content'] ?? '';
			// 处理随机段落
			if (!empty($global_middle_content)) {
				$global_middle_content = $this->process_random_paragraphs($global_middle_content);
				$global_middle_html = $global_middle_content;
			} else {
				$global_middle_html = '';
			}

			// Prepare Global After
			$global_after_content = $this->settings['global_tabs']['global_after_content'] ?? '';
			// 处理随机段落
			if (!empty($global_after_content)) {
				$global_after_content = $this->process_random_paragraphs($global_after_content);
				$global_after_html = $global_after_content;
			} else {
				$global_after_html = '';
			}
			// --- END: Prepare custom insertions ---

			// 全局中间插入
			// Note: Global Middle insertion will be handled after TOC generation

			            // --- 去重支持（最小侵入）：初始化已见集合与辅助闭包 ---
            $seen = [
                'urls' => [],                // normalized_url => true
                'attachment_ids' => [],      // id => true
                'url_to_attachment' => [],   // normalized_url => attachment_id
            ];
            $normalize_url_for_dedup = function (string $url): string {
                $u = trim($url);
                // 去除 fragment
                $pos = strpos($u, '#');
                if ($pos !== false) { $u = substr($u, 0, $pos); }
                // 拆解 URL
                $parts = @parse_url($u);
                if (!is_array($parts)) return $u;
                $scheme = isset($parts['scheme']) ? strtolower($parts['scheme']) . '://' : '';
                $host   = isset($parts['host']) ? strtolower($parts['host']) : '';
                $path   = isset($parts['path']) ? $parts['path'] : '';
                // 规范化末尾斜杠
                if ($path !== '/' && substr($path, -1) === '/') { $path = rtrim($path, '/'); }
                // query 规范化（排序）
                $query  = '';
                if (!empty($parts['query'])) {
                    parse_str($parts['query'], $q);
                    if (is_array($q) && !empty($q)) {
                        ksort($q);
                        $query = '?' . http_build_query($q);
                    }
                }
                return $scheme . $host . $path . $query;
            };
            $is_seen = function (array $seen, ?int $attachment_id, string $url) use ($normalize_url_for_dedup): bool {
                if ($attachment_id && isset($seen['attachment_ids'][$attachment_id])) return true;
                $norm = $normalize_url_for_dedup($url);
                return isset($seen['urls'][$norm]);
            };
            $mark_seen = function (array &$seen, ?int $attachment_id, string $url) use ($normalize_url_for_dedup): void {
                $norm = $normalize_url_for_dedup($url);
                $seen['urls'][$norm] = true;
                if ($attachment_id && $attachment_id > 0) {
                    $seen['attachment_ids'][$attachment_id] = true;
                    $seen['url_to_attachment'][$norm] = $attachment_id;
                }
            };

            // --- 新增：在获取$post_id后执行方式一图片侧载，生成<img> HTML ---
            // 防止未定义 $post_id 触发 Notice（在创建文章前，该变量可能尚未赋值）
            // 这里仅初始化为 0，不改变原有时序；真正绑定父子关系需在创建文章并拿到有效ID后再处理
            $post_id = isset($post_id) ? (int)$post_id : 0;
            $downloaded_images = [];
            if ($enable_image_url && !empty($pending_image_urls)) {
                foreach ($pending_image_urls as $idx => $pending_url) {
                    try {
                        // 改为直接侧载以获取 attachment_id 与最终本地 URL，便于去重
                        $attach_res = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::attach_remote_image_to_post((int)$post_id, (string)$pending_url, false, '', 60);
                        if (!empty($attach_res['ok']) && !empty($attach_res['url'])) {
                            $final_url = (string)$attach_res['url'];
                            $att_id    = isset($attach_res['attachment_id']) ? (int)$attach_res['attachment_id'] : 0;
                            if ($is_seen($seen, $att_id, $final_url)) {
                                error_log('[AI-Post][Dedup] skip duplicated method-1 image: url=' . $final_url . ' id=' . $att_id);
                                continue;
                            }
                            // 构造 <img>，保持与先前行为一致（可选 alt 空字符串）
                            $alt_attr_str = $auto_alt_enabled_global ? ' alt=""' : '';
                            $img_html = '<img src="' . esc_url($final_url) . '"' . $alt_attr_str . ' />';
                            $downloaded_images[] = $img_html;
                            $mark_seen($seen, $att_id, $final_url);
                        } else {
                            // 失败则回退远程 IMG，同时记录 seen（按远程URL，避免后续再次尝试同图）
                            $alt_attr_str = $auto_alt_enabled_global ? ' alt=""' : '';
                            $img_html = '<img src="' . esc_url((string)$pending_url) . '"' . $alt_attr_str . ' />';
                            $downloaded_images[] = $img_html;
                            $mark_seen($seen, null, (string)$pending_url);
                            error_log('[AI Post] 方式一图片侧载失败（fallback remote IMG），URL: ' . $pending_url . ' PostID: ' . (int)$post_id . ' | error=' . ($attach_res['error'] ?? 'unknown'));
                        }
                    } catch (\Throwable $e) {
                        error_log('[AI Post] 方式一图片侧载异常: ' . $e->getMessage() . ' | URL: ' . $pending_url . ' | PostID: ' . (int)$post_id);
                    }
                }
                error_log('[AI Post] 方式一图片侧载完成，成功生成HTML数量: ' . count($downloaded_images) . ' / ' . count($pending_image_urls) . '，PostID: ' . (int)$post_id);
            }

            // --- 修改：分散插入图片（方式一）---
            if ($enable_image_url && !empty($downloaded_images)) { // 使用侧载后的图片HTML
				// 添加表格保护机制
				$table_placeholders = [];
				$placeholder_index = 0;
				
				// 保护HTML表格
				$content_with_placeholders = preg_replace_callback(
					'/<table[^>]*>.*?<\/table>/is',
					function($matches) use (&$table_placeholders, &$placeholder_index) {
						$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
						$table_placeholders[$placeholder] = $matches[0];
						return $placeholder;
					},
					$content
				);
				
				// 保护Markdown表格格式 - 加强版
				if ($content_with_placeholders) {
					// 保护标准的Markdown表格格式
					$content_with_placeholders = preg_replace_callback(
						'/(\|[^\n]+\|\n)(\|[\s\-:]+\|\n)(\|[^\n]+\|\n)+/s',
						function($matches) use (&$table_placeholders, &$placeholder_index) {
							$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
							$table_placeholders[$placeholder] = $matches[0];
							return $placeholder;
						},
						$content_with_placeholders
					);
					
					// 保护可能被打断的Markdown表格行
					$content_with_placeholders = preg_replace_callback(
						'/\|\s*([^\n|]+\|)+\s*\n/',
						function($matches) use (&$table_placeholders, &$placeholder_index) {
							$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
							$table_placeholders[$placeholder] = $matches[0];
							return $placeholder;
						},
						$content_with_placeholders
					);
				}
				
				// 使用保护后的内容
				if (!empty($table_placeholders)) {
					$content = $content_with_placeholders;
					error_log("AI Post Debug: 图片分散插入前保护了 " . count($table_placeholders) . " 个表格结构");
				}
				
				// 原有的图片分散插入逻辑
				$content_parts = preg_split('/(<\/p>)/i', $content, -1, PREG_SPLIT_DELIM_CAPTURE); // 保留分隔符</p>
				$total_paragraphs = floor(count($content_parts) / 2); // 大约的段落数
				$images_to_insert = count($downloaded_images);
				$image_inserted_count = 0;

                // 调试：记录插入前的统计数据
                error_log('AI Post Debug: 分散插入前统计 => paragraphs=' . (int)$total_paragraphs . ', images=' . (int)$images_to_insert . ', content_len=' . strlen((string)$content));

                if ($total_paragraphs > 0 && $images_to_insert > 0) {
                    $insert_interval = max(1, floor($total_paragraphs / ($images_to_insert + 1))); // 计算插入间隔
                    $new_content = '';
                    $paragraph_counter = 0;

                    for ($i = 0; $i < count($content_parts); $i += 2) {
                        $part = $content_parts[$i];
                        $delimiter = $content_parts[$i+1] ?? ''; // 获取 </p> 或末尾空串
                        $new_content .= $part . $delimiter;
                        $paragraph_counter++;

                        // 检查是否到达插入点且还有图片未插入
                        if (($paragraph_counter % $insert_interval === 0 || $paragraph_counter === $total_paragraphs) // 在间隔点或最后一段后插入
                            && $image_inserted_count < $images_to_insert) 
                        {
                            $current_image_html = $downloaded_images[$image_inserted_count];
                            // 用 figure 包裹图片 (与方式二统一，使用 size-full)
                            $wrapped_image_html = '<figure class="wp-block-image size-full aligncenter">' . $current_image_html . '</figure>';
                            $new_content .= $wrapped_image_html;
                            $image_inserted_count++;
                        }
                    }
                    $content = $new_content; // 更新内容
                    error_log('AI Post Debug: 分散插入完成 => inserted=' . (int)$image_inserted_count . ', interval=' . (int)$insert_interval . ', content_len=' . strlen((string)$content));
                    
                    // 恢复表格结构
                    if (!empty($table_placeholders)) {
                        $content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
                        error_log("AI Post Debug: 图片分散插入后恢复了 " . count($table_placeholders) . " 个表格结构");
                    }
                } elseif ($total_paragraphs === 0 && $images_to_insert > 0) {
                    // 兜底策略：当正文没有<p>段落时，将图片插入正文开头，避免图片始终为0的情况
                    $prepend_html = '';
                    foreach ($downloaded_images as $img_html) {
                        $prepend_html .= '<figure class="wp-block-image size-full aligncenter">' . $img_html . '</figure>';
                        $image_inserted_count++;
                    }
                    $content = $prepend_html . $content;
                    error_log('AI Post Debug: 无段落兜底插入 => inserted=' . (int)$image_inserted_count . ', content_len=' . strlen((string)$content));
                }
            }
            // --- 结束修改 ---

			// --- 新增：生成并插入文章目录 --- 
			$toc_html = ''; // Initialize TOC HTML
			$toc_enabled = $this->settings['global_tabs']['enable-toc'] ?? false;
			if ($toc_enabled) {
				$toc_settings = [
					// Pass necessary settings for TOC generation
					'title' => $this->settings['global_tabs']['toc-title-text'] ?? '文章目录',
					'background' => $this->settings['global_tabs']['toc-background-color'] ?? '#f9f9f9',
					'link_color' => $this->settings['global_tabs']['toc-link-color'] ?? '#1a0dab', // 读取新的链接颜色设置
					'min_headings' => $this->settings['global_tabs']['toc-min-headings'] ?? 3,
				];
				// Call generate_and_insert_toc with the current $content (which is the core content at this point)
				// It returns the TOC HTML and the content modified with heading IDs
				list($toc_html, $content) = $this->generate_and_insert_toc($content, $toc_settings);
				// $content is now the core content with heading IDs potentially added
			}
			// --- 结束：生成并插入文章目录 --- 

			// --- START: Assemble final content in the correct order ---
			$content = $toc_html . $global_before_html . $task_before_html . $content;
			// --- END: Assemble final content ---

			// --- START: Protect table structure before middle insertion ---
			$table_placeholders = [];
			$placeholder_index = 0;
			$content = preg_replace_callback(
				'/<table(.*?)<\/table>/is', // Find table blocks
				function($matches) use (&$table_placeholders, &$placeholder_index) {
					$placeholder = '%%AI_TABLE_PROTECT_' . $placeholder_index++ . '%%';
					$table_placeholders[$placeholder] = $matches[0]; // Store the original table HTML
					return $placeholder;
				},
				$content
			);
			if (!empty($table_placeholders)) {
				error_log("AI Post Debug: Protected " . count($table_placeholders) . " table(s) before middle insertion.");
			}
			// --- END: Protect table structure ---

			// --- Handle Combined Middle Insertion (Global + Task) ---
			// 添加调试日志
			if (empty($global_middle_html)) {
				error_log("AI Post Debug: Task {$key} - global_middle_html 为空");
			}
			if (empty($task_middle_html)) {
				error_log("AI Post Debug: Task {$key} - task_middle_html 为空");
			}
			
			$combined_middle_html = $global_middle_html . $task_middle_html; // Combine global and task middle content
			if (!empty($combined_middle_html)) {
				// Try splitting by common block-level closing tags
				$split_pattern = '/(<\/p>|<\/div>|<\/ul>|<\/ol>|<\/table>|<br\\s*\\/?>)/i';
				$content_parts = preg_split($split_pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);

				if (count($content_parts) > 1) {
					// Calculate insertion point based on the number of *pairs* (tag + content before it)
					$num_blocks = floor(count($content_parts) / 2);
					$insert_index = max(1, floor($num_blocks / 2)) * 2; // Find the index *before* the closing tag where we want to insert

					// Insert the middle content
					array_splice($content_parts, $insert_index, 0, $combined_middle_html);

					// Re-join the parts
					$content = implode('', $content_parts);
					error_log("AI Post Debug: Inserted combined middle content (global+task) using preg_split.");
				} else {
					 // Fallback: If splitting didn't work well, insert based on string length (less ideal)
					 error_log("AI Post Debug: preg_split for middle insertion failed. Falling back to string position for combined middle content.");
					 $content_len = mb_strlen($content);
					 $insert_pos = floor($content_len / 2);
					 // Try to find the end of a tag near the middle to avoid breaking tags
					 $nearest_tag_end = strpos($content, '>', $insert_pos);
					 if ($nearest_tag_end !== false) {
						 $insert_pos = $nearest_tag_end + 1;
					 }
					 $content = mb_substr($content, 0, $insert_pos) . $combined_middle_html . mb_substr($content, $insert_pos);
				}
			}

			// --- START: Restore table structure after middle insertion ---
			if (!empty($table_placeholders)) {
				$content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
				error_log("AI Post Debug: Restored table(s) after middle insertion.");
			}
			// --- END: Restore table structure ---

			// --- Handle Appending Content ---
			$content .= $task_after_html;
			$content .= $global_after_html;
			// --- End Handle Appending Content ---

			// --- START: Add Method 2 Image (Title Image) BEFORE ALT/WebP processing ---
			$enable_image_url_2 = (bool)($this->settings['tasks'][$key]['enable-image-url-2'] ?? false);
			$enable_doubao_first_flag = (bool)($this->settings['tasks'][$key]['enable-doubao-imagegen'] ?? false);

			if (!empty(trim($title))) {
				if ($enable_image_url_2) {
					// 仅执行方式二：插入标题图，不触发 Doubao 分散逻辑，避免被短路
					error_log("AI Post Debug: Task {$key} - Inserting Method-2 (title) image via insert_title_image_only BEFORE ALT/WebP.");
					$content = $this->insert_title_image_only($content, $title);
				} else {
					error_log("AI Post Debug: Task {$key} - Method 2 (title) image is DISABLED.");
				}

				// 同时允许方式三独立执行（若开启），以实现并存
				if ($enable_doubao_first_flag) {
					\error_log('[AI-Post][InsertImage] Execute Doubao distributed insert (method-3) alongside method-2 when enabled. task_key=' . (string)$key);
					$content = $this->insert_doubao_images_distributed($content, $title);
				}
			} else {
				error_log("AI Post Debug: Task {$key} - Skip Method-2/3 image insert because TITLE IS EMPTY.");
			}
			// --- END: Add Method 2 Image ---

			// --- START: New universal ALT and WebP processing point ---
			error_log("AI Post Debug: Task {$key} - Preparing for centralized ALT and WebP processing.");
			// 1. Process ALT attributes for ALL images in the content
			$auto_alt_enabled_setting = $this->settings['global_tabs']['auto-alt-enabled'] ?? true;
			ImageProcessor::process_and_update_alt_attributes($content, $title, $auto_alt_enabled_setting);  // Pass $title as the base for alt text

			// 2. Process WebP Conversion for ALL images if enabled
			// The _process_webp_conversion method itself checks the 'enable-webp-conversion' setting
			$this->_process_webp_conversion($content);
			error_log("AI Post Debug: Task {$key} - Centralized ALT and WebP processing completed.");
			// --- END: New universal ALT and WebP processing point ---

			// --- START: Debug before empty check ---
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Title: '" . $title . "'");
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Content (first 500 chars): " . substr($content, 0, 500));
			$content_after_strip_tags = strip_tags($content, '<img>');
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Content after strip_tags(<img> allowed) (first 500 chars): " . substr($content_after_strip_tags, 0, 500));
			$trimmed_content_after_strip_tags = trim($content_after_strip_tags);
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Content after trim(strip_tags(<img> allowed)) (first 500 chars): " . substr($trimmed_content_after_strip_tags, 0, 500));
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Is title empty? " . (empty(trim($title)) ? 'Yes' : 'No'));
			error_log("AI Post Debug: Task {$key} - BEFORE empty check. Is trimmed_content_after_strip_tags empty? " . (empty($trimmed_content_after_strip_tags) ? 'Yes' : 'No'));
			// 

			// 检查重复标题
			if ($this->settings['global_tabs']['duplicate-check-enabled'] ?? false) {
				// 修复：为 duplicate-check-days 提供默认值 30，避免 Undefined index
				$check_days = $this->settings['global_tabs']['duplicate-check-days'] ?? 30;
				error_log("AI Post Debug: 检查重复标题 '{$title}'，检查天数: {$check_days}");
				
				if ($this->is_duplicate_title($title, $check_days)) {
					$action = $this->settings['global_tabs']['duplicate-action'] ?? 'trash';
					error_log("AI Post Debug: 检测到重复标题 '{$title}'，处理方式: {$action}");
					
					if ($action === 'rewrite') {
						error_log("AI Post Debug: 尝试使用AI改写重复标题 '{$title}'");
						$new_title = $this->rewrite_duplicate_title($title, $chat_gpt);
						if ($new_title) {
							error_log("AI Post Debug: 重复标题AI改写成功: '{$title}' → '{$new_title}'");
							
							// 构建用于改写的提示词
							$prompt_summary = "请将重复标题改写为保持原意但更独特的版本";
							
							// 同时添加到运行日志表，使用实际的提示词
							\HaoZiTeam\AIPost\Service\Features\RunLog::add(
								$main_instruction,
								$new_title,
								'publish',
								'post_id:' . (isset($post_id) ? $post_id : 0),
								'duplicate',
								0,  // 日志级别
								$title  // 添加原始标题
							);
							
							$title = $new_title;
						} else {
							error_log("AI Post Debug: 重复标题AI改写失败，将文章状态设为草稿");
							$action = 'draft'; // 改写失败则存为草稿
						}
					}

					if ($action !== 'rewrite') {
						error_log("AI Post Debug: 重复标题处理方式不是改写，设置文章状态为: {$action}");
						$post_status = $action;
					}
				}
			}

			// --- 新增：空标题检查与处理 ---
			$empty_title_mode = $this->settings['global_tabs']['empty-title-mode'] ?? 'abort'; // 新增配置项，默认abort
			$title_checked = \HaoZiTeam\AIPost\Service\Features\HtmlUtils::check_and_handle_empty_title($title, $content, $empty_title_mode);
			if ($title_checked === false) {
				error_log("AI Post 警告：标题为空，已根据设置中止发布（empty-title-mode=abort）");
				return; // 直接中止
			}
			$title = $title_checked;
			// --- 结束：空标题检查与处理 ---

			// --- 新增：空标题处理逻辑对接 empty-title-action ---
			$empty_title_action = $this->settings['global_tabs']['empty-title-action'] ?? 'draft';
			if (empty(trim($title))) {
				switch ($empty_title_action) {
					case 'trash':
						$post_status = 'trash';
						$title = '未命名文章-' . date('Ymd-His');
						break;
					case 'draft':
						$post_status = 'draft';
						$title = '未命名文章-' . date('Ymd-His');
						break;
					case 'ai-rewrite':
						// 调用AI生成标题
						try {
							$ai_title_prompt = "请根据以下内容生成一个合适的中文标题，要求简洁有吸引力，直接输出标题文本：\n" . mb_substr(strip_tags($content), 0, 200);
							$ai_title_result = $chat_gpt->ask($ai_title_prompt);
							$title = trim($ai_title_result['answer'] ?? '');
							if ($title === '') {
								$title = '未命名文章-' . date('Ymd-His');
							} else {
								$is_rewrite_type = 'empty'; // 新增：空标题AI改写
								
								// 提取实际使用的提示词摘要
								$prompt_summary = "根据内容生成合适的中文标题";
								
								// 同时添加到运行日志表
								\HaoZiTeam\AIPost\Service\Features\RunLog::add(
									$prompt_summary,
									$title,
									'publish',
									'post_id:' . (isset($post_id) ? $post_id : 0),
									'empty',
									0,  // 日志级别
									'未命名文章'  // 添加原始标题
								);
							}
						} catch (\Exception $e) {
							$title = '未命名文章-' . date('Ymd-His');
						}
						break;
					case 'first-heading':
						// 截取正文第一个h1/h2/h3/h4作为标题
						$found_heading = false;
						if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $content, $matches)) {
							$title = trim(strip_tags($matches[1]));
							$found_heading = true;
						} elseif (preg_match('/<h2[^>]*>(.*?)<\/h2>/is', $content, $matches)) {
							$title = trim(strip_tags($matches[1]));
							$found_heading = true;
						} elseif (preg_match('/<h3[^>]*>(.*?)<\/h3>/is', $content, $matches)) {
							$title = trim(strip_tags($matches[1]));
							$found_heading = true;
						} elseif (preg_match('/<h4[^>]*>(.*?)<\/h4>/is', $content, $matches)) {
							$title = trim(strip_tags($matches[1]));
							$found_heading = true;
						}
						if (!$found_heading || $title === '') {
							$title = '未命名文章-' . date('Ymd-His');
						}
						break;
					default:
						$post_status = 'draft';
						$title = '未命名文章-' . date('Ymd-His');
						break;
				}
			}
			// --- 结束：空标题处理逻辑对接 ---

			// 按全局设置：在入库前根据发布编码方式对正文进行可选的 Unicode 转义（标题保持 UTF-8）
			$this->maybe_apply_publish_encoding($title, $content);

			// 然后创建文章
			$post_id = wp_insert_post([
				'post_title' => $title,
				'post_status' => 'draft',
				'post_content' => trim($content),
				'post_author' => $this->settings['tasks'][$key]['post-author'] ?? 1,
			], true);

			if (is_wp_error($post_id) || $post_id === 0) {
				error_log('文章创建失败: ' . print_r($post_id, true));
				return;
			}

			// 确保后续操作都有有效的$post_id
			$post = get_post($post_id);
			if (!$post) {
				error_log('无法获取文章对象，ID: ' . $post_id);
				return;
			}

			// 最后处理特色图片（标准化：使用 ImageProcessor 统一入库与父子关联）
			if ($this->settings['global_tabs']['auto-featured-image-enabled'] ?? false) {
				$position = $this->get_featured_image_position($content);
				if (!empty($position)) {
					try {
						// 若该 URL 已被方式一/豆包入库并记录，直接使用现有附件，避免重复侧载
						$fast_set_done = false;
						if (isset($normalize_url_for_dedup) && is_callable($normalize_url_for_dedup)) {
							$norm = $normalize_url_for_dedup((string)$position);
							if (isset($seen['url_to_attachment'][$norm])) {
								$aid = (int)$seen['url_to_attachment'][$norm];
								if ($aid > 0) {
									if (!has_post_thumbnail($post_id)) {
										if (set_post_thumbnail($post_id, $aid)) {
											$fast_set_done = true;
											error_log('[AI-Post][Dedup] featured uses existing attachment without sideload: id=' . $aid . ' post_id=' . (int)$post_id);
										}
									} else {
										// 已存在特色图，保持现状跳过
									}
								}
							}
							if (!$fast_set_done) {
								// 二次尝试：根据内容 URL 反查本地附件，避免重复下载
								$aid2 = 0;
								$pos_url = (string)$position;
								if (function_exists('attachment_url_to_postid')) {
									$aid2 = (int)attachment_url_to_postid($pos_url);
									// WebP 兼容回退：将 .webp 映射为 .png/.jpg 再次尝试
									if ($aid2 === 0 && preg_match('/\.webp($|\?)/i', $pos_url)) {
										$fallback_png = preg_replace('/\.webp(\b|$)/i', '.png$1', $pos_url);
										$fallback_jpg = preg_replace('/\.webp(\b|$)/i', '.jpg$1', $pos_url);
										$aid2 = (int)attachment_url_to_postid($fallback_png);
										if ($aid2 === 0) { $aid2 = (int)attachment_url_to_postid($fallback_jpg); }
										if ($aid2 > 0) {
											error_log('[AI-Post][Featured] mapped webp to existing attachment via fallback: id=' . $aid2);
										}
									}
								}
								if ($aid2 > 0) {
									if (!has_post_thumbnail($post_id)) {
										if (set_post_thumbnail($post_id, $aid2)) {
											$fast_set_done = true;
											error_log('[AI-Post][Featured] set by attachment_url_to_postid: id=' . $aid2 . ' post_id=' . (int)$post_id);
										}
									} else {
										$fast_set_done = true;
									}
								}
							}
							if (!$fast_set_done) {
								// 兜底：仍未命中本地附件，才尝试下载
								$attach_res = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::attach_remote_image_to_post((int)$post_id, (string)$position, true);
								if (!$attach_res['ok']) {
									error_log('[AI Post] 设置特色图失败: ' . $position . ' | ' . ($attach_res['error'] ?? 'unknown'));
								} elseif (empty($attach_res['featured_set'])) {
									error_log('[AI Post] 特色图未能设置成功（已入库 attachment #' . (int)($attach_res['attachment_id'] ?? 0) . '），文章ID: ' . (int)$post_id);
								}
							}
						}
					} catch (\Throwable $e) {
						error_log('[AI Post] 特色图流程异常: ' . $e->getMessage());
					}
				}
			}

			// 设置分类和标签（原有代码）
			wp_set_post_terms($post_id, $this->settings['tasks'][$key]['post-category'], 'category');
			// 判断是否开启自动标签
			$tags_all = array();
			if ((bool)$this->settings['tasks'][$key]['post-tags'] ?? false) {
				// 获取标签语言选项
				$tag_language = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh'; // 默认选择中文

				// 语言映射
				$language_map = LanguageUtils::get_language_map();
				
				// 如果是自定义语言，则使用用户提供的语言描述
				if ($tag_language === 'custom') {
					$custom_language_code = $this->settings['tasks'][$key]['custom-language-code'] ?? '';
					$custom_language_description = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
					$selected_language = !empty($custom_language_description) ? $custom_language_description : '自定义语言';
				} else {
					$selected_language = $language_map[$tag_language] ?? '中文简体'; // 默认中文
				}

				// 在适当的位置初始化 $is_chinese
				$is_chinese = in_array($tag_language, ['tags-zh', 'tags-zh-fan']);

				// 根据选项生成相应语言的标签
				try {
					// 关键：清空会话上下文，避免前序步骤的语言或系统提示影响标签语言
					if (method_exists($chat_gpt, 'clearMessages')) {
						$chat_gpt->clearMessages();
						if (function_exists('error_log')) {
							error_log("AI Post Debug: Task {$key} - 标签生成前已清空对话上下文");
						}
					}
					// 明确语言约束与输出格式（强化中文/所选语言约束）
					$question = '请严格使用' . $selected_language . '输出3-5个高相关的SEO标签。要求：1) 仅输出标签本身；2) 每个标签简短精确；3) 使用英文逗号分隔；4) 不要任何解释或前后缀；5) 禁止输出与所选语言不一致的内容。文章内容：' . mb_substr(wp_strip_all_tags($title . ' ' . $content), 0, 500) . '...';

					$tags = $chat_gpt->ask($question);

					// 添加 Token 使用量日志 (标签生成)
					if (isset($tags['usage'])) {
						error_log("模型[" . $chat_gpt->getModel() . "] Token Usage (Tags - Task {$key}): " . json_encode($tags['usage']));
					}

					$tags_ai = explode(',', str_replace(['，', '、', '；', ';'], ',', $tags['answer']));
					$tags_ai = array_map('trim', $tags_ai);
					$tags_ai = array_filter($tags_ai);
					
					if (is_array($tags_ai) && !empty($tags_ai)) {
						foreach ($tags_ai as $item) {
							array_push($tags_all, $item);
						}
					}
				} catch (Exception $e) {
					// 标签生成失败时，记录错误但继续处理
					error_log('AI Post 标签生成失败: ' . $e->getMessage());
				}
			}
			// 判断是否开启自定义标签
			if ((bool)$this->settings['tasks'][$key]['post-tags-diy'] ?? false) {
				// 获取自定义标签内容
				$post_tags_diy_text = $this->settings['tasks'][$key]['post-tags-diy-text'] ?? null; // 默认选择中文

				// 根据选项生成相应语言的标签
				try {
					if ($post_tags_diy_text) {
						$post_tags_diy_text = str_replace('，', ',', $post_tags_diy_text);
						$tags_diy = explode(',', $post_tags_diy_text);
						if (is_array($tags_diy)) {
							foreach ($tags_diy as $item) {
								array_push($tags_all, $item);
							}
						}
					}

				} catch (Exception $e) {
					throw $e;
				}
			}
			//设置tag
			$tags_generated_successfully = false; // 新增：标签生成成功标志
			if ($tags_all) {
				wp_set_post_terms($post_id, $tags_all, 'post_tag');
				$tags_generated_successfully = true; // 标记成功
				// 自动写入Yoast SEO焦点关键词
				$focus_keyword = $tags_all[0];
				update_post_meta($post_id, '_yoast_wpseo_focuskw', $focus_keyword);
			} else {
				// 没有标签时用标题
				update_post_meta($post_id, '_yoast_wpseo_focuskw', $title);
			}

			// --- 修改：根据选择的方法存储Meta Description --- 
			$meta_desc_method_final = $this->settings['global_tabs']['meta_description_generation_method'] ?? 'extract';
			error_log("AI Post Debug: Task {$key} - Final Meta Description Method Check: {$meta_desc_method_final}");

			if ($meta_desc_method_final === 'ai') {
				if (!empty($ai_generated_meta_description)) {
					$desc_to_save = (string)$ai_generated_meta_description;
					// 条件：当选为 html-entity 时才实体化；否则保持 UTF-8
					$mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
					if ($mode === 'html-entity') {
						$desc_to_save = $this->encode_html_entities_for_publish($desc_to_save);
					}
					update_post_meta($post_id, '_ai_post_description', $desc_to_save);
					// 适配Yoast SEO
					update_post_meta($post_id, '_yoast_wpseo_metadesc', $desc_to_save);
					error_log("AI Post Debug: Task {$key} - 已将 AI 生成的 Meta Description 存入 post meta (Method: AI)。");
				} else {
					// AI生成失败，可以选择不存或者回退到截取 (当前逻辑是不存，避免覆盖空值)
					error_log("AI Post Debug: Task {$key} - AI Meta Description 生成失败或为空，未存储描述 (Method: AI)。");
				}
			} elseif ($meta_desc_method_final === 'extract') {
				// 使用截取逻辑
				$raw_content = $this->get_raw_content($content, $key);
				$description = mb_substr(wp_strip_all_tags($raw_content ?: $content), 0, 160); // 使用更接近Google的160字符限制
				$desc_to_save = (string)$description;
				// 按设置决定是否实体化：html-entity => 实体；utf8 => 保持中文
				$mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
				if ($mode === 'html-entity') {
					$desc_to_save = $this->encode_html_entities_for_publish($desc_to_save);
				}
				update_post_meta($post_id, '_ai_post_description', $desc_to_save);
				// 适配Yoast SEO
				update_post_meta($post_id, '_yoast_wpseo_metadesc', $desc_to_save);
				error_log("AI Post Debug: Task {$key} - 使用截取内容生成的描述存入 post meta (Method: Extract): " . $desc_to_save);
			} else { // Method is 'none' or unexpected
				// 不存储 Meta Description
				delete_post_meta($post_id, '_ai_post_description'); // 确保删除可能存在的旧值
				// 适配Yoast SEO
				delete_post_meta($post_id, '_yoast_wpseo_metadesc');
				error_log("AI Post Debug: Task {$key} - Meta Description 设置为不生成 (Method: None)。");
			}
			// --- 结束修改            $keywords_limit = $this->settings['global_tabs']['meta-keywords-limit'] ?? 200;
            // Define $keywords_limit before use
            $keywords_limit = $this->settings['global_tabs']['meta-keywords-limit'] ?? 200;
            $keywords = MetaTags::format_tags_as_keywords_string($tags_all, $keywords_limit);
            $keywords_to_save = (string)$keywords;
            // 条件：当选为 html-entity 时才实体化；否则保持 UTF-8
            $mode = $this->settings['global_tabs']['publish-content-encoding'] ?? 'utf8';
            if ($mode === 'html-entity') {
                $keywords_to_save = $this->encode_html_entities_for_publish($keywords_to_save);
            }
            update_post_meta($post_id, '_ai_post_keywords', $keywords_to_save);

			// 新增：强制触发内链处理
			$this->add_internal_links($post_id, $post, true);

			// --- 修复：在更新状态前检查标签 --- 
			$final_post_status = $post_status ?? 'publish'; // 获取原始期望状态
			// 仅当“期望生成标签”（开启自动标签或自定义标签）但生成失败时，才强制为草稿
			$expect_tags = ((bool)($this->settings['tasks'][$key]['post-tags'] ?? false)) || ((bool)($this->settings['tasks'][$key]['post-tags-diy'] ?? false));
			if ($final_post_status === 'publish' && $expect_tags && !$tags_generated_successfully) {
				$final_post_status = 'draft';
				error_log("AI Post Debug: Task {$key} - 期望生成标签但失败，已强制为 draft (expect_tags=true)");
			} else {
				// 未期望生成标签或非发布状态，不触发强制草稿
				if (function_exists('error_log')) {
					error_log("AI Post Debug: Task {$key} - 标签检查通过: expect_tags=" . ($expect_tags ? 'true' : 'false') . ", tags_generated=" . ($tags_generated_successfully ? 'true' : 'false') . ", final_status=" . $final_post_status);
				}
			}
			// --- 结束修复 ---

			// --- 新增：在闭合检查前进行最终清理 ---
			$content_before_cleanup = $content; // 用于调试
			error_log("AI Post Debug: Task {$key} - Content length before final cleanup: " . strlen($content_before_cleanup));
			// 移除无效的 <p/> 标签
			$content = str_replace('<p/>', '', $content);
			// 修复错误的插入模式
			$content = str_replace('<div><!-- AI-TASK-START --></p>', '<div><!-- AI-TASK-START -->', $content);
			// 再次移除空段落标签 (包括只包含空格的)
			$content = preg_replace('/<p>\s*<\/p>/s', '', $content);
			error_log("AI Post Debug: Task {$key} - Content length after final cleanup: " . strlen($content));
			// --- 结束新增清理 ---

			// 在最后更新文章之前尝试闭合 HTML 标签
			$original_content_length = strlen($content); // 记录原始长度用于调试
			$content = HtmlUtils::close_html_tags($content);
			$processed_content_length = strlen($content); // 记录处理后长度
			error_log("AI Post Debug: Task {$key} - HTML tag closing attempted. Original length: {$original_content_length}, Processed length: {$processed_content_length}");

			// 最后更新文章状态
			wp_update_post([
				'ID' => $post_id,
				'post_status' => $final_post_status // 使用最终确定的状态
			]);

			// 新增：写入运行日志，记录文章ID和任务key
			$extra = 'post_id:' . $post_id . ', task_key:' . $key;
			// 获取的指令（第一行）
			$main_instruction = isset($main_instruction) ? $main_instruction : '';
			$is_rewrite = $options['is_rewrite'] ?? null; // 获取is_rewrite参数
			$original_title = isset($options['original_title']) ? $options['original_title'] : null; // 获取原始标题
			\HaoZiTeam\AIPost\Service\Features\RunLog::add($main_instruction, $title, $final_post_status, $extra, $is_rewrite, 0, $original_title);

            // 新增：数据库统计埋点（不依赖 debug.log）
            try {
                // 标记为 AI-Post 生成
                \update_post_meta($post_id, '_ai_post_generated', '1');
                // 记录最终状态（publish/draft）
                \update_post_meta($post_id, '_ai_post_final_status', $final_post_status);
                // 记录任务 key 与提供商（若有）
                \update_post_meta($post_id, '_ai_post_task', (string)$key);
                $provider = $this->settings['tasks'][$key]['imagegen-provider'] ?? '';
                if ($provider !== '') {
                    \update_post_meta($post_id, '_ai_post_provider', (string)$provider);
                }
            } catch (\Throwable $e) {
                \error_log('[AI-Post][Stats] 写入文章统计埋点失败: ' . $e->getMessage());
            }

            // --- 结束修改 ---

            // 新增：强制触发内链处理
            $this->add_internal_links($post_id, $post, true);

            // --- START: Generate Schema.org JSON-LD if enabled ---
            $schema_enabled = $this->settings['global_tabs']['enable_schema_org'] ?? false;
            if ($schema_enabled) {
                error_log("AI Post Debug: Task {$key} - Generating Schema.org JSON-LD for Post ID: {$post_id}");
                try {
                    $schema_data = [
                        '@context' => 'https://schema.org'
                    ];

                    // --- Article/BlogPosting Schema ---
                    $article_schema = [
                        '@type' => 'Article', // Or BlogPosting
                        'mainEntityOfPage' => [
                            '@type' => 'WebPage',
                            '@id' => get_permalink($post_id)
                        ],
                        'headline' => $post->post_title,
                        'datePublished' => get_post_time('c', true, $post_id), // ISO 8601 format
                        'dateModified' => get_post_modified_time('c', true, $post_id), // ISO 8601 format
                        // Author, Publisher, Image, Description, inLanguage added conditionally below
                    ];

                    // Read Schema control settings
                    $add_author = $this->settings['global_tabs']['schema_enable_author'] ?? true;
                    $add_publisher = $this->settings['global_tabs']['schema_enable_publisher'] ?? true;
                    $add_language = $this->settings['global_tabs']['schema_enable_inlanguage'] ?? true;

                    // Conditionally add Author
                    if ($add_author) {
                         $article_schema['author'] = [
                            '@type' => 'Person',
                            'name' => get_the_author_meta('display_name', $post->post_author),
                            'url' => get_author_posts_url($post->post_author) // Add author URL
                        ];
                    }
                   
                    // Conditionally add Publisher
                    if ($add_publisher) {
                         $article_schema['publisher'] = [
                            '@type' => 'Organization',
                            'name' => get_bloginfo('name')
                            // Optionally add logo
                            // 'logo' => [ '@type' => 'ImageObject', 'url' => get_site_icon_url() ]
                        ];
                    }

                    // Conditionally add Language
                    if ($add_language) {
                        $lang_code = get_bloginfo('language'); // Get WordPress language setting (e.g., zh-CN)
                        // --- START: Apply zh-Hans to zh-CN mapping for Schema ---
                        if ($lang_code === 'zh-Hans') {
                             $lang_code = 'zh-CN'; // Map zh-Hans to zh-CN
                        }
                        // --- END: Apply zh-Hans to zh-CN mapping for Schema ---
                        if ($lang_code) {
                             $article_schema['inLanguage'] = $lang_code;
                        }
                    }

                    // Add image if featured image exists
                    if (has_post_thumbnail($post_id)) {
                        $image_url = get_the_post_thumbnail_url($post_id, 'full');
                        if ($image_url) {
                             $article_schema['image'] = [
                                '@type' => 'ImageObject',
                                'url' => $image_url
                            ];
                        }
                    }
                     // Get the final description stored in meta
                    $final_meta_description = get_post_meta($post_id, '_ai_post_description', true);
                    if (!empty($final_meta_description)) {
                        $article_schema['description'] = $final_meta_description;
                    }

                    // --- FAQPage Schema (if FAQ content exists) ---
                    $faq_schema = null;
                    // Check if $faq_content_html exists and seems like FAQ
                    // We need a reliable way to know if FAQ was generated. Using the presence of H3 might be a heuristic.
                    if (!empty($faq_content_html) && strpos($faq_content_html, '<h3') !== false) {
                        $faq_qa_pairs = [];
                        // Parse FAQ HTML again to extract Q&A pairs for Schema
                         libxml_use_internal_errors(true);
                         $faq_dom = new DOMDocument();
                         if (mb_detect_encoding($faq_content_html, 'UTF-8', true) === false) {
                            $faq_content_html_utf8 = mb_convert_encoding($faq_content_html, 'UTF-8');
                         } else {
                            $faq_content_html_utf8 = $faq_content_html;
                         }
                         // Use a wrapper div for parsing fragments
                         @$faq_dom->loadHTML('<div>' . $faq_content_html_utf8 . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
                         libxml_clear_errors();
                         $faq_xpath = new DOMXPath($faq_dom);
                         $questions = $faq_xpath->query('//h3');
                         
                         foreach ($questions as $question_node) {
                             $question_text = trim($question_node->textContent);
                             $answer_text = '';
                             $next_node = $question_node->nextSibling;
                             while ($next_node && ($next_node->nodeType !== XML_ELEMENT_NODE || $next_node->nodeName !== 'h3')) {
                                 if ($next_node->nodeType === XML_ELEMENT_NODE && $next_node->nodeName === 'p') {
                                     // Get inner HTML of the first paragraph as answer
                                     $answer_text = trim($faq_dom->saveHTML($next_node));
                                     $answer_text = strip_tags($answer_text); // Get text content
                                     break; // Assume first <p> after <h3> is the answer
                                 } elseif ($next_node->nodeType === XML_TEXT_NODE && trim($next_node->nodeValue) !== '') {
                                     // Fallback: Collect text nodes if no <p> found immediately
                                     $answer_text .= trim($next_node->nodeValue) . " ";
                                 }
                                 $next_node = $next_node->nextSibling;
                             }
                             $answer_text = trim($answer_text);

                             if (!empty($question_text) && !empty($answer_text)) {
                                 $faq_qa_pairs[] = [
                                     '@type' => 'Question',
                                     'name' => $question_text,
                                     'acceptedAnswer' => [
                                         '@type' => 'Answer',
                                         'text' => $answer_text
                                     ]
                                 ];
                             }
                         }

                         if (!empty($faq_qa_pairs)) {
                             $faq_schema = [
                                 '@type' => 'FAQPage',
                                 'mainEntity' => $faq_qa_pairs
                             ];
                         }
                    }

                    // Combine schemas if both exist
                    if ($faq_schema) {
                        $schema_data['@graph'] = [$article_schema, $faq_schema];
                    } else {
                        $schema_data = array_merge($schema_data, $article_schema);
                    }

                    // Encode and save to post meta
                    $schema_json = wp_json_encode($schema_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
                    if ($schema_json) {
                        update_post_meta($post_id, '_ai_post_schema_json_ld', $schema_json);
                        error_log("AI Post Debug: Task {$key} - Successfully generated and saved Schema JSON-LD for Post ID: {$post_id}");
                    } else {
                        error_log("AI Post Debug: Task {$key} - Failed to encode Schema to JSON for Post ID: {$post_id}");
                    }

                } catch (Exception $e) {
                    error_log("AI Post Debug: Task {$key} - Error generating Schema JSON-LD for Post ID: {$post_id} - " . $e->getMessage());
                }
            } else {
                 // Optionally delete existing schema if setting is disabled
                 delete_post_meta($post_id, '_ai_post_schema_json_ld');
                 error_log("AI Post Debug: Task {$key} - Schema.org generation is disabled. Removed schema meta if existed for Post ID: {$post_id}");
            }
            // --- END: Generate Schema.org JSON-LD ---
        }

    // 新增方法（已在类的前面定义过一次，这里为重复定义，删除以避免语法冲突）

	private function is_duplicate_title($title, $days = 30) {
		global $wpdb;

		$sql = "SELECT COUNT(*) FROM {$wpdb->posts} 
                WHERE post_title = %s 
                AND post_type = 'post' 
                AND post_status IN ('publish', 'future', 'draft', 'pending')";

		$params = [$title];

		if ($days > 0) {
			$sql .= " AND post_date >= DATE_SUB(NOW(), INTERVAL %d DAY)";
			$params[] = $days;
		}

		$count = $wpdb->get_var($wpdb->prepare($sql, $params));

		return $count > 0;
	}

	private function rewrite_duplicate_title($original_title, $chat_gpt, $retry = 3) {
		error_log("AI Post Debug: 开始重复标题AI改写，原标题: '{$original_title}'，最大尝试次数: {$retry}");
		
		// 从示例标题数组中随机获取一个示例标题
		$custom_examples = $this->settings['global_tabs']['custom-title-examples'] ?? '';
		$example_titles = array_filter(explode("\n", $custom_examples));
		
		if (empty($example_titles)) {
			$example_titles = [
				'老师现场提了一个问题，同学的回答亮了',
				'那些整天熬夜加班的人注意了',
				'这个小技巧，99%的人都不知道',
				'掌握这3点，轻松玩转母婴行业私域运营',
				'我用了2个月，做坏了6次热点营销',
				'上海和深圳对比，未来你更看好谁?'
			];
		}
		
		$random_index = array_rand($example_titles);
		$random_title = trim($example_titles[$random_index]);
		
		for ($i = 0; $i < $retry; $i++) {
			try {
				error_log("AI Post Debug: 重复标题AI改写尝试 #" . ($i + 1));
				$prompt = "请改写以下标题，保持原意但使其更独特：\n\"$original_title\"\n"
				          ."要求：\n"
				          ."1. 必须保留核心关键词和主题\n"
				          ."2. 通过替换修饰词、调整语序或增加吸引力元素使标题更独特\n"
				          ."3. 避免使用《》【】()等特殊符号\n"
				          ."4. 请参考以下示例标题的风格和结构，包括是否使用疑问句、数字等元素：\n   \"{$random_title}\"\n"
				          ."5. 可以考虑使用数字、问句或对比结构增加吸引力\n"
				          ."6. 标题风格应保持与原标题一致\n"
				          ."7. 直接输出新标题，不要添加额外说明";

				$response = $chat_gpt->ask($prompt);
				$new_title = trim($response['answer']);
				error_log("AI Post Debug: AI返回的新标题: '{$new_title}'");

				// 添加 Token 使用量日志 (标题重写)
				if (isset($response['usage'])) {
					error_log("AI Post Debug: 模型[" . $chat_gpt->getModel() . "] Token使用量: " . json_encode($response['usage']));
				}

				// 清理标题中的特殊字符
				$new_title = preg_replace('/[《》【】*#"""""「」『』〔〕（）()<>『』〈〉]/u', '', $new_title);
				if ($new_title !== $response['answer']) {
					error_log("AI Post Debug: 清理特殊字符后的标题: '{$new_title}'");
				}

				if (!$this->is_duplicate_title($new_title)) {
					error_log("AI Post Debug: 改写成功，新标题不重复: '{$new_title}'");
					
					// 记录改写日志
					\HaoZiTeam\AIPost\Service\Features\RunLog::add(
						$prompt,
						$new_title,
						'success',
						'',
						'duplicate',
						0,
						$original_title  // 添加原始标题
					);
					
					return $new_title;
				} else {
					error_log("AI Post Debug: 改写后的标题仍然重复: '{$new_title}'，尝试下一次改写");
				}
			} catch (Exception $e) {
				error_log("AI Post Debug: 重复标题AI改写异常: " . $e->getMessage());
				throw $e;
			}
		}
		
		error_log("AI Post Debug: 重复标题AI改写失败，已达到最大尝试次数: {$retry}");
		return false;
	}

    /**
     * 仅插入“方式二（标题图）”，不触发 Doubao 分散插入。
     * - 使用 generate_thumbnail($title, true) 强制标题模式生成图片
     * - 使用与 the_content() 相同的插入与表格保护逻辑
     */
    private function insert_title_image_only(string $content, string $alt_text, array $attr = []): string
    {
        $key = $this->currentKey;
        // 生成或获取标题图（强制标题模式，不走 AI 文生图）
        $attachment = $this->generate_thumbnail($alt_text, true);
        if (!$attachment || !is_array($attachment) || empty($attachment['guid'])) {
            \error_log('[AI-Post][InsertImage][Method-2] generate_thumbnail returned empty, skip title image insert.');
            return $content;
        }

        // 构建 <img> 标签
        $class = 'img-featured img-responsive';
        if (!empty($attr['class'])) {
            $class = $attr['class'];
        }
        $auto_alt_enabled = $this->settings['global_tabs']['auto-alt-enabled'] ?? false;
        $img_html = '<img class="' . $class . ' wp-post-image" src="' . esc_url($attachment['guid']) . '"';
        if ($auto_alt_enabled) {
            $img_html .= ' alt="' . esc_attr($alt_text) . '"';
        }
        if (strpos($class, 'retina') !== false) {
            $img_html .= ' data-src="' . esc_url($attachment['guid']) . '"';
        }
        if (!empty($attr['loading'])) {
            $img_html .= ' loading="lazy"';
        }
        $image_html = $img_html . ' />';
        $image_html = str_replace('{img}', $image_html, '<figure class="wp-block-image size-full aligncenter">{img}</figure>');

        // 插入位置设置（1=开头，3=结尾，2=中间，其它=随机/自动）
        $position_setting = (int)($this->settings['tasks'][$key]['enable-image-url-position'] ?? 1);
        \error_log('[AI-Post][InsertImage][Method-2] position_setting=' . $position_setting);

        // 表格保护：替换为占位符，避免破坏结构
        $table_placeholders = [];
        $placeholder_index = 0;
        $content_with_placeholders = preg_replace_callback(
            '/<table\b[^>]*>.*?<\/table>/is',
            function($matches) use (&$table_placeholders, &$placeholder_index) {
                $placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
                $table_placeholders[$placeholder] = $matches[0];
                return $placeholder;
            },
            $content
        );
        if ($content_with_placeholders) {
            $content_with_placeholders = preg_replace_callback(
                '/<div[^>]*class=["\'][^"\']*(?:table|wp-block-table)[^"\']*["\'][^>]*>.*?<\/div>/is',
                function($matches) use (&$table_placeholders, &$placeholder_index) {
                    $placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
                    $table_placeholders[$placeholder] = $matches[0];
                    return $placeholder;
                },
                $content_with_placeholders
            );
            $content_with_placeholders = preg_replace_callback(
                '/(\|[^\n]+\|\n)(\|[\s\-:]+\|\n)(\|[^\n]+\|\n)+/s',
                function($matches) use (&$table_placeholders, &$placeholder_index) {
                    $placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
                    $table_placeholders[$placeholder] = $matches[0];
                    return $placeholder;
                },
                $content_with_placeholders
            );
            $content_with_placeholders = preg_replace_callback(
                '/<[^>]*data-table[^>]*>.*?<\/[^>]*>/is',
                function($matches) use (&$table_placeholders, &$placeholder_index) {
                    $placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
                    $table_placeholders[$placeholder] = $matches[0];
                    return $placeholder;
                },
                $content_with_placeholders
            );
        }
        $content = !empty($table_placeholders) ? $content_with_placeholders : $content;

        switch ($position_setting) {
            case 1:
                $content = $image_html . $content;
                \error_log('[AI-Post][InsertImage][Method-2] inserted at head (position=1).');
                break;
            case 3:
                $content = $content . $image_html;
                \error_log('[AI-Post][InsertImage][Method-2] inserted at tail (position=3).');
                break;
            default:
                $image_start_num = 0;
                $did_insert = false;
                if (preg_match_all('/<(p|br|div)[^>]*>/i', $content, $mat)) {
                    $p_nums = count($mat[0]);
                    if ($position_setting == 2) {
                        $image_start_num = intval(($p_nums - 1) / 2);
                        \error_log('[AI-Post][InsertImage][Method-2] middle index=' . $image_start_num);
                    } else {
                        $image_start_num = mt_rand(0, max(0, $p_nums - 1));
                        \error_log('[AI-Post][InsertImage][Method-2] random index=' . $image_start_num);
                    }
                } else {
                    $content = $image_html . $content;
                    break;
                }
                $content = preg_replace_callback(
                    '/(<\/p>|<\/div>|<\/ul>|<\/ol>|<\/h[1-6]>|<\/section>|<\/article>|<\/blockquote>)/i',
                    function ($matches) use ($image_start_num, $image_html, &$did_insert) {
                        static $i = -1; $i++;
                        if ($i == $image_start_num) { $did_insert = true; return $matches[0] . $image_html; }
                        return $matches[0];
                    },
                    $content
                );
                if (!$did_insert) {
                    $content = $image_html . $content;
                }
                break;
        }

        // 恢复表格
        if (!empty($table_placeholders)) {
            $content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
        }

        return $content;
    }

	// 新增SEO meta生成方法
	public function add_seo_meta_tags()
	{
		global $post;
		
		// Check if we are on a single post page
		if (!is_single() || !isset($post->ID)) {
			return;
		}

		// Read settings for description and keywords
		$desc_method = $this->settings['global_tabs']['meta_description_generation_method'] ?? 'extract';
		$keywords_enabled = $this->settings['global_tabs']['seo_keywords_enabled'] ?? false;

		// Get saved meta data
		$meta_keywords = get_post_meta($post->ID, '_ai_post_keywords', true);
		$meta_description = get_post_meta($post->ID, '_ai_post_description', true);

		echo "\n<!-- AI Post SEO Meta -->\n"; // Corrected newline

		// --- START: 移除 content-language meta 输出 ---
		// $add_language_meta = $this->settings['global_tabs']['schema_enable_inlanguage'] ?? true; // Check the schema language setting
		// if ($add_language_meta) {
		// 	$site_language = get_bloginfo('language');
		// 	$content_language = $site_language; // Default to site language
		// 	if ($site_language === 'zh-Hans') {
		// 		$content_language = 'zh-CN'; // Map zh-Hans to zh-CN
		// 	}
		// 	if ($content_language) {
		// 		echo '<meta http-equiv="content-language" content="' . esc_attr($content_language) . '">' . "\\n";
		// 	}
		// }
		// --- END: 移除 content-language meta 输出 ---

		// Add publisher meta tag (for compatibility) if enabled in Schema settings
		$add_publisher_meta = $this->settings['global_tabs']['schema_enable_publisher'] ?? true; // Check the schema publisher setting
		if ($add_publisher_meta) {
			$publisher_name = get_bloginfo('name');
			if($publisher_name){
			    echo '<meta name="publisher" content="' . esc_attr($publisher_name) . '">' . "\n"; // Corrected newline
			}
		}

		// --- START: Add Author Meta Tag ---
		$author_name = get_the_author_meta('display_name', $post->post_author);
		if ($author_name) {
			echo '<meta name="author" content="' . esc_attr($author_name) . '">' . "\n"; // Corrected newline
		}
		// --- END: Add Author Meta Tag ---

		// Output Keywords tag if enabled and keywords exist
		if ($keywords_enabled) { // 只检查是否启用了关键词功能
			if (empty($meta_keywords)) { // 如果插件没有保存过关键词 (例如首次生成，或用户清除了)
				$post_tags = get_the_tags($post->ID);
				if ($post_tags) {
					$meta_keywords = MetaTags::format_tags_as_keywords_string($post_tags, 5); // New
				}
			}
			if (!empty($meta_keywords)) { // 再次检查，确保有关键词才输出
				echo '<meta name="keywords" content="' . esc_attr($meta_keywords) . '">' . "\n";
			}
		}

		// Output Description tag based on the selected method
		if ($desc_method === 'ai' || $desc_method === 'extract') {
			// Use the saved meta description (which was generated either by AI or extraction)
			if (!empty($meta_description)) {
				// Basic validation for description length and content (optional but recommended)
				$min_length = 25;
				$is_valid_description = mb_strlen($meta_description) >= $min_length &&
										!preg_match('/^[\pP\s]+$/u', $meta_description);

				if ($is_valid_description) {
					echo '<meta name="description" content="' . esc_attr($meta_description) . '">' . "\n"; // Corrected newline
				} else {
					// Saved description is invalid (e.g., too short), fallback to live extraction
					error_log("AI Post SEO: Post ID {$post->ID} has an invalid meta description stored: {$meta_description}. Falling back to live extraction.");
                    if (isset($post->post_content)) {
                        $fallback_desc = mb_substr(wp_strip_all_tags($post->post_content), 0, 160); // Extract ~160 chars
                        if (!empty($fallback_desc)) {
                            echo '<meta name="description" content="' . esc_attr($fallback_desc) . '">' . "\n"; // Output fallback
                        }
                    }
				}
			} else {
				// Fallback if no description was saved (relevant for extract mode if saving failed, or AI mode if generation failed)
				 error_log("AI Post SEO: Post ID {$post->ID} has no meta description stored. Method: {$desc_method}. Falling back to live extraction.");
                 if (isset($post->post_content)) {
                     $fallback_desc = mb_substr(wp_strip_all_tags($post->post_content), 0, 160); // Extract ~160 chars
                     if (!empty($fallback_desc)) {
                         echo '<meta name="description" content="' . esc_attr($fallback_desc) . '">' . "\n"; // Output fallback
                     }
                 }
			}
		} 

		// --- Open Graph & Twitter Card Meta --- 
		if ($this->settings['global_tabs']['enable_og_twitter_meta'] ?? false) {
			echo "\n<!-- AI Post Open Graph & Twitter Card Meta -->\n"; // Corrected newline
			$post_title = get_the_title($post->ID);
			$post_url = get_permalink($post->ID);
			$site_name = get_bloginfo('name');
			$final_description = !empty($meta_description) ? $meta_description : mb_substr(wp_strip_all_tags($post->post_content), 0, 160); // Fallback description
			$final_description_escaped = esc_attr(trim($final_description));

			// Basic Open Graph Tags
			echo '<meta property="og:title" content="' . esc_attr($post_title) . '" />' . "\n"; // Corrected newline
			echo '<meta property="og:url" content="' . esc_url($post_url) . '" />' . "\n"; // Corrected newline
			echo '<meta property="og:type" content="article" />' . "\n"; // Corrected newline
			echo '<meta property="og:site_name" content="' . esc_attr($site_name) . '" />' . "\n"; // Corrected newline
			if (!empty($final_description_escaped)) {
				echo '<meta property="og:description" content="' . $final_description_escaped . '" />' . "\n"; // Corrected newline
			}
  
			// Twitter Card Tags
			echo '<meta name="twitter:card" content="summary_large_image" />' . "\n"; // Corrected newline, Or summary
			echo '<meta name="twitter:title" content="' . esc_attr($post_title) . '" />' . "\n"; // Corrected newline
			if (!empty($final_description_escaped)) {
				echo '<meta name="twitter:description" content="' . $final_description_escaped . '" />' . "\n"; // Corrected newline
			}
			// Optional: Add twitter:site and twitter:creator if you have that info
			// echo '<meta name="twitter:site" content="@YourTwitterHandle" />' . "\\n";
			// echo '<meta name="twitter:creator" content="@AuthorTwitterHandle" />' . "\\n";
  
			// Image for both OG and Twitter
			if (has_post_thumbnail($post->ID)) {
				$image_url = get_the_post_thumbnail_url($post->ID, 'large'); // Use a suitable size
				if ($image_url) {
					echo '<meta property="og:image" content="' . esc_url($image_url) . '" />' . "\n";
					echo '<meta name="twitter:image" content="' . esc_url($image_url) . '" />' . "\n";
					// Optional: Add image dimensions if known
					// $image_data = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'large');
					// if ($image_data) {
					// 	echo '<meta property="og:image:width" content="' . $image_data[1] . '" />' . "\\n";
					// 	echo '<meta property="og:image:height" content="' . $image_data[2] . '" />' . "\\n";
					// }
				}
			} else {
			    // Fallback image if no featured image (optional)
			    // $fallback_image_url = 'YOUR_DEFAULT_IMAGE_URL';
			    // echo '<meta property="og:image" content="' . esc_url($fallback_image_url) . '" />' . "\\n";
			    // echo '<meta name="twitter:image" content="' . esc_url($fallback_image_url) . '" />' . "\\n";
			}
			
			echo "<!-- End AI Post Open Graph & Twitter Card Meta -->\n";
		}

		echo "<!-- End AI Post SEO Meta -->\n";
	}

	private function get_raw_content($content, $task_key)
	{
		// 统一处理所有语言的插入内容
		$patterns = [
			// 匹配带注释标记的内容（所有语言）
			'/<!-- AI-(GLOBAL|TASK)-START -->.*?<!-- AI-\1-END -->/isu',
			
			// 增强版div匹配（支持多语言字符）
			'/<div[^>]*class\s*=\s*["\']custom-insert-(global|task)["\'][^>]*>([\p{L}\p{N}\p{P}\p{S}\s]++|.*?<\/div>){1,5}/isu',
			// 新增：移除 AI Post 目录块（TOC），避免摘要截取到目录文本
			'/<div\s+class="ai-post-toc".*?<\/div>/is'
		];

		// 循环清理直到无变化
		do {
			$prev = $content;
			$content = preg_replace($patterns, '', $content);
		} while ($prev !== $content);

		// 统一内容清洗流程
		$processing_steps = [
			'remove_scripts' => fn($c) => preg_replace('/<script\b[^>]*>.*?<\/script>/isu', '', $c),
			'remove_styles' => fn($c) => preg_replace('/<style\b[^>]*>.*?<\/style>/isu', '', $c),
			'remove_shortcodes' => fn($c) => strip_shortcodes($c),
			'remove_html_tags' => fn($c) => strip_tags($c),
			'normalize_whitespace' => fn($c) => preg_replace('/[\p{Z}\s]+/u', ' ', $c),
			'trim_content' => fn($c) => trim($c)
		];

		foreach ($processing_steps as $step) {
			$content = $step($content);
		}

		return $content;
	}




	/**
	 * 添加内链处理逻辑
	 */
	public function add_internal_links($post_id, $post, $update)
	{
		// 始终使用数据库中的最新内容，避免使用上游传入的过期 $post 导致覆盖已插入的图片
		$latest_post = get_post($post_id);
		if (!$latest_post || !is_object($latest_post)) {
			error_log('[AI Post 内链调试] 无法获取最新文章对象，ID: ' . $post_id);
			return;
		}
		
		// 检查是否已处理过，防止无限循环 (如果通过 filter 调用)
		if (get_post_meta($post_id, '_ai_post_internal_links_processed', true)) {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 已标记为处理过，跳过。");
			// return; // 暂时注释掉，以便测试，但生产环境应考虑启用
		}

		$original_content = $latest_post->post_content ?? '';
		$content_modified = false; // 标记内容是否被修改
		$content = $original_content; // 工作副本

		error_log("[AI Post 内链调试 Post ID: {$post_id}] 开始处理内链... | latest_content_length=" . strlen($original_content));

		// --- 分离 TOC ---
		$extracted_toc_html = '';
		$toc_pattern = '/<div class="ai-post-toc".*?<\\/div>/is';
		if (preg_match($toc_pattern, $content, $matches)) {
			$extracted_toc_html = $matches[0];
			$content = preg_replace($toc_pattern, '', $content, 1);
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 已分离 TOC。");
		}

		// --- 保护现有链接和图片 ---
		$placeholders = [];
		$placeholder_counter = 0;

		// 保护 <a> 标签
		$content = preg_replace_callback('/<a\\s[^>]*>.*?<\\/a>/is', function($match) use (&$placeholders, &$placeholder_counter) {
			$placeholder = '%%AI_PLACEHOLDER_' . $placeholder_counter++ . '%%';
			$placeholders[$placeholder] = $match[0];
			return $placeholder;
		}, $content);
		error_log("[AI Post 内链调试 Post ID: {$post_id}] 已保护 " . count($placeholders) . " 个原始 a 标签。");

		// 保护 <img> 标签
		$initial_img_count = count($placeholders);
		$content = preg_replace_callback('/<img[^>]*>/is', function($match) use (&$placeholders, &$placeholder_counter) {
			$placeholder = '%%AI_PLACEHOLDER_' . $placeholder_counter++ . '%%';
			$placeholders[$placeholder] = $match[0];
			return $placeholder;
		}, $content);
		error_log("[AI Post 内链调试 Post ID: {$post_id}] 已保护 " . (count($placeholders) - $initial_img_count) . " 个 img 标签。");

		// --- 准备内链替换 ---
		$keyword_replacements_count = []; // 跟踪每个关键词的替换次数
		$replace_limit = intval($this->settings['global_tabs']['link-replace-count'] ?? 2);
		$final_link_data = []; // 存储最终要替换的链接信息 ['placeholder' => ['url' => ..., 'keyword' => ..., 'nofollow' => ...]]
		$keyword_link_counter = 0;

		// --- 1. 处理自定义内链 (优先级最高) ---
		if (!empty($this->settings['global_tabs']['custom-links'])) {
			error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 开始处理自定义内链...");
			$custom_links = $this->process_custom_links($this->settings['global_tabs']['custom-links']);
			// 按长度降序排序
			uksort($custom_links, function($a, $b) { return mb_strlen($b) - mb_strlen($a); });

			foreach ($custom_links as $keyword => $url) {
				if (empty(trim($keyword)) || empty(trim($url))) continue; // 跳过无效规则

				if (!isset($keyword_replacements_count[$keyword])) {
					$keyword_replacements_count[$keyword] = 0;
				}

				if ($keyword_replacements_count[$keyword] >= $replace_limit) {
					error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 已达上限 ({$replace_limit})，跳过。");
					continue;
				}

				$nofollow_attr = ($this->settings['global_tabs']['link-nofollow'] ?? false) ? ' rel="nofollow"' : '';
				$pattern = '/(' . preg_quote($keyword, '/') . ')/iu'; // 移除单词边界 \b，进行更宽松匹配
				error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 尝试匹配关键词 '{$keyword}' 使用正则 (无边界): {$pattern}");

				$content = preg_replace_callback($pattern, function($match) use (&$keyword_replacements_count, $keyword, $url, $nofollow_attr, $replace_limit, &$final_link_data, &$keyword_link_counter, $post_id) {
					// 再次检查限制（因为 preg_replace_callback 会为每个匹配运行）
					if ($keyword_replacements_count[$keyword] < $replace_limit) {
						$keyword_replacements_count[$keyword]++;
						$link_placeholder = '%%KEYWORD_LINK_' . $keyword_link_counter++ . '%%';
						$final_link_data[$link_placeholder] = [
							'url' => $url,
							'keyword' => $match[1], // 使用实际匹配到的文本（保留大小写）
							'nofollow' => $nofollow_attr
						];
						error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 匹配成功 (第 {$keyword_replacements_count[$keyword]} 次)，生成占位符: {$link_placeholder}");
						return $link_placeholder;
					} else {
						// 已达到限制，返回原始匹配文本
						error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 在回调中发现已达上限，保留原文 '{$match[0]}'");
						return $match[0];
					}
				}, $content, -1, $count); // 设置 limit 为 -1 (无限)，在回调中控制

				if ($count > 0) {
					$content_modified = true; // 标记内容已被修改
					error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 正则替换关键词 '{$keyword}' 完成，共找到 {$count} 处可能匹配 (实际替换受限)。当前总替换数: {$keyword_replacements_count[$keyword]}");
				}
			}
			error_log("[AI Post 自定义内链调试 Post ID: {$post_id}] 自定义内链处理完成。");
		} else {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 未配置或未启用自定义内链规则。");
		}

		// --- 2. 处理自动文章/标签内链 ---
		if ($this->settings['global_tabs']['enable-internal-links'] ?? false) {
			error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 开始处理自动内链...");
			$tags = wp_get_post_tags($post_id);

			if (!empty($tags)) {
				error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 找到 " . count($tags) . " 个标签。");
				// 按标签名长度降序排序，优先处理长标签，减少部分包含问题 (可选优化)
				usort($tags, function($a, $b) { return mb_strlen($b->name) - mb_strlen($a->name); });

				foreach ($tags as $tag) {
					$keyword = $tag->name;
					if (empty(trim($keyword))) continue;

					error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 处理标签/关键词: '{$keyword}'");
					if (!isset($keyword_replacements_count[$keyword])) {
						$keyword_replacements_count[$keyword] = 0;
					}

					// 提前检查替换限制
					if ($keyword_replacements_count[$keyword] >= $replace_limit) {
						error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 已达替换上限 ({$replace_limit})，跳过链接查找和替换。");
						continue;
					}

					// --- 查找链接目标 ---
					$link_url = '';
					$related_posts = new \WP_Query([
						'tag_id' => $tag->term_id,
						'posts_per_page' => 1,
						'post__not_in' => [$post_id],
						'orderby' => $this->settings['global_tabs']['related-post-criteria'] ?? 'date',
						'order' => 'DESC'
					]);

					if ($related_posts->have_posts()) {
						$link_url = get_permalink($related_posts->posts[0]->ID);
						error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 找到相关文章链接: {$link_url}");
					} else {
						$enable_fallback = $this->settings['global_tabs']['related-post-fallback'] ?? true;
						$enable_tag_linking = $this->settings['global_tabs']['auto-tag-linking'] ?? false;
						if ($enable_fallback && $enable_tag_linking) {
							$link_url = get_tag_link($tag->term_id);
							if ($link_url) {
							    error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 未找到相关文章，已启用备用和标签内链，回退到标签链接: {$link_url}");
							} else {
							    error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 未找到相关文章，尝试回退标签链接失败 (get_tag_link 返回空)。");
                                $link_url = ''; // 确保 $link_url 为空
							}
						} else {
							error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 未找到相关文章，且未启用备用或标签内链。");
                            // $link_url 已经是 ''
						}
					}
					wp_reset_postdata();
					// --- 结束链接查找 ---

					// 只有找到有效链接目标时才进行替换尝试
					if (!empty($link_url)) {
						$nofollow_attr = ($this->settings['global_tabs']['link-nofollow'] ?? false) ? ' rel="nofollow"' : '';
						$pattern = '/(' . preg_quote($keyword, '/') . ')/iu'; // 移除单词边界 \b，进行更宽松匹配
						error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 尝试匹配关键词 '{$keyword}' 使用正则 (无边界): {$pattern}");

						$initial_link_data_count = count($final_link_data); // 记录替换前数量
						$replacements_attempted_in_callback = 0; // 记录回调函数被调用的次数 (即正则匹配次数)

						$content = preg_replace_callback($pattern, function($match) use (&$keyword_replacements_count, $keyword, $link_url, $nofollow_attr, $replace_limit, &$final_link_data, &$keyword_link_counter, $post_id, &$replacements_attempted_in_callback) {
							$replacements_attempted_in_callback++; // 每次回调都增加尝试次数
							// 检查替换限制
							if ($keyword_replacements_count[$keyword] < $replace_limit) {
								$keyword_replacements_count[$keyword]++;
								$link_placeholder = '%%KEYWORD_LINK_' . $keyword_link_counter++ . '%%';
								$final_link_data[$link_placeholder] = [
									'url' => $link_url,
									'keyword' => $match[1], // 使用实际匹配文本
									'nofollow' => $nofollow_attr
								];
								// 这个日志在循环外记录更清晰
								// error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 匹配成功 (第 {$keyword_replacements_count[$keyword]} 次)，生成占位符: {$link_placeholder}");
								return $link_placeholder;
							} else {
								// 已达到限制，返回原始匹配文本
								// error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}' 在回调中发现已达上限，保留原文 '{$match[0]}'");
								return $match[0];
							}
						}, $content, -1); // $count 参数在这里不再直接使用，我们在回调内外追踪

						$actual_replacements_made = count($final_link_data) - $initial_link_data_count;

						if ($actual_replacements_made > 0) {
							$content_modified = true; // 标记内容已被修改
							error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 成功生成 {$actual_replacements_made} 个链接占位符。当前总替换数: {$keyword_replacements_count[$keyword]}/{$replace_limit}");
						} else if ($replacements_attempted_in_callback > 0) {
							// 正则找到了匹配项，但没有实际生成占位符 (因为达到 limit)
							error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 在内容中找到 {$replacements_attempted_in_callback} 处匹配，但因已达替换上限 ({$replace_limit}) 未生成新链接。");
						} else {
							// 正则没有找到任何匹配项
							error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 找到链接目标 ({$link_url})，但在内容中未找到匹配项。");
						}
					} else {
						// 这个 else 对应 if (!empty($link_url))，意味着最初就没找到链接
						// 日志已在上方查找链接的 else 分支中记录，这里无需重复
                        // error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 关键词 '{$keyword}': 未找到有效链接目标 (相关文章或备用标签链接)，跳过替换。");
					}
				} // End foreach tag
				error_log("[AI Post 自动内链调试 Post ID: {$post_id}] 所有标签处理完成。");
			} else {
				error_log("[AI Post 内链调试 Post ID: {$post_id}] 未找到文章标签，无法进行自动链接。");
			}
		} else {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 文章互链功能未启用。");
		}

		// --- 恢复原始内容占位符 ---
		if (!empty($placeholders)) {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 开始恢复 " . count($placeholders) . " 个原始 a/img 标签...");
			// 必须使用 krsort 确保内部的占位符先被替换，防止占位符本身包含其他占位符的数字部分导致错误
			krsort($placeholders); // 按占位符数字倒序排序
			$content = str_replace(array_keys($placeholders), array_values($placeholders), $content);
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 原始 a/img 标签恢复完成。");
		}

		// --- 最后：替换关键词占位符为最终链接 ---
		if (!empty($final_link_data)) {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 开始将 " . count($final_link_data) . " 个关键词占位符转换为最终链接...");
			foreach ($final_link_data as $placeholder => $data) {
				$final_link_html = sprintf(
					'<a href="%s"%s title="%s">%s</a>',
					esc_url($data['url']),
					$data['nofollow'], // 已经包含了 rel="nofollow" 或为空字符串
					esc_attr($data['keyword']), // 使用实际匹配的关键词作为 title
					esc_html($data['keyword'])   // 使用实际匹配的关键词作为链接文本
				);
				// 使用 str_replace 替换，更安全
				$content = str_replace($placeholder, $final_link_html, $content);
			}
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 关键词占位符转换完成。");
			$content_modified = true; // 只要有最终链接数据，就认为内容已修改
		}

		// --- 恢复 TOC ---
		if (!empty($extracted_toc_html)) {
			$content = $extracted_toc_html . $content;
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 已恢复 TOC。");
		}

		// --- 清理可能未处理的占位符（安全措施）---
		$content = preg_replace('/%%(AI_PLACEHOLDER_|KEYWORD_LINK_)[0-9]+%%/', '', $content);
		error_log("[AI Post 内链调试 Post ID: {$post_id}] 清理未处理占位符完成。");

		// --- 更新文章内容 (仅当内容实际修改时) ---
		if ($content_modified || $content !== $original_content) {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 内容已修改，准备更新数据库...");

			// 标记文章已处理 (应在更新前或更新后立即执行)
			update_post_meta($post_id, '_ai_post_internal_links_processed', true);

			// 移除当前钩子，防止更新时再次触发自己导致无限循环
			// 注意：需要知道这个函数是通过哪个钩子调用的，这里假设是 'content_save_pre'
			// 如果是通过 save_post 调用，则需要移除 save_post 的 action
			// 暂时假设是 content_save_pre
			// remove_filter('content_save_pre', [$this, 'add_internal_links_filter'], 999); // 需要知道 filter 的名称和优先级

			// 更新文章
			$update_result = wp_update_post([
				'ID' => $post_id,
				'post_content' => $content
			], true); // true 返回 WP_Error on failure

			if (is_wp_error($update_result)) {
				error_log("[AI Post 内链调试 Post ID: {$post_id}] 更新文章失败: " . $update_result->get_error_message());
			} else {
				error_log("[AI Post 内链调试 Post ID: {$post_id}] 数据库更新完成。");
			}

			// 重新添加钩子（如果需要且知道名称）
			// add_filter('content_save_pre', [$this, 'add_internal_links_filter'], 999, 1);

		} else {
			error_log("[AI Post 内链调试 Post ID: {$post_id}] 内链处理后内容未改变，无需更新数据库。");
		}

		// 在函数结束时或处理完后清除标记，如果希望每次保存都重新处理
		// delete_post_meta($post_id, '_ai_post_internal_links_processed');

	} // End function add_internal_links

	private function chinese_segment($text) {
		// 实现中文分词逻辑或调用分词库
	}

	// 新增图片下载处理方法
	private function download_image_to_media($url, $post_id, $size)
	{
		require_once(ABSPATH . 'wp-admin/includes/image.php');
		
		$tmp = download_url($url);
		if (is_wp_error($tmp)) {
			error_log('AI Post 图片下载失败: ' . $tmp->get_error_message());
			return false;
		}

		$file_array = [
			'name' => basename($url),
			'tmp_name' => $tmp
		];

		$attachment_id = media_handle_sideload($file_array, $post_id);
		
		if (is_wp_error($attachment_id)) {
			@unlink($tmp);
			error_log('AI Post 媒体处理失败: ' . $attachment_id->get_error_message());
			return false;
		}

		// 生成指定尺寸的缩略图（保持比例）
		$metadata = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id));
		
		// 添加自定义尺寸处理
		add_filter('image_resize_dimensions', function($default, $orig_w, $orig_h, $dest_w, $dest_h, $crop){
			if($dest_w == 300 && $dest_h == 225){
				// 计算缩放比例
				$ratio = $orig_w / $orig_h;
				if ($dest_w / $dest_h > $ratio) {
					$dest_w = $dest_h * $ratio;
				} else {
					$dest_h = $dest_w / $ratio;
				}
				return array(0, 0, 0, 0, (int)$dest_w, (int)$dest_h, $orig_w, $orig_h);
			}
			return $default;
		}, 10, 6);

		if (!empty($metadata)) {
			wp_update_attachment_metadata($attachment_id, $metadata);
		}

		// 检查是否启用WebP转换
		$enable_webp = ($this->settings['global_tabs']['enable-webp-conversion'] ?? '0') === '1';
		if ($enable_webp) {
			$file_path = get_attached_file($attachment_id);
			if ($file_path && file_exists($file_path)) {
				$path_info = pathinfo($file_path);
				$webp_path = $path_info['dirname'] . '/' . $path_info['filename'] . '.webp';
				
				// 使用GD库转换
				if (function_exists('imagewebp')) {
					$image_type = exif_imagetype($file_path);
					$image = null;
					
					switch ($image_type) {
						case IMAGETYPE_JPEG:
							$image = imagecreatefromjpeg($file_path);
							break;
						case IMAGETYPE_PNG:
							$image = imagecreatefrompng($file_path);
							if ($image) {
								imagepalettetotruecolor($image);
								imagealphablending($image, true);
								imagesavealpha($image, true);
							}
							break;
						case IMAGETYPE_GIF:
							$image = imagecreatefromgif($file_path);
							break;
					}
					
					if ($image) {
						if (imagewebp($image, $webp_path, 80)) {
							error_log("AI Post: 成功将图片转换为WebP格式: {$webp_path}");
							// 更新附件元数据
							$metadata['file'] = str_replace($path_info['basename'], $path_info['filename'] . '.webp', $metadata['file']);
							wp_update_attachment_metadata($attachment_id, $metadata);
							// 删除原图
							@unlink($file_path);
						}
						imagedestroy($image);
					}
				}
			}
		}

		return $attachment_id;
	}

	/**
	 * 注册自定义CSS设置项
	 */
	public function register_custom_css_setting($settings)
	{
		$settings['global_tabs']['global_custom_css'] = [
			'label'   => '全局自定义CSS',
			'type'    => 'textarea',
			'rows'    => 10,
			'default' => '',
			'desc'    => '在此处添加自定义CSS代码，无需包含&lt;style&gt;标签'
		];
		
		return $settings;
	}

	private function get_featured_image_position($content) {
		// 修复设置路径为正确的层级
		$position_setting = (int)($this->settings['global_tabs']['featured-image-position'] ?? 1);
		preg_match_all('/<img[^>]+src=([\'"])(?<src>.+?)\1[^>]*>/i', $content, $matches);
		$images = $matches['src'] ?? [];
		
		// 添加调试日志
		error_log('当前选择位置：'.$position_setting.'，实际索引：'.max(0, $position_setting - 1).'，图片数量：'.count($images));
		
		if (!empty($images)) {
			return $images[max(0, $position_setting - 1)] ?? $images[0];
		}
		
		return false;
	}

	/**
	 * 根据AI图片插入位置设置，在内容中插入AI生成的图片
	 * 
	 * @param string $content 原始内容
	 * @param string $img_html 图片HTML代码
	 * @return string 插入图片后的内容
	 */
	private function insert_ai_image_by_position($content, $img_html) {
        // 优先读取任务级豆包文生图插入位置；如未设置则回退到全局设置
        $task_key = $this->currentKey;
        $task_pos = null;
        if (isset($this->settings['tasks'][$task_key]['doubao-imagegen-insert-position'])) {
            $task_pos = (int)$this->settings['tasks'][$task_key]['doubao-imagegen-insert-position'];
        }
        // 全局“AI文生图插入位置”已删除，不再读取全局；无任务级配置时使用默认 1（首段后）
        $position_setting = (int)($task_pos ?? 1);
        // 约束范围到 1..5，默认用 1（首段后）
        if ($position_setting < 1 || $position_setting > 5) {
            $position_setting = 1;
        }
		
		// 将内容按段落分割
		$paragraphs = preg_split('/(<\/p>|<br\s*\/?>)/i', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
		$clean_paragraphs = [];
		
		// 重新组合段落，保持原有格式
		for ($i = 0; $i < count($paragraphs); $i += 2) {
			if (isset($paragraphs[$i]) && trim($paragraphs[$i]) !== '') {
				$paragraph = $paragraphs[$i];
				if (isset($paragraphs[$i + 1])) {
					$paragraph .= $paragraphs[$i + 1];
				}
				$clean_paragraphs[] = $paragraph;
			}
		}
		
		$total_paragraphs = count($clean_paragraphs);
		
		switch ($position_setting) {
			case 1: // 第一个段落后
				if ($total_paragraphs >= 1) {
					array_splice($clean_paragraphs, 1, 0, $img_html);
				} else {
					$clean_paragraphs[] = $img_html;
				}
				break;
			case 2: // 第二个段落后
				if ($total_paragraphs >= 2) {
					array_splice($clean_paragraphs, 2, 0, $img_html);
				} else {
					$clean_paragraphs[] = $img_html;
				}
				break;
			case 3: // 第三个段落后
				if ($total_paragraphs >= 3) {
					array_splice($clean_paragraphs, 3, 0, $img_html);
				} else {
					$clean_paragraphs[] = $img_html;
				}
				break;
			case 4: // 文章中间位置
				$middle_position = intval($total_paragraphs / 2);
				if ($middle_position > 0) {
					array_splice($clean_paragraphs, $middle_position, 0, $img_html);
				} else {
					$clean_paragraphs[] = $img_html;
				}
				break;
			case 5: // 文章末尾
			default:
				$clean_paragraphs[] = $img_html;
				break;
		}
		
		return implode('', $clean_paragraphs);
	}

	/**
	 * 将多张图片以“配图方式一”的思路平均分散插入正文。
	 * - 若存在 <p>/</p> 或 <br> 分段，则按段落数量平均分配插入点。
	 * - 若无段落，兜底将所有图片依次插入到正文开头。
	 * - 保持 <figure class="wp-block-image size-full aligncenter"> 包裹结构。
	 *
	 * @param string $content 原始内容
	 * @param array  $figures 由若干 HTML 片段组成的数组（每项形如 <figure>...）
	 * @return string 插入后的内容
	 */
	private function insert_images_distributed($content, array $figures) {
		try {
			$figures = array_values(array_filter($figures, function($x){ return is_string($x) && trim($x) !== ''; }));
			$img_n = count($figures);
			if ($img_n === 0) { return $content; }

			// 分段
			$parts = preg_split('/(<\/p>|<br\s*\/?\>)/i', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
			$paras = [];
			for ($i = 0; $i < count($parts); $i += 2) {
				if (isset($parts[$i]) && trim($parts[$i]) !== '') {
					$paragraph = $parts[$i];
					if (isset($parts[$i + 1])) { $paragraph .= $parts[$i + 1]; }
					$paras[] = $paragraph;
				}
			}

			$total = count($paras);
			\error_log('[AI Post Debug] 分散插入前统计: paragraphs=' . $total . ' | images=' . $img_n . ' | content_len=' . strlen($content));

			if ($total === 0) {
				// 无段落，头部依次插入
				return implode('', $figures) . $content;
			}

			// 计算间隔，尽量平均：将 content 划分为 (img_n+1) 个区间，在每个区间末插入一张
			$interval = (int) floor($total / ($img_n + 1));
			if ($interval < 1) { $interval = 1; }
			$inserted = 0;
			$offset = 0;
			for ($k = 0; $k < $img_n; $k++) {
				$idx = min(($k + 1) * $interval + $offset, count($paras));
				array_splice($paras, $idx, 0, $figures[$k]);
				$inserted++;
				// 当 interval 太小导致集中到结尾时，适当推进 offset
				if ($idx >= count($paras) - 1 && $interval === 1) { $offset++; }
			}

			$final = implode('', $paras);
			\error_log('[AI Post Debug] 分散插入完成: inserted=' . $inserted . ' | interval=' . $interval . ' | final_len=' . strlen($final));
			return $final;
		} catch (\Throwable $e) {
			\error_log('[AI Post Debug] 分散插入异常: ' . $e->getMessage());
			// 兜底：全部头部插入
			return implode('', $figures) . $content;
		}
	}

	private function process_custom_links($content) {
		$custom_links = explode("\n", $this->settings['global_tabs']['custom-links'] ?? '');
        $links = [];
		foreach ($custom_links as $link) {
			$link = trim($link);
			if (empty($link)) continue;
			
			$parts = explode('|', $link, 2);
			if (count($parts) === 2) {
				$keyword = trim($parts[0]);
				$url = trim($parts[1]);
                $links[$keyword] = $url;
			}
		}
		
		return $links;
	}

	/**
	 * 检查当前时间是否在给定的时间段内
	 */
	private function is_time_between($current, $start, $end)
	{
		// 将时间转换为分钟数进行比较
		$current_minutes = $this->time_to_minutes($current);
		$start_minutes = $this->time_to_minutes($start);
		$end_minutes = $this->time_to_minutes($end);
		
		error_log("AI Post Debug: 当前分钟数 {$current_minutes}，开始分钟数 {$start_minutes}，结束分钟数 {$end_minutes}");
		
		// 处理跨天的情况
		if ($end_minutes < $start_minutes) {
			return $current_minutes >= $start_minutes || $current_minutes <= $end_minutes;
		}
		
		return $current_minutes >= $start_minutes && $current_minutes <= $end_minutes;
	}

	/**
	 * 将时间字符串转换为分钟数
	 */
	private function time_to_minutes($time)
	{
		list($hours, $minutes) = explode(':', $time);
		return (intval($hours) * 60) + intval($minutes);
	}

	/**
	 * 执行AI任务请求，增强版本包含完整的错误处理和日志记录
	 * 
	 * @param string $model 模型名称
	 * @param string $topic 任务主题
	 * @param string $proxy 代理设置 
	 * @param array $options 其他选项
	 * @return array 结构化的响应结果
	 */
	public function runTask($model, $topic, $proxy = null, $options = [])
	{
		// 开始执行任务，记录基本参数
		error_log("AI任务执行: 模型={$model}, 主题={$topic}" . ($proxy ? ", 代理={$proxy}" : ""));
		
		try {
			// 记录关键参数
			error_log("任务参数: " . json_encode([
				'model' => $model,
				'topic' => $topic,
				'has_proxy' => !empty($proxy),
				'options_count' => count($options)
			]));

			// 验证账户信息
			$accountInfo = $this->validateAccountInfo();
			if (!$accountInfo['valid']) {
				error_log("账户验证失败: " . $accountInfo['message']);
				return [
					'status' => 'error',
					'code' => 401,
					'message' => '账户验证失败: ' . $accountInfo['message'],
					'data' => null
				];
			}
			error_log("账户验证成功: " . substr($accountInfo['token'], 0, 5) . '...' . substr($accountInfo['token'], -5));

			// 初始化API客户端
			try {
				$apiClient = $this->initApiClient($model, $accountInfo);
				error_log("API客户端初始化成功，使用模型: " . $apiClient->getModel());
			} catch (Exception $e) {
				error_log("API客户端初始化失败: " . $e->getMessage());
				return [
					'status' => 'error',
					'code' => 500,
					'message' => 'API客户端初始化失败: ' . $e->getMessage(),
					'data' => null
				];
			}

			// 准备请求内容
			$prompt = isset($options['prompt']) ? $options['prompt'] : '';
			$content = trim($topic . "\n" . $prompt);
			error_log("准备发送请求，内容长度: " . strlen($content) . " 字节");

			// 发送API请求
			try {
				$response = $apiClient->ask($content);
				error_log("API请求成功，响应ID: " . ($response['id'] ?? 'unknown'));
				
				// 记录Token使用情况
				if (isset($response['usage'])) {
					error_log("Token使用: " . json_encode($response['usage']));
				}
			} catch (Exception $e) {
				error_log("API请求失败: " . $e->getMessage());
				$errorCode = 500;
				$errorMessage = $e->getMessage();
				
				// 解析常见错误类型
				if (strpos($errorMessage, 'Unauthorized') !== false || strpos($errorMessage, '401') !== false) {
					$errorCode = 401;
					$errorMessage = '认证失败，请检查API密钥是否有效';
				} elseif (strpos($errorMessage, 'Rate limit') !== false) {
					$errorCode = 429;
					$errorMessage = '请求频率过高，请稍后再试';
				} elseif (strpos($errorMessage, 'timeout') !== false) {
					$errorCode = 408;
					$errorMessage = '请求超时，请稍后再试';
				}
				
				return [
					'status' => 'error',
					'code' => $errorCode,
					'message' => $errorMessage,
					'data' => null
				];
			}

			// 处理响应
			$result = $this->processResponse($response, $options);
			error_log("响应处理完成，状态: " . $result['status']);
			
			// 文章生成成功后写入日志
			$title = $options['title'] ?? $topic;
			$status = $options['final_status'] ?? 'unknown';
			$is_rewrite = $options['is_rewrite'] ?? null; // 获取is_rewrite参数
			$original_title = $options['original_title'] ?? null; // 获取原始标题
			\HaoZiTeam\AIPost\Service\Features\RunLog::add($topic, $title, $status, '', $is_rewrite, 0, $original_title);
			
			return $result;
		} catch (Exception $e) {
			// 捕获所有未处理的异常
			error_log("任务执行过程中发生未处理异常: " . $e->getMessage());
			error_log("异常堆栈: " . $e->getTraceAsString());
			
			return [
				'status' => 'error',
				'code' => 500,
				'message' => '执行过程中发生错误: ' . $e->getMessage(),
				'data' => null
			];
		}
	}

	/**
	 * 验证账户信息
	 * 
	 * @return array 包含验证结果和令牌信息
	 */
	private function validateAccountInfo()
	{
		// 获取当前设置
		$settings = $this->settings;
		$apiServerType = $settings['api-server-type'] ?? 'custom';
		error_log("验证账户信息，服务器类型: " . $apiServerType);
		
		// 根据不同API服务类型获取密钥
		$token = '';
		$valid = false;
		$message = '';
		
		if ($apiServerType === 'deepseek') {
			if (!empty($settings['deepseek-accounts'])) {
				$accounts = $settings['deepseek-accounts'];
				$index = rand(1, count($accounts));
				$account = $accounts[$index - 1];
				$token = $account['account-secret-key'] ?? '';
				$valid = !empty($token);
				$message = $valid ? 'DeepSeek账户验证成功' : 'DeepSeek账户密钥为空';
			} else {
				$message = '未配置DeepSeek账户';
			}
		} elseif ($apiServerType === 'doubao') {
			if (!empty($settings['doubao-accounts'])) {
				$accounts = $settings['doubao-accounts'];
				$index = rand(1, count($accounts));
				$account = $accounts[$index - 1];
				$token = $account['account-secret-key'] ?? '';
				$valid = !empty($token);
				$message = $valid ? '豆包账户验证成功' : '豆包账户密钥为空';
			} else {
				$message = '未配置豆包账户';
			}
		} else {
			// 常规API账户逻辑 - 多种来源尝试
			if (!empty($settings['openai-accounts'])) {
				$accounts = $settings['openai-accounts'];
				$index = rand(1, count($accounts));
				$account = $accounts[$index - 1];
				$token = $account['account-secret-key'] ?? '';
				$valid = !empty($token);
				$message = $valid ? 'OpenAI账户验证成功' : 'OpenAI账户密钥为空';
			} elseif (!empty($settings['cardPwd'])) {
				$token = $settings['cardPwd'];
				$valid = !empty($token);
				$message = $valid ? '卡密验证成功' : '卡密为空';
			} elseif (!empty($settings['cardNum'])) {
				$token = $settings['cardNum'];
				$valid = !empty($token);
				$message = $valid ? '卡号验证成功' : '卡号为空';
			} else {
				$message = '未找到有效的API密钥';
			}
		}
		
		return [
			'valid' => $valid,
			'token' => $token,
			'message' => $message,
			'server_type' => $apiServerType
		];
	}

	/**
	 * 初始化API客户端
	 * 
	 * @param string $model 模型名称
	 * @param array $accountInfo 账户信息
	 * @return V2 API客户端实例
	 */
	private function initApiClient($model, $accountInfo)
	{
		$serverType = $accountInfo['server_type'];
		$token = $accountInfo['token'];
		
		if ($serverType === 'deepseek') {
			$apiServer = 'https://api.deepseek.com/';
			$selectedModel = !empty($model) ? $model : 'deepseek-chat';
			
			return new V2(
				$token,
				$apiServer,
				$selectedModel,
				null,
				null,
				360,
				[
					'is_deepseek' => true,
					'headers' => [
						'Authorization' => 'Bearer ' . $token,
						'Content-Type' => 'application/json'
					]
				]
			);
		} elseif ($serverType === 'doubao') {
			$apiServer = 'https://ark.cn-beijing.volces.com/api/v3/';
			$selectedModel = !empty($model) ? $model : 'doubao-1.5-pro-32k-250115';
			
			return new V2(
				$token,
				$apiServer,
				$selectedModel,
				null,
				null,
				360,
				[
					'is_doubao' => true,
					'temperature' => 0.7,
					'headers' => [
						'Authorization' => 'Bearer ' . $token,
						'Content-Type' => 'application/json'
					]
				]
			);
		} else {
			// 标准API服务
			$apiServer = $this->settings['openai-api-server'] ?? 'https://sk.slapi.cn/';
			$selectedModel = !empty($model) ? $model : 'gpt-3.5-turbo';
			
			return new V2(
				$token,
				$apiServer,
				$selectedModel
			);
		}
	}

	/**
	 * 处理API响应
	 * 
	 * @param array $response API响应
	 * @param array $options 选项
	 * @return array 处理后的结果
	 */
	private function processResponse($response, $options = [])
	{
		if (empty($response) || !isset($response['answer'])) {
			return [
				'status' => 'error',
				'code' => 500,
				'message' => '无效的API响应',
				'data' => null
			];
		}
		
		$answer = $response['answer'];
		$outputFormat = $options['output_format'] ?? 'text';
		
		// 如果包含提示词的处理
		if (isset($options['with_prompt']) && $options['with_prompt']) {
			$answer = $this->processWithPrompt($answer, $options);
		}
		
		// 处理不同的输出格式
		if ($outputFormat === 'json') {
			try {
				// 尝试解析JSON响应
				if (substr($answer, 0, 1) !== '{' && substr($answer, 0, 1) !== '[') {
					// 查找可能的JSON字符串
					if (preg_match('/```(?:json)?\s*({[\s\S]*?}|[[\s\S]*?])\s*```/', $answer, $matches)) {
						$jsonStr = $matches[1];
					} else {
						$jsonStr = $answer;
					}
				} else {
					$jsonStr = $answer;
				}
				
				// 清理可能的非JSON字符
				$jsonStr = preg_replace('/^[^{\[]+/', '', $jsonStr);
				$jsonStr = preg_replace('/[^}\]]+$/', '', $jsonStr);
				
				$jsonData = json_decode($jsonStr, true);
				if (json_last_error() !== JSON_ERROR_NONE) {
					error_log("JSON解析失败: " . json_last_error_msg() . ", 原始内容: " . substr($jsonStr, 0, 100) . "...");
					throw new Exception("无法解析JSON响应");
				}
				
				return [
					'status' => 'success',
					'code' => 200,
					'message' => '成功',
					'data' => $jsonData
				];
			} catch (Exception $e) {
				error_log("处理JSON响应失败: " . $e->getMessage());
				return [
					'status' => 'error',
					'code' => 422,
					'message' => '无法解析为JSON格式: ' . $e->getMessage(),
					'data' => $answer // 返回原始回答作为备选
				];
			}
		} else {
			// 文本格式直接返回
			return [
				'status' => 'success',
				'code' => 200,
				'message' => '成功',
				'data' => $answer
			];
		}
	}

	/**
	 * 处理带提示词的内容
	 * 
	 * @param string $answer 原始回答
	 * @param array $options 选项
	 * @return string 处理后的内容
	 */
	private function processWithPrompt($answer, $options)
	{
		// 移除常见的提示词前缀
		$answer = preg_replace('/^(以下是|好的|我来|这是|以下提供)/i', '', $answer);
		$answer = trim($answer);
		
		// 处理特定格式的提示内容
		if (isset($options['prompt_format'])) {
			switch ($options['prompt_format']) {
				case 'markdown':
					// 移除markdown代码块标记
					$answer = preg_replace('/```[a-z]*\n/i', '', $answer);
					$answer = str_replace('```', '', $answer);
					break;
				case 'code':
					// 提取代码块内容
					if (preg_match('/```(?:[a-z]*\n)?([\s\S]*?)```/i', $answer, $matches)) {
						$answer = $matches[1];
					}
					break;
			}
		}
		
		return trim($answer);
	}

	// --- 新增：生成并插入文章目录的方法 ---
	private function generate_and_insert_toc($content, $settings)
	{
		libxml_use_internal_errors(true);
		$dom = new DOMDocument();
		// 统一确认 UTF-8，并将输入以 HTML-ENTITIES 形式提供给 DOMDocument，避免中文被误读
		if (mb_detect_encoding($content, 'UTF-8', true) === false) {
			$content = mb_convert_encoding($content, 'UTF-8');
		}
		$toc_wrapped = '<div>' . $content . '</div>';
		$toc_encoded = mb_convert_encoding($toc_wrapped, 'HTML-ENTITIES', 'UTF-8');
		$dom->loadHTML($toc_encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
		libxml_clear_errors();

		$xpath = new DOMXPath($dom);
		// 修改 XPath 查询，包含 h4
		$headings = $xpath->query('//h2 | //h3 | //h4');

		// 如果没有足够的标题，则不生成目录
		if ($headings->length < $settings['min_headings']) {
			return ['', $content]; // 返回空目录和原始内容
		}

		// 读取新设置：目录默认状态
		$default_state = $this->settings['global_tabs']['toc-default-state'] ?? 'expanded';
		$initial_class = ($default_state === 'collapsed') ? 'toc-collapsed' : 'toc-expanded';

		$toc = '<div class="ai-post-toc ' . $initial_class . '" style="background-color: ' . esc_attr($settings['background']) . '; border: 1px solid #eee; padding: 15px; margin-bottom: 20px;">';
		// 添加折叠/展开图标 和 文字 (移除 color 样式)
		$toc .= '<p class="toc-title" style="font-weight: bold; margin-bottom: 10px; cursor: pointer;">' 
			  . esc_html($settings['title']) 
			  . '<span class="toc-toggle-icon">▼</span>' 
			  . '<span class="toc-status-text toc-text-close" style="margin-left: 5px; font-weight: normal; font-size: 0.9em;">Close</span>' // 添加 Close 文本
			  . '<span class="toc-status-text toc-text-open" style="margin-left: 5px; font-weight: normal; font-size: 0.9em;">Open</span>'   // 添加 Open 文本
			  . '</p>';
		$toc .= '<ul class="toc-list" style="list-style: none; padding-left: 0; margin-bottom: 0;">'; // 修改为 toc-list
		$has_toc_items = false;
		$current_level = 0;
		$counter = 0;

		foreach ($headings as $heading) {
			if ($heading instanceof DOMElement) { // 添加检查
				$level = (int)substr($heading->nodeName, 1); // 获取 h2/h3/h4 的级别
				$text = trim($heading->textContent);
				if (empty($text)) {
					continue;
				}

				$id = $heading->getAttribute('id');
				if (empty($id)) {
					$id = 'toc-heading-' . $counter++;
					$heading->setAttribute('id', $id);
				}

				// 根据级别调整列表嵌套
				if ($level > $current_level) {
					$toc .= '<ul style="list-style: none; padding-left: 20px;">';
				} elseif ($level < $current_level) {
					$toc .= '</ul></li>'; // 先闭合内层 ul，再闭合外层 li
				}

				if ($level == $current_level && $has_toc_items) { // 只有在已经有项目后才闭合同级 li
					$toc .= '</li>';
				}

				// 添加 toc-link 类用于 JS 选择器, 并应用链接颜色
				$toc .= '<li style="margin-bottom: 5px;"><a href="#' . esc_attr($id) . '" class="toc-link" style="text-decoration: none; color: ' . esc_attr($settings['link_color']) . ';">' . esc_html($text) . '</a>';
				$current_level = $level;
				$has_toc_items = true;
			} else {
			   // 如果不是 DOMElement，记录错误或跳过
			   error_log("AI Post Debug: TOC generation encountered a non-element node."); 
			   continue; 
			}
		}

		// 闭合所有未闭合的列表项和列表
		// 将闭合循环的条件从 >= 2 修改为 >= 2，以处理可能的 H4 层级
		while ($current_level >= 2) { // Condition should be >= 2 to close H2 level lists
			$toc .= '</li></ul>';
			$current_level--;
		}

		$toc .= '</div>'; // 闭合主 div

		// 保存修改后的 HTML 内容（包含添加的 ID）
		// --- START: Revised Content Extraction ---
		$modified_content = '';
		$doc_element = $dom->documentElement; // Get the root element

		// Check if we have a root element and it seems to be our wrapper div
		if ($doc_element && $doc_element->nodeName === 'div') {
			 // Iterate through the children of the wrapper div
			 foreach ($doc_element->childNodes as $node) {
				 $modified_content .= $dom->saveHTML($node);
			 }
			 error_log("AI Post TOC Debug: Extracted content using root element (wrapper div) iteration.");
		} else {
			// Fallback 1: If the root element isn't the div (unexpected), try finding the body's first child
			error_log("AI Post TOC Debug: Root element was not the expected div (".($doc_element ? $doc_element->nodeName : 'null')."). Trying body->firstChild iteration.");
			$body = $dom->getElementsByTagName('body')->item(0);
			if ($body && $body->firstChild) {
				foreach ($body->firstChild->childNodes as $node) {
					$modified_content .= $dom->saveHTML($node);
				}
				error_log("AI Post TOC Debug: Extracted content using body->firstChild iteration.");
			} else {
				// Fallback 2: Final Fallback - If all else fails, save the whole document and attempt regex/stripping
				error_log("AI Post TOC Debug: *** FINAL FALLBACK EXTRACTION TRIGGERED! (body->firstChild failed) ***");
				$saved_html = $dom->saveHTML();
				error_log("AI Post TOC Debug: Full saved HTML in fallback: " . substr($saved_html, 0, 200));

				// Try the regex again on the full saved HTML (made slightly less greedy)
				if (preg_match('/<body[^>]*>\s*<div[^>]*>(.*?)<\/div>\s*<\/body>/is', $saved_html, $matches)) {
					$modified_content = $matches[1];
					error_log("AI Post TOC Debug: Extracted content using fallback regex.");
				} else {
					// Last resort: basic tag stripping
					error_log("AI Post TOC Debug: Fallback regex failed. Resorting to basic tag stripping.");
					$modified_content = preg_replace('/^.*?<body[^>]*>/is', '', $saved_html);
					$modified_content = preg_replace('/<\/body>.*?$/is', '', $saved_html);
					$modified_content = preg_replace('/^\s*<div[^>]*>/is', '', $modified_content);
					$modified_content = preg_replace('/<\/div>\s*$/is', '', $modified_content);
					$modified_content = trim($modified_content);
					error_log("AI Post TOC Debug: Content after basic stripping: " . substr($modified_content, 0, 100));
				}
			}
		}

		// Final cleanup: Decode HTML entities regardless of extraction method
		$modified_content = html_entity_decode($modified_content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		error_log("AI Post TOC Debug: Final modified_content length after decode: " . strlen($modified_content));
		// --- END: Revised Content Extraction ---

		// 只有在确实生成了目录项时才返回目录 HTML
		return $has_toc_items ? [$toc, $modified_content] : ['', $content]; // Return original $content if no TOC items
	}
	// --- 结束：生成并插入文章目录的方法 ---

	// 新增：将TOC脚本添加到页脚
	public function add_toc_script_to_footer() {
		StyleUtils::output_toc_script($this->settings); //New
	}

	/**
	 * Output Schema.org JSON-LD script to footer if available in post meta.
	 */
	public function output_schema_json_ld()
	{
		MetaTags::output_stored_schema_json_ld(); // New
	}

	/**
	 * Processes WebP conversion for images in the content.
	 *
	 * @param string &$content The content string (passed by reference).
	 * @return void
	 */
	private function _process_webp_conversion(&$content)
	{
        ImageProcessor::convert_content_images_to_webp($content, $this->settings);
	}

	/**
	 * 处理随机段落内容
	 * 格式为 **段落一**|**段落二**|**段落三**
	 * 
	 * @param string $content 原始内容
	 * @return string 处理后的内容
	 */
	private function process_random_paragraphs($content) {
		// 如果内容为空，直接返回
		if (empty($content)) {
			return $content;
		}
		
		// 检查内容是否使用了随机段落格式（**内容**|**内容**）
		if (preg_match('/\*\*(.*?)\*\*\|(.*)/s', $content)) {
			// 提取所有段落
			$pattern = '/\*\*(.*?)\*\*/';
			preg_match_all($pattern, $content, $matches);
			
			if (!empty($matches[1])) {
				// 随机选择一个段落
				$random_index = array_rand($matches[1]);
				$selected_paragraph = $matches[1][$random_index];
				
				error_log("AI Post Debug: 随机段落功能 - 从 " . count($matches[1]) . " 个段落中选择了第 " . ($random_index + 1) . " 个段落");
				
				// 返回选中的段落
				return $selected_paragraph;
			}
		}
		
		// 如果没有使用特殊格式或解析失败，则返回原内容
		return $content;
	}
}
