<?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
use HaoZiTeam\AIPost\Service\Features\Logger;
use HaoZiTeam\AIPost\Service\Settings\Traits\TraitLicense;

class Base
{
	use TraitLicense;

	/** CJK 全角标点码点：编码时保留原样，避免标点丢失 */
	private const CJK_PRESERVE_CODEPOINTS = [
		0x3000, 0x3001, 0x3002, 0xFF0C, 0xFF1B, 0xFF1A, 0xFF1F, 0xFF01,
		0x2014, 0x2026, 0x300A, 0x300B, 0xFF08, 0xFF09, 0x3008, 0x3009,
		0x3010, 0x3011, 0x201C, 0x201D, 0x2018, 0x2019,
	];

	private $settings;
	private $currentKey;
	private $headBuffering = false;
	private $imagePromptClient = null;

	private function append_cache_buster_to_url(string $url): string
	{
		$url = trim($url);
		if ($url === '') {
			return $url;
		}

		$bust = (string)time() . '_' . (string)mt_rand(1000, 9999);
		if (function_exists('add_query_arg')) {
			return (string)add_query_arg('r', $bust, $url);
		}
		$sep = (strpos($url, '?') === false) ? '?' : '&';
		return $url . $sep . 'r=' . rawurlencode($bust);
	}

	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;
				Logger::debug('使用 ai-post-tasks 作为任务源，count=' . count($tasks_opt), 'Init');
			} elseif (is_array($this->settings) && !empty($this->settings['tasks']) && is_array($this->settings['tasks'])) {
				// 回退：旧结构，存在于 ai-post.tasks
				$this->settings = get_option('ai-post');
				Logger::debug('检测到旧结构（ai-post.tasks）作为任务源，count=' . count($this->settings['tasks']), 'Init');
			} else {
				// 无任务配置
				$this->settings = get_option('ai-post');
				Logger::debug('未检测到任务配置（ai-post-tasks 与 ai-post.tasks 均为空）', 'Init');
			}

		} catch (\Throwable $e) {
			Logger::error('读取任务源时发生异常: ' . $e->getMessage(), 'Init');
		}

		// 加载媒体库
		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'));
		add_action('aipost_ai_comments_generate', array($this, 'aipost_ai_comments_generate'), 10, 2);
		add_action('aipost_ai_comments_approve_comment', array($this, 'aipost_ai_comments_approve_comment'), 10, 1);
		add_action('aipost_ai_comments_task_cleanup', array($this, 'aipost_ai_comments_task_cleanup'), 10, 2);
		add_filter('get_avatar_comment_types', array($this, 'aipost_ai_comments_add_avatar_comment_type'), 10, 1);
		add_filter('pre_get_avatar_data', array($this, 'aipost_ai_comments_pre_get_avatar_data'), 99, 2);
		add_filter('get_avatar', array($this, 'aipost_ai_comments_filter_get_avatar'), 99, 6);

		// 新增头部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";

        $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) . '">'; }
            }
        }

        if (trim($buf) === '') { 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 !== '') {
                echo "\n<style id=\"ai-post-global-css\">\n" . $css . "\n</style>\n";
            }

            // 新增：统一在此输出插件内部定义的全局样式（包括特性卡片样式等）
            if (is_array($this->settings)) {
                \HaoZiTeam\AIPost\Service\Features\StyleUtils::output_global_styles($this->settings);
            }
        } 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 转义
            // 这样可以避免“所有内容拼成一串”而丢失可视标点
            if (in_array($codepoint, self::CJK_PRESERVE_CODEPOINTS, 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];
            // 保留常见中文全角标点
            if (in_array($codepoint, self::CJK_PRESERVE_CODEPOINTS, 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() 已移除，避免与后文完整实现重复声明

    /**
     * 使用 AI 客户端根据文章标题和内容摘要生成语义化的文生图 prompt。
     * 通过让 LLM 理解文章主题，生成与内容高度相关的英文场景描述，
     * 大幅提升文生图的内容相关性。
     *
     * @param string $title 文章标题
     * @param string $content_excerpt 文章内容摘要（可选，提供更多上下文）
     * @return string 英文场景描述，失败时返回空字符串（调用方应回退到静态模板）
     */
    private function buildImageScenePrompt(string $title, string $content_excerpt = ''): string
    {
        if (!$this->imagePromptClient) {
            return '';
        }
        $title = trim($title);
        if ($title === '') {
            return '';
        }

        // 构造精简的 prompt，让 AI 生成具体的视觉场景描述
        $instruction = "You are an image prompt engineer. Based on the article information below, " .
            "write a concise English image description (2-3 sentences) for an AI image generator. " .
            "The image must directly illustrate the article's core topic. " .
            "Describe specific visual elements: subjects, objects, environment, colors, style, lighting, and composition. " .
            "For technology/software topics, show relevant interfaces, dashboards, devices, or workflows. " .
            "For lifestyle topics, show relevant people, places, or activities. " .
            "IMPORTANT: The description must NOT include any text, letters, or words to be rendered in the image.\n\n" .
            "Article title: " . $title;

        if ($content_excerpt !== '') {
            $instruction .= "\nArticle excerpt: " . mb_substr($content_excerpt, 0, 300);
        }

        $instruction .= "\n\nOutput ONLY the image description in English, nothing else.";

        try {
            $response = $this->imagePromptClient->ask($instruction);
            $scene = '';
            if (is_array($response) && !empty($response['answer'])) {
                $scene = trim((string)$response['answer']);
            } elseif (is_string($response)) {
                $scene = trim($response);
            }

            // 基本验证：场景描述应是合理长度的英文文本
            if ($scene !== '' && strlen($scene) >= 20 && strlen($scene) <= 1500) {
                Logger::debug('AI scene prompt generated (len=' . strlen($scene) . '): ' . mb_substr($scene, 0, 120) . '...', 'ImageGen');
                return $scene;
            }

            Logger::warning('AI scene prompt invalid or empty (len=' . strlen($scene) . '), falling back to static template', 'ImageGen');
            return '';
        } catch (\Throwable $e) {
            Logger::error('AI scene prompt generation failed: ' . $e->getMessage(), 'ImageGen');
            return '';
        }
    }

    /**
     * 仅执行豆包文生图“配图方式一(分散)”插入，不触发标题图生成流程
     *
     * @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) {
                Logger::debug('distributed-only: task_key=' . (string)$key, 'InsertImage');

                // 提供商选择：默认豆包，兼容未来扩展
                $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 系列常用上限
                    }
                } elseif ($provider === 'zhipu') {
                    // 智谱每次仅生成单张图片，数量通过循环调用实现
                    $img_count = (int)($task_settings['zhipu-imagegen-image-count'] ?? 1);
                    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; }
                }
                Logger::debug('distributed-only: provider=' . $provider . ' img_count=' . $img_count, 'InsertImage');

                // 生成一次 Prompt（优先使用 AI 语义化场景描述提升相关性）
                $ai_scene_hint = $this->buildImageScenePrompt((string)$alt_text);
                $prompt = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_prompt_from_title((string)$alt_text, $this->settings, $ai_scene_hint);
                $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);
                    } elseif ($provider === 'zhipu') {
                        $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_zhipu($prompt, $this->settings, $task_settings);
                    } else {
                        $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
                    }
                    if (is_wp_error($gen)) {
                        Logger::warning('distributed-only: gen failed #' . ($i+1) . ' provider=' . $provider . ' => ' . $gen->get_error_message(), 'InsertImage');
                        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);
                    Logger::info('distributed-only: inserted ' . count($figures) . ' image(s).', 'InsertImage');
                } else {
                    Logger::warning('distributed-only: no valid images generated.', 'InsertImage');
                }
            } else {
                Logger::debug('distributed-only: imagegen switch OFF, skip. task_key=' . (is_scalar($key) ? (string)$key : gettype($key)), 'InsertImage');
            }
        } catch (\Throwable $e) {
            Logger::error('distributed-only: exception: ' . $e->getMessage(), 'InsertImage');
        }

        return $content;
    }

	/**
	 * 注册定时任务
	 *
	 * @return void
	 */
    public function plugin_init(): void
    {
        // 循环注册定时任务
        if (empty($this->settings['tasks'])) {
            Logger::debug('未发现可用任务（settings.tasks 为空），跳过注册', 'Cron');
            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));
                    Logger::info("检测到任务已关闭，已取消后续调度: {$key}", 'Cron');
                } 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);
                    Logger::debug(sprintf(
                        'Frequency decision | task=%s | last_run=%s | diff=%sm | freq=%sm | decision=skip',
                        (string)$key,
                        $last_run ? date('Y-m-d H:i:s', $last_run) : 'never',
                        (string)$time_diff,
                        (string)$freq
                    ), 'Cron');
                }
                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));
                Logger::info(sprintf('已为任务 %s 注册单次调度，delay=%ss，next=%s', (string)$key, (string)$delay, date('Y-m-d H:i:s', $next)), 'Cron');
                // 新增：在本次请求内主动触发一次 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);
                            Logger::debug(sprintf('已主动触发 spawn_cron() 以加速任务 %s 执行', (string)$key), 'Cron');
                        }
                    }
                } 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);
                        $catchup_time = time() + 5;
                        wp_schedule_single_event($catchup_time, 'ai_post_cron', array($key)); // 5秒后补发，避免与当前请求竞争
                        Logger::info(sprintf('任务 %s 检测到长时间未跑，已补发一次执行（5s 后）', (string)$key), 'Cron');
                        // 新增：补发后也主动触发一次 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);
                                    Logger::debug(sprintf('补发后已主动触发 spawn_cron() 以加速任务 %s 执行', (string)$key), 'Cron');
                                }
                            }
                        } catch (\Throwable $e) {
                            // 忽略 spawn 错误
                        }
                    } else {
                        Logger::debug(sprintf('任务 %s 补发已在短期内安排，跳过重复补发', (string)$key), 'Cron');
                    }
                } else {
                    Logger::debug(sprintf('任务 %s 已存在排程，next=%s', (string)$key, date('Y-m-d H:i:s', (int)$scheduled)), 'Cron');
                }
            }
        }

    }

	/**
	 * 获取文章对应的唯一文件名
	 *
	 * @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 验证）
		Logger::debug('invoked: task_key=' . (string)$key . ' | title="' . (string)$post_title . '" | force_title_mode=' . ($force_title_mode ? 'true' : 'false'), 'Thumbnail');

		// 优先：如启用豆包文生图，则先尝试基于标题生成英文指令并调用文生图
		$enable_doubao = (bool)($task_settings['enable-doubao-imagegen'] ?? false);
		Logger::debug('开关状态 enable-doubao-imagegen=' . ($enable_doubao ? 'ON' : 'OFF') . ' | task_key=' . (string)$key . ' | title="' . (string)$post_title . '"', 'ImageGen');
		if ($enable_doubao && !$force_title_mode) {
			try {
				$ai_scene_hint = $this->buildImageScenePrompt((string)$post_title);
				$prompt = ImageProcessor::generate_image_prompt_from_title((string)$post_title, $this->settings, $ai_scene_hint);
				$prompt_log = mb_substr((string)$prompt, 0, 160);
				Logger::debug('已生成提示词，长度=' . strlen((string)$prompt) . ' | 预览="' . $prompt_log . (strlen((string)$prompt) > 160 ? '…' : '') . '"', 'ImageGen');
				$provider = (string)($task_settings['imagegen-provider'] ?? 'doubao');
				if ($provider === 'aliyun') {
					$gen = ImageProcessor::generate_image_via_aliyun_dashscope($prompt, $this->settings);
				} elseif ($provider === 'zhipu') {
					$gen = ImageProcessor::generate_image_via_zhipu($prompt, $this->settings, $task_settings);
				} else {
					$gen = ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
				}
				if (is_wp_error($gen)) {
					Logger::warning('Failed to generate (provider=' . $provider . '), falling back to local thumbnail: ' . $gen->get_error_message(), 'ImageGen');
				} elseif (is_array($gen)) {
					// 约定：返回 ['url' => ...]，根据任务设置决定是否侧载到媒体库
					Logger::debug('API returned structure (provider=' . $provider . '): ' . json_encode(array_keys($gen)) . ' | url=' . (string)($gen['url'] ?? ''), 'ImageGen'); 
					if (!empty($gen['url'])) {
						$download_to_media = (bool)($task_settings['doubao-imagegen-download-to-media'] ?? true);
						Logger::debug('Decision (provider=' . $provider . '): ' . ($download_to_media ? 'download and sideload to media' : 'return remote URL'), 'ImageGen');
						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'])) {
								Logger::info('Sideload successful (provider=' . $provider . '), attachment_id=' . ((int)($attach['attachment_id'] ?? 0)) . ', url=' . (string)$attach['url'], 'ImageGen');
								$attachment_id = (int)($attach['attachment_id'] ?? 0);

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

            // 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)) {
				Logger::debug('return attachment array: id=' . (string)($attachment['id'] ?? '') . ' | guid=' . (string)($attachment['guid'] ?? '') . ' | file=' . (string)($attachment['file'] ?? '') . ' | type=' . (string)($attachment['type'] ?? ''), 'Thumbnail');
			} else {
				Logger::debug('return null/false', 'Thumbnail');
			}
			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 与内容长度
			Logger::debug('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 ? '…' : '') . '"', 'InsertImage');

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

                    // 提供商选择：默认豆包，兼容未来扩展
                    $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; }
                        }
                    } elseif ($provider === 'zhipu') {
                        $img_count = (int)($task_settings['zhipu-imagegen-image-count'] ?? 1);
                        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);
                    Logger::debug('the_content: provider=' . $provider . ' img_count=' . $img_count . ' download_to_media=' . ($download_to_media ? 'ON' : 'OFF'), 'InsertImage');

                    // 生成一次 Prompt，复用以提升一致性（优先使用 AI 语义化场景描述）
                    $ai_scene_hint = $this->buildImageScenePrompt((string)$alt_text);
                    $prompt = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_prompt_from_title((string)$alt_text, $this->settings, $ai_scene_hint);

                    $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);
                        } elseif ($provider === 'zhipu') {
                            $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_zhipu($prompt, $this->settings, $task_settings);
                        } else {
                            // 关键修复：传入 $task_settings 以使用任务级尺寸/模型等参数
                            $gen = \HaoZiTeam\AIPost\Service\Features\ImageProcessor::generate_image_via_volcengine($prompt, $this->settings, $task_settings);
                        }
                        if (is_wp_error($gen)) {
                            Logger::warning('the_content: gen failed #' . ($i+1) . ' provider=' . $provider . ' => ' . $gen->get_error_message(), 'InsertImage');
                            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);
                        Logger::info('the_content: inserted ' . count($figures) . ' image(s) via distributed method-1.', 'InsertImage');
                        // 关键修复：分散插图已完成时，直接返回，避免后续标题图流程再次插入导致多一张
                        return $content;
                    } else {
                        Logger::warning('the_content: no valid images generated, skip distributed insert.', 'InsertImage');
                    }
                }
            } catch (\Throwable $e) {
                Logger::error('the_content: exception during distributed insert: ' . $e->getMessage(), 'InsertImage');
            }
            // --- 改造结束 ---

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

			if ($attachment && isset($attachment['guid'])) { // This check is now somewhat redundant but safe to keep
				Logger::debug('the_content: Got attachment GUID=' . $attachment['guid'] . ', alt_text=' . $alt_text, 'InsertImage');
			} elseif (!$attachment) {
				Logger::warning('the_content: generate_thumbnail returned null/false. alt_text=' . $alt_text, 'InsertImage');
				// 如果 $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);
				Logger::debug('the_content: position_setting=' . $position_setting . ' (1=head, 3=tail, 0/2=auto)', 'InsertImage');

				// --- 优化开始: 添加表格保护机制 ---
				// 保存原始内容，用于后续恢复表格
				$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
				);
				if ($content_with_placeholders) {
					$content_with_placeholders = preg_replace_callback(
						'/<div[^>]*class=["\'][^"\']*\bai-faq\b[^"\']*["\'][^>]*>.*?<\/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
					);
				}
				
				// 只有在成功替换表格后才使用带占位符的内容
				if (!empty($table_placeholders)) {
					$content = $content_with_placeholders;
					Logger::debug('图片插入前保护了 ' . count($table_placeholders) . ' 个表格结构', 'InsertImage');
				} else {
					Logger::debug('未检测到需保护的表格结构，跳过占位', 'InsertImage');
				}
				// --- 表格保护机制结束 ---

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

							if ($this->settings['tasks'][$key]['enable-image-url-position'] == 2) {
								// --- 优化: 改进中间位置计算逻辑 ---
								$image_start_num = intval(($p_nums - 1) / 2);
								Logger::debug('the_content: middle position computed index=' . $image_start_num, 'InsertImage');
							} else {
								$image_start_num = mt_rand(0, max(0, $p_nums - 1));
								Logger::debug('the_content: random position chosen index=' . $image_start_num, 'InsertImage');
							}
						} else {
							// 若没有可用标签，直接头部兜底
							Logger::debug('the_content: no candidate tags found, fallback to head prepend.', 'InsertImage');
							$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) {
							Logger::debug('the_content: regex pass did not insert, fallback to head prepend.', 'InsertImage');
							$content = $image_html . $content;
						}
				}
				
				// --- 优化: 恢复表格结构 ---
				if (!empty($table_placeholders)) {
					$content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
					Logger::debug('the_content: restored ' . count($table_placeholders) . ' protected table blocks after insertion.', 'InsertImage');
				}
				// --- 恢复表格结构结束 ---
			}
		}

		return $content;
	}


	/**
	 * 运行定时任务
	 *
	 * @param $key
	 *
	 * @return void
	 */
	public function ai_post_cron($key)
    {
		// 优先级队列：非队列消费上下文时，仅入队（NORMAL）并返回，由队列统一调度执行。
		// 队列消费时会设置 $GLOBALS['aipost_queue_processing']=true，以避免 do_action('ai_post_cron') 触发递归入队。
		if (empty($GLOBALS['aipost_queue_processing'])) {
			try {
				if (class_exists('HaoZiTeam\\AIPost\\Service\\Features\\PriorityQueue')) {
					PriorityQueue::enqueue_task_cron($key);
					return;
				}
			} catch (\Throwable $e) {
				// ignore and fallback to direct run
			}
		}

		// === 运行时授权心跳检查（带 Transient 缓存，每 6 小时远端验证一次）===
		if (!$this->license_heartbeat_check()) {
			Logger::warning('授权无效或已过期，任务被阻止: ' . (string)$key, 'Cron');
			return;
		}
		// === 文件完整性 canary：验证授权端点未被篡改 ===
		try {
			$_ep = $this->_auth_base();
			if (strpos($_ep, base64_decode('cHJvZC1hcGkvcGx1Z2luL2F1dGgv')) === false
				|| strpos($_ep, 'https://') !== 0) {
				Logger::error('license endpoint tampered, task blocked: ' . (string)$key, 'Integrity');
				return;
			}
		} catch (\Throwable $_ie) {}

		$is_rewrite_type = null; // 新增：AI改写类型标记
       $did_run = false; // 频率门控：标记本次是否真正执行了任务

       // === Cron 运行环境兜底：避免长任务被 PHP max_execution_time 强制中断 ===
       try {
           if (function_exists('ignore_user_abort')) {
               @ignore_user_abort(true);
           }
           if (function_exists('set_time_limit')) {
               @set_time_limit(0);
           }
           if (function_exists('ini_set')) {
               @ini_set('max_execution_time', '0');
           }
       } catch (\Throwable $e) {
           // 忽略
       }

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

       // 关键：将当前任务键写入实例，供 the_content()/generate_thumbnail()/insert_doubao_images_distributed() 等读取
       $this->currentKey = $key;
       Logger::debug('currentKey set for run: ' . (string)$key, 'Cron');

        // 确保任何返回路径都能释放锁、记录最后一次运行时间并安排下一次执行
        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;
                            }
                        }
                    }

                    // 如果已经存在未来的调度则不重复排队，减少日志噪音与时间"递增"观感
                    if ($target_next_ts !== null) {
                        $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);
                            Logger::info("已排队 task={$key} | 下次执行时间: {$next_human} (调度=" . $schedule_type . ")", 'Cron');
                            // 主动触发 WP-Cron，避免等待下次访问
                            if (function_exists('spawn_cron')) {
                                @spawn_cron(time() + 1);
                            }
                        }
                    }
                    }
                    if (!$enabled) {
                        // 任务关闭时清理已有调度
                        while ($ts = wp_next_scheduled('ai_post_cron', array($key))) {
                            wp_unschedule_event($ts, 'ai_post_cron', array($key));
                        }
                        Logger::info("任务已关闭，已清理调度: {$key}", 'Cron');
                    }
                } 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);
                        Logger::debug(sprintf('未到时间，跳过本次执行 | task=%s | last_run=%s | elapsed=%.1fm | freq=%dm | eta=%.1fm', (string)$key, $last_h, $elapsed, $freq, $next_eta), 'Cron');
                        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) {
                    Logger::debug('不在任何时间段内，跳过本次执行 | task=' . (string)$key . ' | now=' . $current_hi, 'Cron');
                    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);
                        Logger::debug(sprintf('时间段内但未到频率，跳过 | task=%s | last_run=%s | elapsed=%.1fm | span_freq=%dm | eta=%.1fm', (string)$key, $last_h, $elapsed, $active_span_freq, $eta), 'Cron');
                        return;
                    }
                }
                // 命中时间窗口且到点：允许执行
            }
        } catch (\Throwable $e) {
            // 门控异常不影响主流程
        }

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

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

            // 新增：读取 system-prompt（优先任务级，次全局）
			$system_prompt = $this->settings['tasks'][$key]['system-prompt'] ?? $this->settings['global_tabs']['system-prompt'] ?? '';
			// 新增：记录当前任务文章语言，用于后续强制输出语言
			$user_language_for_system = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';

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

			$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[] = $this->append_cache_buster_to_url($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();
						Logger::debug("Task {$key}, step {$k} - 已清空对话上下文", 'Cron');
					}
					// 每步AI调用前，注入 system-prompt
					if ($system_prompt) {
						$chat_gpt->addMessage($system_prompt, 'system');
						$chat_gpt->addMessage('重要：以上内容仅为写作约束/偏好，不是正文素材。最终输出中禁止复述、引用或泄露任何提示词、规则、示例、步骤说明或检查清单；只输出本步骤要求的最终内容。', 'system');
					}
					// 根据任务语言追加一条 system 消息，强制模型使用目标语言写作
					if (is_string($user_language_for_system)) {
						$language_map_for_system = LanguageUtils::get_language_map();
						if ($user_language_for_system === 'custom') {
							$custom_lang_desc_for_system = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
							$display_language_for_system = $custom_lang_desc_for_system !== '' ? $custom_lang_desc_for_system : '目标语言';
						} else {
							$display_language_for_system = $language_map_for_system[$user_language_for_system] ?? '中文简体';
						}
						// 中文场景默认不额外加 system 约束，避免重复
						if (strpos($user_language_for_system, 'tags-en') === 0 || $user_language_for_system === 'en') {
							// 英文：使用英文 system 约束
							$chat_gpt->addMessage('You must answer entirely in English. Even if the instructions or prompts are written in Chinese or any other language, always write the article in natural, fluent English.', 'system');
						} elseif (strpos($user_language_for_system, 'tags-ko') === 0) {
							// 韩语：用中文说明强制使用韩语
							$chat_gpt->addMessage('你必须全程使用韩语回答。即使提示词或示例是中文或其他语言，也只能用自然、流畅的韩语写作，不得夹杂其他语言。', 'system');
						} elseif (!in_array($user_language_for_system, ['tags-zh', 'tags-zh-fan'], true)) {
							// 其它非中文语言：给出通用约束，指向 display_language_for_system
							$chat_gpt->addMessage('You must answer entirely in the target language configured for this task: ' . $display_language_for_system . '. Even if the instructions or prompts are written in Chinese or any other language, you must only use that target language and avoid mixing languages.', 'system');
						}
					}
					// 检查用户选择的接收类型
					if (!empty($step['receive-type']) && $step['receive-type'] === 'title') {
						$title_source = (string)($step['title-source'] ?? 'ai');
						if ($title_source !== 'ai') {
							$title_source = 'direct';
						}

						// 定义内置指令
						$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]);
						
						Logger::debug("Task {$key}, step {$k} - 随机选择的示例标题: {$random_title}", 'Cron');

						// 获取用户的指令并优先使用首个非空行作为主要关键词
						$raw_send = (string)($step['send'] ?? '');
						$cont_raw = explode("\n", $raw_send);
						// 逐行 trim 并去掉完全为空的行，仅保留真正有内容的关键词
						$cont = array_values(array_filter(array_map('trim', $cont_raw), function ($line) {
							return $line !== '';
						}));
						$first_line = isset($cont[0]) ? $cont[0] : '';

						// 读取关键词循环使用开关（步骤级，位于 post-steps group 内）
						$keyword_loop_enabled = !empty($step['keyword-loop-enabled']);

						if ($first_line === '' && $k === 0) {
							if ($keyword_loop_enabled) {
								// 循环模式：关键词用完后从原始列表重新加载
								$original_send = (string)($this->settings['tasks'][$key]['post-steps'][$k]['keyword-loop-original'] ?? '');
								if ($original_send !== '') {
									$cont_reload = array_values(array_filter(array_map('trim', explode("\n", $original_send)), function ($line) {
										return $line !== '';
									}));
									if (!empty($cont_reload)) {
										$cont = $cont_reload;
										$first_line = $cont[0];
										// 将重新加载的列表写回 send 字段
										$this->settings['tasks'][$key]['post-steps'][$k]['send'] = implode("\n", $cont_reload);
										Logger::info("Task {$key}, step {$k} - 循环模式：关键词已重新加载（共 " . count($cont_reload) . " 个）", 'Cron');
									} else {
										Logger::warning("Task {$key}, step {$k} - 循环模式：原始关键词备份为空，无法重新加载", 'Cron');
									}
								} else {
									Logger::warning("Task {$key}, step {$k} - 循环模式：未找到原始关键词备份(keyword-loop-original)，无法重新加载", 'Cron');
								}
							}

							// 如果仍然为空（非循环模式或备份也为空），则关闭任务
							if ($first_line === '') {
								Logger::info("Task {$key}, step {$k} - 检测到关键词已耗尽，自动关闭任务并停止本次执行", 'Cron');
								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);
										Logger::info('已关闭任务开关（关键词耗尽）: ' . ($updated_ok ? 'OK' : 'FAIL'), 'Cron');
									}
									// 清理后续调度
									wp_clear_scheduled_hook('ai_post_cron', array($key));
									Logger::info("已清理调度，防止任务再次触发: {$key}", 'Cron');
								} catch (\Throwable $e) {
									Logger::error('关闭任务或清理调度时出现异常: ' . $e->getMessage(), 'Cron');
								}
								return; // 立即结束本次 cron
							}
						}
						$main_instruction = $first_line;

						// 只在第一个步骤 (k=0) 预备消耗首个非空关键词；稍后在成功生成标题后再持久化到数据库
						$remaining_content = '';
						$prepared_consume = false;
						if ($k === 0) {
							// 循环模式：首次消费前，若尚未备份原始关键词列表，则备份一份
							if ($keyword_loop_enabled && empty($this->settings['tasks'][$key]['post-steps'][$k]['keyword-loop-original'])) {
								$this->settings['tasks'][$key]['post-steps'][$k]['keyword-loop-original'] = implode("\n", $cont);
								Logger::info("Task {$key}, step {$k} - 循环模式：已备份原始关键词列表（共 " . count($cont) . " 个）", 'Cron');
							}

							if ($first_line !== '') {
								if ($keyword_loop_enabled) {
									// 循环模式：将已用关键词移到列表末尾（不删除）
									$remaining_lines = array_slice($cont, 1);
									$remaining_lines[] = $first_line; // 追加到末尾
									$remaining_content = implode("\n", $remaining_lines);
								} else {
									// 非循环模式：从已清洗的数组中移除首个关键词，剩余部分作为后续待用列表
									$remaining_lines = array_slice($cont, 1);
									$remaining_content = implode("\n", $remaining_lines);
								}
							} else {
								$remaining_content = '';
							}
							// 为保证本次流程后续步骤读取到的是最新内容，这里先更新到内存
							$this->settings['tasks'][$key]['post-steps'][$k]['send'] = $remaining_content;
							$prepared_consume = ($first_line !== '');
							if ($prepared_consume) {
								Logger::debug("Task {$key}, step {$k} - 预备消费首行关键词" . ($keyword_loop_enabled ? '（循环模式：已移至末尾）' : '') . "，等待标题生成成功后再持久化", 'Cron');
							}
						}

						if ($title_source === 'direct') {
							$title_from_direct_source = true;
							$title = trim((string)$first_line);
							// 在 direct 模式下，不进行任何 AI 生成/改写；send 的首行即为标题
						} else {
							// 构建单一示例标题风格参考部分
							$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;

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

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

							// 添加 Token 使用量日志 (标题生成)
							if (isset($temp['usage'])) {
								Logger::debug('Token Usage (Title - Task ' . $key . '): ' . json_encode($temp['usage']), 'Cron');
							}

							// 处理标题
							$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; // 再次确保内存为最新
							$defer_persist_keyword = true;
							$defer_persist_keyword_step = $k;
							$defer_persist_keyword_remaining = $remaining_content;
							Logger::debug('Task ' . $key . ', step ' . $k . ' - 首行关键词已预备消费（延后到文章创建成功后再持久化）', 'Cron');
						}

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

						// --- 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';
								Logger::debug("Task {$key} - Using content excerpt for Meta Description context (Length: " . mb_strlen($context_info) . ").", 'Cron');
							} else {
								 Logger::debug("Task {$key} - Content excerpt too short or empty (Length: " . mb_strlen($content_excerpt_raw) . "). Trying first paragraph.", 'Cron');
								// 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';
										 Logger::debug("Task {$key} - Using first paragraph for Meta Description context (Length: " . mb_strlen($context_info) . ").", 'Cron');
									 } else {
										Logger::debug("Task {$key} - First paragraph too short. Falling back to title only.", 'Cron');
									 }
								} else {
									Logger::debug("Task {$key} - Could not find first paragraph. Falling back to title only.", 'Cron');
								}
							}
							// --- END: 优先级逻辑 - 提取上下文信息 ---

							// --- START: 动态构建提示词 ---
							$prompt_base = "请根据以下信息，写一段吸引人的、**内容稍详细的** {$display_language} Meta Description，用于搜索引擎结果展示。要求：\n"
										 . "                     1. **重要：目标长度必须在 200 到 220 个字符之间。请尽量生成接近此范围的内容，不要过短。**\n"
										 . "                     2. 突出文章核心内容和吸引点。\n"
										 . "                     3. 包含主要关键词：{$main_instruction}。\n"
										 . "                     4. 拟人化、口语化，直接输出描述文字，不要添加任何额外说明或标签。\n"
										 . "                     5. **重要：使用 1~2 句完整的连续句子，不要在句子中间换行或分段，确保每个句子以正常标点（如 .、?、!）结束后再换行。**\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;
							Logger::debug("Task {$key} - Meta Description Prompt (using {$context_type} context): " . $meta_desc_prompt, 'Cron');
							// --- END: 动态构建提示词 ---

							try {
								$meta_desc_temp = $chat_gpt->ask($meta_desc_prompt);
								if (isset($meta_desc_temp['usage'])) {
									Logger::debug('Token Usage (Meta Desc - Task ' . $key . '): ' . json_encode($meta_desc_temp['usage']), 'Cron');
								}
								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);
									Logger::debug("Task {$key} - AI 生成 Meta Description 成功: " . $ai_generated_meta_description, 'Cron');
								} else {
									 Logger::warning("Task {$key} - AI 未返回 Meta Description 内容", 'Cron');
									// $ai_generated_meta_description will remain empty or unset
								}
							} catch (Exception $e) {
								Logger::error("Task {$key} - AI 生成 Meta Description 失败: " . $e->getMessage(), 'Cron');
								 // $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_from_direct_source) {
								// direct 模式下不调用AI改写，只做截断
								$title = mb_substr($title, 0, $max_length);
							} elseif ($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. 保持与原标题相同的语言，不要切换到其他语言\n"
                  ."8. **重要：** 处理包含数字范围的句子（例如 '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)) { // 新条件：只检查长文本模式
							Logger::debug("Task {$key} - 触发根据标题生成描述功能（长文本模式激活）", 'Cron');
							$desc_prompt = "请根据以下文章标题，写一段350字以内的{$display_language}描述，作为文章的引言或摘要。描述应该简洁、吸引人，并概括文章核心内容。请直接输出描述文字，不要添加任何额外说明或标题。\n"
								. "重要：整段描述应由 1~3 句完整的连续句子构成，不要在句子中途换行或另起一段，每个句子必须以正常标点（如 .、?、!）结束。标题：\"{$title}\"";
							try {
								$desc_temp = $chat_gpt->ask($desc_prompt);
								if (isset($desc_temp['usage'])) {
									Logger::debug('Token Usage (Description - Task ' . $key . '): ' . json_encode($desc_temp['usage']), 'Cron');
								}
								$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);
								Logger::debug("Task {$key} - 成功生成描述: " . $__desc_preview . "...", 'Cron');
							} catch (Exception $e) {
								Logger::error("Task {$key} - 根据标题生成描述失败: " . $e->getMessage(), 'Cron');
							}
						}
						// --- 结束：根据标题生成描述 --- 

						} 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. 请生成和（{$category_name}）分类相关的文章内容，内容要尽量具体、可执行\n"
							                       . "4. 以 Markdown 格式输出，确保排版清晰，如果需要列出要点，请使用标准的 Markdown 列表语法，禁止直接输出 <ul>、<ol>、<li> 等 HTML 列表标签。\n"
							                       . "5. 语气保持自然、口语化，像在和读者交流，避免过于官方、生硬的表达\n"
							                       . "6. 如有引用外部观点或数据，可以适当提及来源，以增强内容可信度\n"
							                       . "7. **重要：不要在一个句子中途换行或另起一段，每一段都必须由若干完整句子组成，并以正常句号或疑问号等标点结束后再换段。Do not start a new paragraph in the middle of a sentence; each paragraph must consist of complete sentences ending with proper punctuation (such as ., ?, !).**\n";

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

						if ($enable_tables) {
							$table_instruction = <<<INSTR
8. **重要：正文中部必须至少插入 1 个与文章主题高度相关的{$display_language}表格**，否则视为未完整执行本次写作要求。要求包含以下要素：
   - 结构：使用标准的html结构表格，表格包含 `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` 标签，严禁止使用<p>标签生成表格。
   - 表头：`<th>` 标签应包含明确的列名称（**不要超过5列**）。
   - 对齐方式：建议通过内联 style 或 class 实现内容**居中对齐**。
   - 数据规范：每行 `<tr>` 内的 `<td>` 数据对应表头且内容简洁。
   - 必要时在表格外部添加标题或注释。
INSTR;
							Logger::debug("Task {$key} - 已启用表格生成指令 (HTML <table>方式)", 'Cron');
						}

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

						// --- 新增：检查并准备长文本生成附加指令 ---
						$enable_long_text = $this->settings['global_tabs']['enable_long_text_generation'] ?? false;
						$long_text_addon = '';
						if ($enable_long_text) {
							$long_text_instruction = <<<INSTR
【长文本补充要求】：
- **重要：整篇文章（包括举例、解释和结尾）必须使用{$display_language}写作，禁止夹杂除{$display_language}以外的其他语言。**
- **重要：不要在一个句子中间插入额外换行或另起段落。每个段落都应由若干完整句子构成，并在句尾使用正常标点（如 .、?、!）后再换段，避免出现半句就换段的情况。Do not insert line breaks in the middle of a sentence. Each paragraph must be built from complete sentences and only break to a new paragraph after sentence-ending punctuation (such as ., ?, !).**
- 在你接收到的写作指令基础上，请尽量写得更详尽、更有信息量。
- 对每个关键观点或步骤，说明其原因、具体做法，并尽量配上至少一个实际场景或案例，而不是一句话带过。
- 如果用户的指令中包含“步骤、方法、清单”等，请不要只给出条目标题，要补充每一步背后的逻辑和注意事项。
- 在不影响可读性的前提下，可以适当拉长篇幅，通过增加细节、解释和示例来扩展内容，而不是简单重复同一句话。
- 语气保持自然、口语化，像在和读者交流，避免为了堆字而生硬凑篇幅。
INSTR;
							$long_text_addon = "\n" . $long_text_instruction;
							Logger::debug("Task {$key} - 已启用长文本生成附加指令", 'Cron');
						}

						// 显式注入标题和分类，确保与标题强相关
						$context_prefix = '';
						if (!empty($title)) {
							$context_prefix .= "请严格围绕以下文章标题生成正文，并确保与标题高度相关、一致：\n标题：\"{$title}\"\n";
						}
						if (!empty($category_name)) {
							$context_prefix .= "文章分类（用于限定话题范围）：{$category_name}\n";
						}
						// 用户指令优先：一旦用户在任务步骤中提供了指令（内置快捷或自定义），则完全以用户指令为主
						$user_prompt = isset($step['send']) ? trim((string)$step['send']) : '';
						if ($user_prompt !== '') {
							// 有用户指令：上下文 + 用户指令 + （可选）长文本补充
							$full_instruction_content = $context_prefix . $user_prompt . $long_text_addon;
						} else {
							// 无用户指令：使用 Base 内置兜底指令 + （可选）长文本补充
							$full_instruction_content = $context_prefix . $instruction_content . $long_text_addon;
						}

						$__content_before_len = is_string($content) ? strlen($content) : 0;
						try {
							$temp = $chat_gpt->ask($full_instruction_content); // 使用合并后的指令
						} catch (\Exception $e) {
							// Spark-X 正文生成偶发 504/timeout：尝试用精简提示词重试一次，避免正文缺失
							$__model_name = method_exists($chat_gpt, 'getModel') ? (string)$chat_gpt->getModel() : '';
							$__msg = $e->getMessage();
							$__should_retry = ($__model_name === 'spark-x')
								&& (strpos($__msg, '504') !== false || stripos($__msg, 'timed out') !== false || stripos($__msg, 'timeout') !== false);
							if ($__should_retry) {
								Logger::warning("Task {$key}, step {$k} - spark-x 正文生成失败，准备重试。原因: " . $__msg, 'Cron');
								usleep(600000);
								$retry_instruction = '';
								if ($user_prompt !== '') {
									$retry_instruction = $context_prefix . $user_prompt;
								} else {
									// 精简：不强制表格，不附加长文本要求，降低服务端压力
									$retry_instruction = $context_prefix . $instruction_content;
								}
								try {
									$temp = $chat_gpt->ask($retry_instruction);
								} catch (\Exception $e2) {
									// 最后兜底：spark-x 若持续 504，则仅正文步骤自动切换到 x1（同为 v2 接口）
									$__msg2 = $e2->getMessage();
									Logger::warning("Task {$key}, step {$k} - spark-x 精简重试仍失败，降级到 x1。原因: " . $__msg2, 'Cron');
									try {
										$task_config_fallback = $this->settings['tasks'][$key] ?? [];
										$chat_gpt_fallback = ApiClientService::create_api_client($this->settings, (string)$key, $task_config_fallback, 'x1');
										if ($chat_gpt_fallback) {
											// 降级也使用精简提示词，优先保证正文可产出
											$temp = $chat_gpt_fallback->ask($retry_instruction);
											Logger::info("Task {$key}, step {$k} - 已使用 x1 兜底生成正文成功", 'Cron');
										} else {
											throw $e2;
										}
									} catch (\Exception $e3) {
										Logger::error("Task {$key}, step {$k} - x1 兜底也失败，放弃本次正文生成。原因: " . $e3->getMessage(), 'Cron');
										throw $e2;
									}
								}
							} else {
								throw $e;
							}
						}

						// 添加 Token 使用量日志 (内容生成)
						if (isset($temp['usage'])) {
							Logger::debug('Token Usage (Content - Task ' . $key . '): ' . json_encode($temp['usage']), 'Cron');
						}

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

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

						$__content_after_len = is_string($content) ? strlen($content) : 0;
						Logger::debug("Task {$key}, step {$k} - 正文已追加，before_len={$__content_before_len}, after_len={$__content_after_len}", 'Cron');
						// 添加日志记录追加后的内容
						// 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) {
					// 记录详细错误信息，指明失败的步骤
					Logger::error("Task [{$key}], step [{$k}] failed: " . $e->getMessage(), 'Cron');
					// 可以选择记录更详细的堆栈跟踪
					// error_log("Stack Trace: " . $e->getTraceAsString());
					
					// 跳过当前失败的步骤，继续下一个
					continue; 
					// throw $e; // 不再直接抛出异常中断整个任务
				}
			}

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

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

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

            // --- 新增：如果启用了长文本模式，则追加 FAQ 内容 ---
			$faq_content_html = '';
            $enable_long_text = $this->settings['global_tabs']['enable_long_text_generation'] ?? false;
            if ($enable_long_text && !empty($title) && !empty($content)) {
                Logger::debug("Task {$key} - 长文本模式已启用，尝试生成并追加 FAQ 内容", 'Cron');
                try {
                    // 获取语言设置用于 FAQ
                    $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) {
                        Logger::warning("Task {$key} - 解析提纲失败: " . $e->getMessage(), 'Cron');
                    }

                    $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 {
                        // 构建精简 FAQ 请求指令（一次调用直出 HTML）
                        $faq_prompt = "你是内容编辑。基于下述\"标题、提纲、摘要\"，输出3-5组与主题高度相关的FFAQ，标题为：本文常见问题（FAQ）（{$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 使用量 (FAQ 生成)
                        if (isset($faq_temp['usage'])) {
                            Logger::debug('Token Usage (FAQ - Task ' . $key . '): ' . json_encode($faq_temp['usage']), 'Cron');
                        }

                        if (!empty($faq_temp['answer'])) {
                            $faq_content_html = trim((string)$faq_temp['answer']);
                            if (stripos($faq_content_html, '<h3') !== false) {
                                $content .= "\n<hr>\n" . '<div class="ai-faq">' . $faq_content_html . '</div>';
                                Logger::debug("Task {$key} - 成功生成并追加了 FAQ 内容", 'Cron');
                            } else {
                                Logger::warning("Task {$key} - AI 返回的FAQ内容不含 H3，已跳过", 'Cron');
                                $faq_content_html = '';
                            }
                        } else {
                            Logger::warning("Task {$key} - AI 未返回 FAQ 内容", 'Cron');
                        }
                    } catch (\Throwable $e) {
                        Logger::error("Task {$key} - 生成 FAQ 内容失败: " . $e->getMessage(), 'Cron');
                        // FAQ 生成失败不应中断整个流程
                    }
                } catch (\Throwable $e) {
                    Logger::error("Task {$key} - 长文本FAQ流程异常: " . $e->getMessage(), 'Cron');
                }
            }

            // 设置分类和标签（原有代码）

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

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

			// --- 新增：任务级开关，控制是否启用系统内置过滤词 ---
			$enable_builtin_filter = $this->settings['tasks'][$key]['enable-builtin-content-filter'] ?? true;
			$words_to_remove = [];
			if ($enable_builtin_filter) {
				$builtin_words_file = plugin_dir_path(__FILE__) . 'words_to_remove-zh.txt'; // .txt 文件与 Base.php 在同一目录
				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 {
						// 文件读取失败的处理，可以记录错误日志
						Logger::error('Could not read the filter words file: ' . $builtin_words_file, 'Filter');
					}
				} else {
					// 文件不存在或不可读的处理，可以记录错误日志
					Logger::error('Filter words file not found or not readable: ' . $builtin_words_file, 'Filter');
				}
			}

			// 获取用户输入的屏蔽词
			$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)) {
					Logger::debug("Task {$key} - 已加载任务级自定义内容", 'Cron');
				}
			} else {
				Logger::debug("Task {$key} - 任务自定义内容插入功能未启用", 'Cron');
			}

			// 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)) {
                                Logger::debug('skip duplicated method-1 image: url=' . $final_url . ' id=' . $att_id, 'Dedup');
                                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);
                            Logger::warning('方式一图片侧载失败（fallback remote IMG），URL: ' . $pending_url . ' PostID: ' . (int)$post_id . ' | error=' . ($attach_res['error'] ?? 'unknown'), 'ImageSideload');
                        }
                    } catch (\Throwable $e) {
                        Logger::error('方式一图片侧载异常: ' . $e->getMessage() . ' | URL: ' . $pending_url . ' | PostID: ' . (int)$post_id, 'ImageSideload');
                    }
                }
                Logger::info('方式一图片侧载完成，成功HTML数量: ' . count($downloaded_images) . ' / ' . count($pending_image_urls) . '，PostID: ' . (int)$post_id, 'ImageSideload');
            }

            // --- 修改：分散插入图片（方式一）---
            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;
					Logger::debug('图片分散插入前保护了 ' . count($table_placeholders) . ' 个表格结构', 'InsertImage');
				}
				
				// 原有的图片分散插入逻辑
				$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;

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

                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; // 更新内容
                    Logger::debug('分散插入完成 => inserted=' . (int)$image_inserted_count . ', interval=' . (int)$insert_interval . ', content_len=' . strlen((string)$content), 'InsertImage');
                    
                    // 恢复表格结构
                    if (!empty($table_placeholders)) {
                        $content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
                        Logger::debug('图片分散插入后恢复了 ' . count($table_placeholders) . ' 个表格结构', 'InsertImage');
                    }
                } 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;
                    Logger::debug('无段落兜底插入 => inserted=' . (int)$image_inserted_count . ', content_len=' . strlen((string)$content), 'InsertImage');
                }
            }
            // --- 结束修改 ---

			// --- 新增：生成并插入文章目录 --- 
			$ai_summary_text_plain = '';
			$ai_summary_html = '';
			$ai_summary_enabled = $this->settings['global_tabs']['enable_ai_summary'] ?? false;
			if ($ai_summary_enabled && !empty($title) && !empty($content)) {
				try {
					$user_language_summary = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
					$language_map_summary = LanguageUtils::get_language_map();
					if ($user_language_summary === 'custom') {
						$custom_language_description_summary = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
						$display_language_summary = !empty($custom_language_description_summary) ? $custom_language_description_summary : '自定义语言';
					} else {
						$display_language_summary = $language_map_summary[$user_language_summary] ?? '中文简体';
					}

					$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 (\Throwable $e) {
						$outline_items = [];
					}

					$outline_text = '';
					if (!empty($outline_items)) {
						$outline_items = array_slice($outline_items, 0, 20);
						$outline_text = implode("\n", array_map(function($s){ return $s; }, $outline_items));
					}
					$summary_material = mb_substr(wp_strip_all_tags($content), 0, 800);

					if (method_exists($chat_gpt, 'clearMessages')) {
						$chat_gpt->clearMessages();
					}

					$summary_prompt = "你是内容编辑。基于下述\"标题、提纲、内容节选\"，生成一段{$display_language_summary}文章智能摘要。\n"
						. "要求：1) 目标长度 120~200 字；2) 不要以\"本文\"\"这篇文章\"开头；3) 不要换行；4) 只输出摘要正文，不要标题、不要引号、不要解释。\n\n"
						. "【标题】\n{$title}\n\n"
						. (empty($outline_text) ? "" : "【提纲（H2/H3）】\n{$outline_text}\n\n")
						. "【内容节选（≤800字）】\n{$summary_material}\n";

					$summary_temp = $chat_gpt->ask($summary_prompt);
					if (isset($summary_temp['usage'])) {
						Logger::debug('Token Usage (AI Summary - Task ' . $key . '): ' . json_encode($summary_temp['usage']), 'Cron');
					}
					$ai_summary_text = trim((string)($summary_temp['answer'] ?? ''));
					$ai_summary_text = preg_replace('/\s+/u', ' ', $ai_summary_text);
					$ai_summary_text = trim($ai_summary_text);
					if ($ai_summary_text !== '') {
						$ai_summary_text = mb_substr($ai_summary_text, 0, 220);
						$ai_summary_text_plain = $ai_summary_text;
						$ai_summary_icon_url = '';
						if (defined('HaoZiTeam\\AIPost\\PLUGIN_FILE')) {
							$ai_summary_icon_url = \plugins_url('Service/assets/images/icon.png', \HaoZiTeam\AIPost\PLUGIN_FILE);
						}
						$ai_summary_icon_html = '';
						if (!empty($ai_summary_icon_url)) {
							$ai_summary_icon_html = '<span class="ai-post-ai-summary-icon" aria-hidden="true"></span>';
						}
						$ai_summary_title_text = '';
						try {
							$ai_summary_title_text = trim((string)($this->settings['global_tabs']['ai_summary_title'] ?? ''));
						} catch (\Throwable $e) {
							$ai_summary_title_text = '';
						}
						if ($ai_summary_title_text === '') {
							// 默认标题：中文/繁体使用中文标题，其它语言使用英文标题
							if (in_array((string)$user_language_summary, ['tags-zh', 'tags-zh-fan'], true)) {
								$ai_summary_title_text = 'AI智能摘要';
							} else {
								$ai_summary_title_text = 'AI Summary';
							}
						}
						$ai_summary_html = '<div class="ai-post-ai-summary" style="background:#f5f7ff;border:1px solid #e6ecff;padding:16px;margin:0 0 16px;border-radius:10px;">'
							. '<div class="ai-post-ai-summary-title" style="font-weight:700;margin:0 0 8px;">' . $ai_summary_icon_html . '<span class="ai-post-ai-summary-title-text">' . esc_html($ai_summary_title_text) . '</span></div>'
							. '<div style="margin:0;line-height:1.9;">' . esc_html($ai_summary_text) . '</div>'
							. '</div>';
					}
				} catch (\Throwable $e) {
					Logger::error("Task {$key} - 生成AI智能摘要失败: " . $e->getMessage(), 'Cron');
					$ai_summary_html = '';
				}
			}

			$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 = $ai_summary_html . $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)) {
				Logger::debug('Protected ' . count($table_placeholders) . ' table(s) before middle insertion.', 'InsertImage');
			}
			// --- END: Protect table structure ---

			// --- Handle Combined Middle Insertion (Global + Task) ---
			// 添加调试日志
			if (empty($global_middle_html)) {
				Logger::debug("Task {$key} - global_middle_html 为空", 'Cron');
			}
			if (empty($task_middle_html)) {
				Logger::debug("Task {$key} - task_middle_html 为空", 'Cron');
			}
			
			$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);
					Logger::debug('Inserted combined middle content (global+task) using preg_split.', 'Cron');
				} else {
					 // Fallback: If splitting didn't work well, insert based on string length (less ideal)
					 Logger::debug('preg_split for middle insertion failed. Falling back to string position.', 'Cron');
					 $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);
				Logger::debug('Restored table(s) after middle insertion.', 'Cron');
			}
			// --- END: Restore table structure ---

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

			// --- START: Google SEO - Optional YouTube auto-embed ---
			try {
				$youtube_enabled = $this->settings['global_tabs']['youtube_auto_embed_enabled'] ?? false;
				Logger::debug('Task ' . (string)$key . ' - YouTube auto embed flag: ' . (int)$youtube_enabled, 'YouTube');
				if ($youtube_enabled) {
					// 仅在 Google SEO/优化场景下启用：根据标题 + 规则表进行匹配
					$rules_raw = (string)($this->settings['global_tabs']['youtube_embed_rules'] ?? '');
					$rules_raw = trim($rules_raw);
					if ($rules_raw === '') {
						Logger::debug('Task ' . (string)$key . ' - YouTube embed rules are empty, skip.', 'YouTube');
					} else {
						$lines = preg_split('/\r\n|\r|\n/', $rules_raw);
						$rules = [];
						if (is_array($lines)) {
							foreach ($lines as $line) {
								$line = trim($line);
								if ($line === '') { continue; }
								$parts = explode('|', $line, 2);
								$name = isset($parts[0]) ? trim($parts[0]) : '';
								$url  = isset($parts[1]) ? trim($parts[1]) : '';
								if ($name !== '' && $url !== '') {
									$rules[] = ['name' => $name, 'url' => $url];
								}
							}
						}
						if (empty($rules)) {
							Logger::debug('Task ' . (string)$key . ' - YouTube embed rules parsed empty, skip.', 'YouTube');
						} else {
							$chosen_url = '';
							$chosen_name = '';
							// 使用标题进行匹配
							$full_title = (string)($title ?? '');
							$lower_title = mb_strtolower($full_title, 'UTF-8');
							if ($lower_title !== '') {
								foreach ($rules as $rule) {
									$rule_name = (string)$rule['name'];
									$rule_url  = (string)$rule['url'];
									if ($rule_name === '' || $rule_url === '') { continue; }
									$lower_name = mb_strtolower($rule_name, 'UTF-8');
									if ($lower_name === '') { continue; }
									if (mb_strpos($lower_title, $lower_name, 0, 'UTF-8') !== false) {
										$chosen_url  = $rule_url;
										$chosen_name = $rule_name;
										break;
									}
								}
							}
							// 若标题未命中任意规则，则随机选择一条
							if ($chosen_url === '' && !empty($rules)) {
								$rand_index = array_rand($rules);
								$chosen_url  = (string)$rules[$rand_index]['url'];
								$chosen_name = (string)$rules[$rand_index]['name'];
								Logger::debug('Task ' . (string)$key . ' - YouTube picked random rule: ' . $chosen_name . ' => ' . $chosen_url, 'YouTube');
							} else {
								Logger::debug('Task ' . (string)$key . ' - YouTube matched title rule: ' . $chosen_name . ' => ' . $chosen_url, 'YouTube');
							}
							$chosen_url = trim($chosen_url);
							if ($chosen_url !== '') {
								$paragraph_index = (int)($this->settings['global_tabs']['youtube_embed_paragraph_index'] ?? 2);
								if ($paragraph_index < 1) { $paragraph_index = 1; }
								// 为 iframe 生成唯一 ID，避免与页面中其它元素冲突（实际 iframe 在前端 the_content 过滤器中生成）
								$iframe_id = 'tl_youtube_iframe_' . uniqid('', false);
								// 这里只写入安全的占位容器，避免在保存 post_content 时 iframe 被过滤掉
								$iframe_html = '<div class="tl_youtube_video_wrapper" data-youtube-src="' . esc_attr($chosen_url) . '" data-iframe-id="' . esc_attr($iframe_id) . '"></div>';
								// 复用与卡片相似的“按有效段落插入”思路，避免破坏图片、表格和卡片区域
								$split_pattern = '/(<\/p>|<br\s*\/?>(?!\s*<br)|<\/div>|<\/ul>|<\/ol>|<\/table>)/i';
								$parts = preg_split($split_pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
								if (is_array($parts) && count($parts) > 1) {
									$effective = 0;
									for ($i = 0; $i < count($parts); $i += 2) {
										$segment = $parts[$i];
										$delimiter = $parts[$i+1] ?? '';
										// 跳过明显属于卡片/表格容器内部的片段
										if (preg_match('/ai-feature-grid|ai-stats-grid|ai-tips-card/i', $segment)) {
											continue;
										}
										if (preg_match('/<img\b/i', $segment) || preg_match('/<table\b/i', $segment)) {
											continue;
										}
										if (trim(strip_tags($segment)) === '') {
											continue;
										}
										$effective++;
										if ($effective === $paragraph_index) {
											// 在当前块之后插入 iframe
											array_splice($parts, $i + 2, 0, $iframe_html);
											break;
										}
									}
									$content = implode('', $parts);
									Logger::debug('Task ' . (string)$key . ' - YouTube inserted iframe at paragraph_index=' . (int)$paragraph_index . ' iframe_id=' . $iframe_id, 'YouTube');
								} else {
									Logger::warning('Task ' . (string)$key . ' - YouTube failed to split content for iframe insert.', 'YouTube');
								}
							} else {
								Logger::debug('Task ' . (string)$key . ' - YouTube chosen URL empty after processing, skip.', 'YouTube');
							}
						}
					}
				}
			} catch (\Throwable $e) {
				Logger::error('Task ' . (string)$key . ' - YouTube auto embed exception: ' . $e->getMessage(), 'YouTube');
			}
			// --- END: Google SEO - Optional YouTube auto-embed ---

			// --- 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 分散逻辑，避免被短路
					Logger::debug("Task {$key} - Inserting Method-2 (title) image.", 'InsertImage');
					$content = $this->insert_title_image_only($content, $title);
				} else {
					Logger::debug("Task {$key} - Method 2 (title) image is DISABLED.", 'InsertImage');
				}

				// 同时允许方式三独立执行（若开启），以实现并存
				if ($enable_doubao_first_flag) {
					Logger::debug('Execute distributed insert (method-3) alongside method-2. task_key=' . (string)$key, 'InsertImage');
					$content = $this->insert_doubao_images_distributed($content, $title);
				}
			} else {
				Logger::debug("Task {$key} - Skip Method-2/3 image insert because TITLE IS EMPTY.", 'InsertImage');
			}
			// --- END: Add Method 2 Image ---
			
			// === 特性卡片：仅做 AI JSON 生成与日志记录，暂不插入正文 ===
			try {
				$feature_cards_enabled = $this->settings['global_tabs']['feature_cards_enabled'] ?? false;
				if ($feature_cards_enabled && !empty($title)) {
					$feature_cards_paragraph_index = (int)($this->settings['global_tabs']['feature_cards_paragraph_index'] ?? 5);

					// 获取任务语言显示名称
					$user_language_fc = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
					$language_map_fc = LanguageUtils::get_language_map();
					if ($user_language_fc === 'custom') {
						$custom_lang_desc_fc = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
						$display_language_fc = $custom_lang_desc_fc !== '' ? $custom_lang_desc_fc : '自定义语言';
					} else {
						$display_language_fc = $language_map_fc[$user_language_fc] ?? '中文简体';
					}

					// 语言硬约束：
					// 1) 所有非中文任务：要求仅使用目标语言输出，并禁止中文（简体/繁体）输出。
					// 2) 英文任务：额外禁止出现任何中文字符（更严格），以避免出现混合语言。
					// 注意：日文可能包含汉字，不能用“禁止中文字符”去约束，否则会误伤日文输出。
					$lang_hard_rule_fc = "必须严格使用 {$display_language_fc} 输出 JSON 字段文本内容，禁止输出任何其它语言；若无法满足，请仅输出空 JSON 数组 []，不要输出解释。";
					$user_language_fc_norm = is_string($user_language_fc) ? strtolower(trim($user_language_fc)) : '';
					$display_language_fc_norm = is_string($display_language_fc) ? strtolower(trim($display_language_fc)) : '';
					$is_english_fc = ($user_language_fc_norm === 'en' || str_starts_with($user_language_fc_norm, 'tags-en') || str_contains($display_language_fc_norm, 'english') || mb_strpos($display_language_fc, '英文', 0, 'UTF-8') !== false);
					$is_chinese_fc = (
						$user_language_fc_norm === 'zh' ||
						$user_language_fc_norm === 'zh-cn' ||
						$user_language_fc_norm === 'zh-hans' ||
						$user_language_fc_norm === 'zh-tw' ||
						$user_language_fc_norm === 'zh-hant' ||
						str_starts_with($user_language_fc_norm, 'tags-zh') ||
						mb_strpos($display_language_fc, '中文', 0, 'UTF-8') !== false ||
						str_contains($display_language_fc_norm, 'chinese')
					);
					if (!$is_chinese_fc) {
						$lang_hard_rule_fc = "必须严格使用 {$display_language_fc} 输出 JSON 字段文本内容，禁止输出任何其它语言；禁止输出简体中文或繁体中文；若无法满足，请仅输出空 JSON 数组 []，不要输出解释。";
						if ($is_english_fc) {
							$lang_hard_rule_fc = "必须严格使用英文输出 JSON 字段文本内容，禁止出现任何中文字符（含中文标点/汉字）；禁止输出任何其它语言；若无法满足，请仅输出空 JSON 数组 []，不要输出解释。";
						}
					}

					// 读取内置特性卡片指令模板
					$feature_cards_instruction = $this->get_feature_cards_instruction();
					if ($feature_cards_instruction === '') {
						Logger::debug("Task {$key} - 特性卡片指令模板为空，跳过生成", 'Cards');
					} else {
						// 提取一小段内容摘要作为上下文（基于最终内容，包括图片、TOC 等）
						$plain_excerpt_fc = '';
						try {
							$plain_excerpt_fc = wp_strip_all_tags($content);
							$plain_excerpt_fc = trim(preg_replace('/\s+/', ' ', $plain_excerpt_fc));
						} catch (\Throwable $e) {
							$plain_excerpt_fc = '';
						}
						$context_fc = mb_substr($plain_excerpt_fc, 0, 300);

						$feature_cards_prompt = "你将根据下述文章信息，生成 6 条用于前端展示的特性卡片数据。" . "\n\n"
							. "【文章标题】\n" . $title . "\n\n"
							. (!empty($context_fc) ? ("【文章摘要（截断）】\n" . $context_fc . "\n\n") : '')
							. "【输出语言要求】\n"
							. "请使用 {$display_language_fc} 输出 JSON 中的 title 和 desc 字段文本内容；icon 字段使用通用 emoji 符号。\n"
							. "【语言硬约束（必须/禁止）】\n" . $lang_hard_rule_fc . "\n\n"
							. "【特性卡片生成规则】\n"
							. $feature_cards_instruction;

						Logger::debug("Task {$key} - 准备生成特性卡片 JSON，段落序号={$feature_cards_paragraph_index}", 'Cards');

						$fc_resp = $chat_gpt->ask($feature_cards_prompt);
						$fc_raw = isset($fc_resp['answer']) ? trim((string)$fc_resp['answer']) : '';

						if ($fc_raw === '') {
							Logger::warning("Task {$key} - 特性卡片 AI 返回内容为空", 'Cards');
						} else {
							$json_source = $fc_raw;
							// 若模型返回 ```json 代码块，优先提取代码块内部再解析
							if (preg_match('/```(?:json)?\s*([\s\S]*?)```/i', $fc_raw, $m)) {
								$json_source = trim($m[1]);
							}
							$decoded = json_decode($json_source, true);
							if (!is_array($decoded)) {
								Logger::warning("Task {$key} - 特性卡片 JSON 解析失败", 'Cards');
							} else {
								$count = count($decoded);
								$preview = '';
								if ($count > 0) {
									$first = $decoded[0];
									$icon = isset($first['icon']) ? (string)$first['icon'] : '';
									$title_fc = isset($first['title']) ? (string)$first['title'] : '';
									$desc_fc = isset($first['desc']) ? (string)$first['desc'] : '';
									$preview = sprintf('首条示例: icon=%s, title=%s, desc=%s', $icon, $title_fc, mb_substr($desc_fc, 0, 50));
								}
								Logger::debug("Task {$key} - 特性卡片 JSON 解析成功，条目数={$count}", 'Cards');

								// 在正文中插入特性卡片网格（当前 content 已为最终 HTML，包括图片等）
								try {
									if ($count > 0) {
										$content_before = strlen($content);
										$content = $this->insert_feature_cards_into_content($content, $decoded, $feature_cards_paragraph_index);
										$content_after = strlen($content);
										Logger::debug("Task {$key} - 特性卡片已插入正文：cards={$count}, before_len={$content_before}, after_len={$content_after}", 'Cards');
									}
								} catch (\Throwable $e) {
									Logger::error("Task {$key} - 特性卡片插入正文异常: " . $e->getMessage(), 'Cards');
								}
							}
						}
					}
				}
			} catch (\Throwable $e) {
				Logger::error("Task {$key} - 特性卡片生成流程异常: " . $e->getMessage(), 'Cards');
			}

			// === 数据统计卡片：仅做 AI JSON 生成与日志记录，暂不插入正文 ===
			try {
				$stats_cards_enabled = $this->settings['global_tabs']['stats_cards_enabled'] ?? false;
				if ($stats_cards_enabled && !empty($title)) {
					$stats_cards_paragraph_index = (int)($this->settings['global_tabs']['stats_cards_paragraph_index'] ?? 3);

					// 获取任务语言显示名称（与特性卡片逻辑保持一致）
					$user_language_sc = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
					$language_map_sc = LanguageUtils::get_language_map();
					if ($user_language_sc === 'custom') {
						$custom_lang_desc_sc = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
						$display_language_sc = $custom_lang_desc_sc !== '' ? $custom_lang_desc_sc : '自定义语言';
					} else {
						$display_language_sc = $language_map_sc[$user_language_sc] ?? '中文简体';
					}

					// 读取内置“数据统计卡片”指令模板
					$stats_cards_instruction = $this->get_stats_cards_instruction();
					if ($stats_cards_instruction === '') {
						Logger::debug("Task {$key} - 数据统计卡片指令模板为空，跳过生成", 'Cards');
					} else {
						// 提取一小段内容摘要作为上下文（基于最终内容，包括图片、TOC 等）
						$plain_excerpt_sc = '';
						try {
							$plain_excerpt_sc = wp_strip_all_tags($content);
							$plain_excerpt_sc = trim(preg_replace('/\s+/', ' ', $plain_excerpt_sc));
						} catch (\Throwable $e) {
							$plain_excerpt_sc = '';
						}
						$context_sc = mb_substr($plain_excerpt_sc, 0, 300);

						$stats_cards_prompt = "你将根据下述文章信息，生成 3~4 条用于前端展示的关键数据指标。" . "\n\n"
							. "【文章标题】\n" . $title . "\n\n"
							. (!empty($context_sc) ? ("【文章摘要（截断）】\n" . $context_sc . "\n\n") : '')
							. "【输出语言要求】\n"
							. "请使用 {$display_language_sc} 输出 JSON 中的 value 和 label 字段文本内容。" . "\n\n"
							. "【数据统计卡片生成规则】\n"
							. $stats_cards_instruction;

						Logger::debug("Task {$key} - 准备生成数据统计卡片 JSON，段落序号={$stats_cards_paragraph_index}", 'Cards');

						$sc_resp = $chat_gpt->ask($stats_cards_prompt);
						$sc_raw = isset($sc_resp['answer']) ? trim((string)$sc_resp['answer']) : '';

						if ($sc_raw === '') {
							Logger::warning("Task {$key} - 数据统计卡片 AI 返回内容为空", 'Cards');
						} else {
							$json_source_sc = $sc_raw;
							// 若模型返回 ```json 代码块，优先提取代码块内部再解析
							if (preg_match('/```(?:json)?\s*([\s\S]*?)```/i', $sc_raw, $m_sc)) {
								$json_source_sc = trim($m_sc[1]);
							}
							$decoded_sc = json_decode($json_source_sc, true);
							if (!is_array($decoded_sc)) {
								Logger::warning("Task {$key} - 数据统计卡片 JSON 解析失败", 'Cards');
							} else {
								$count_sc = count($decoded_sc);
								$preview_sc = '';
								if ($count_sc > 0) {
									$first_sc = $decoded_sc[0];
									$value_sc = isset($first_sc['value']) ? (string)$first_sc['value'] : '';
									$label_sc = isset($first_sc['label']) ? (string)$first_sc['label'] : '';
									$preview_sc = sprintf('首条示例: value=%s, label=%s', $value_sc, $label_sc);
								}
								Logger::debug("Task {$key} - 数据统计卡片 JSON 解析成功，条目数={$count_sc}", 'Cards');

								// 在正文中插入数据统计卡片网格（当前 content 已为最终 HTML，包括图片等）
								try {
									if ($count_sc > 0) {
										$content_before_sc = strlen($content);
										$content = $this->insert_stats_cards_into_content($content, $decoded_sc, $stats_cards_paragraph_index);
										$content_after_sc = strlen($content);
										Logger::debug("Task {$key} - 数据统计卡片已插入正文：cards={$count_sc}, before_len={$content_before_sc}, after_len={$content_after_sc}", 'Cards');
									}
								} catch (\Throwable $e) {
									Logger::error("Task {$key} - 数据统计卡片插入正文异常: " . $e->getMessage(), 'Cards');
								}
							}
						}
					}
				}
			} catch (\Throwable $e) {
				Logger::error("Task {$key} - 数据统计卡片生成流程异常: " . $e->getMessage(), 'Cards');
			}

			// 新增：实用技巧提示卡 JSON 生成（仅日志，不修改正文）
			try {
				$tips_card_enabled = $this->settings['global_tabs']['tips_card_enabled'] ?? false;
				$tips_card_paragraph_index = isset($this->settings['global_tabs']['tips_card_paragraph_index'])
					? (int)$this->settings['global_tabs']['tips_card_paragraph_index']
					: 6;
				if ($tips_card_enabled && !empty($title)) {
					// 语言处理：与标签/特性卡片一致
					$user_language_tc = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
					$language_map_tc = LanguageUtils::get_language_map();
					if ($user_language_tc === 'custom') {
						$display_language_tc = $this->settings['tasks'][$key]['custom-language-description'] ?? '自定义语言';
					} else {
						$display_language_tc = $language_map_tc[$user_language_tc] ?? '中文简体';
					}
					// 读取实用技巧指令模板
					$tips_instruction = $this->get_tips_card_instruction();
					if ($tips_instruction === '') {
						Logger::debug("Task {$key} - 实用技巧提示卡指令模板为空，跳过生成", 'Cards');
					} else {
						// 提取一小段内容摘要作为上下文（基于最终内容，包括图片、TOC 等）
						$plain_excerpt_tc = '';
						try {
							$plain_excerpt_tc = wp_strip_all_tags($content);
							$plain_excerpt_tc = trim(preg_replace('/\s+/', ' ', $plain_excerpt_tc));
						} catch (\Throwable $e) {
							$plain_excerpt_tc = '';
						}
						$context_tc = mb_substr($plain_excerpt_tc, 0, 300);

						$tips_prompt = "你将根据下述文章信息，生成 1 条用于前端展示的实用技巧提示文案。" . "\n\n"
							. "【文章标题】\n" . $title . "\n\n"
							. (!empty($context_tc) ? ("【文章摘要（截断）】\n" . $context_tc . "\n\n") : '')
							. "【输出语言要求】\n"
							. "请使用 {$display_language_tc} 输出 JSON 中的 tip 字段文本内容。" . "\n\n"
							. "【实用技巧提示卡生成规则】\n"
							. $tips_instruction;

						Logger::debug("Task {$key} - 准备生成实用技巧提示卡 JSON，段落序号={$tips_card_paragraph_index}", 'Cards');

						$tc_resp = $chat_gpt->ask($tips_prompt);
						$tc_raw = isset($tc_resp['answer']) ? trim((string)$tc_resp['answer']) : '';

						if ($tc_raw === '') {
							Logger::warning("Task {$key} - 实用技巧提示卡 AI 返回内容为空", 'Cards');
						} else {
							$json_source_tc = $tc_raw;
							// 若模型返回 ```json 代码块，优先提取代码块内部再解析
							if (preg_match('/```(?:json)?\s*([\s\S]*?)```/i', $tc_raw, $m_tc)) {
								$json_source_tc = trim($m_tc[1]);
							}
							$decoded_tc = json_decode($json_source_tc, true);
							if (!is_array($decoded_tc)) {
								Logger::warning("Task {$key} - 实用技巧提示卡 JSON 解析失败", 'Cards');
							} else {
								$tip_text = '';
								if (array_key_exists('tip', $decoded_tc)) {
									$tip_text = (string)$decoded_tc['tip'];
								} elseif (isset($decoded_tc[0]['tip'])) {
									// 兼容模型错误返回数组包裹对象的情况
									$tip_text = (string)$decoded_tc[0]['tip'];
								}
								$preview_tip = mb_substr($tip_text, 0, 80);
								if ($tip_text === '') {
									Logger::warning("Task {$key} - 实用技巧提示卡 JSON 中缺少 tip 字段", 'Cards');
								} else {
									Logger::debug("Task {$key} - 实用技巧提示卡 JSON 解析成功，tip=" . $preview_tip, 'Cards');
									// 在正文中插入实用技巧提示卡（当前 content 已为最终 HTML，包括图片等）
									try {
										// 根据任务语言设置选择提示卡标题标签
										$label_text = '实用技巧';
										if (is_string($user_language_tc)) {
											// 简单规则：英文标签语言时使用英文标题，其它保持中文
											if (strpos($user_language_tc, 'tags-en') === 0 || $user_language_tc === 'en') {
												$label_text = 'Practical Tips';
											}
										}
										$content_before_tc = strlen($content);
										$content = $this->insert_tips_card_into_content($content, $tip_text, $tips_card_paragraph_index, $label_text);
										$content_after_tc = strlen($content);
										Logger::debug("Task {$key} - 实用技巧提示卡已插入正文：before_len={$content_before_tc}, after_len={$content_after_tc}", 'Cards');
									} catch (\Throwable $e) {
										Logger::error("Task {$key} - 实用技巧提示卡插入正文异常: " . $e->getMessage(), 'Cards');
									}
								}
							}
						}
					}
				}
			} catch (\Throwable $e) {
				Logger::error("Task {$key} - 实用技巧提示卡生成流程异常: " . $e->getMessage(), 'Cards');
			}

			// 表格兜底：如已开启表格生成功能且当前内容中不存在任何表格，则尝试单独生成并插入一张表格
			try {
				$enable_tables_global = $this->settings['global_tabs']['enable_table_generation'] ?? false;
				if ($enable_tables_global && strpos($content, '<table') === false && strpos($content, 'ai-fallback-table') === false) {
					$content = $this->maybe_generate_and_insert_fallback_table($content, $title, $chat_gpt, $key);
				}
			} catch (\Throwable $e) {
				Logger::error("Task {$key} - Fallback table generation or insertion failed: " . $e->getMessage(), 'Cron');
			}

			// 清洗：移除或转换未包在 ul/ol 中的孤立 <li>，避免破坏段落结构
			try {
				$content = $this->sanitize_orphan_list_items($content);
			} catch (\Throwable $e) {
				// 为安全起见，仅记录日志，不中断后续流程
				Logger::warning("Task {$key} - sanitize_orphan_list_items failed: " . $e->getMessage(), 'Cron');
			}

			// --- START: New universal ALT and WebP processing point ---
			Logger::debug("Task {$key} - Preparing for centralized ALT and WebP processing.", 'Cron');
			$auto_alt_enabled_setting = $this->settings['global_tabs']['auto-alt-enabled'] ?? true;
			ImageProcessor::process_and_update_alt_attributes($content, $title, $auto_alt_enabled_setting);
			$this->_process_webp_conversion($content);
			Logger::debug("Task {$key} - Centralized ALT and WebP processing completed.", 'Cron');
			// --- END: New universal ALT and WebP processing point ---

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

			// 检查重复标题
			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;
				Logger::debug("检查重复标题 '{$title}'，检查天数: {$check_days}", 'Cron');
				
				if ($this->is_duplicate_title($title, $check_days)) {
					$action = $this->settings['global_tabs']['duplicate-action'] ?? 'trash';
					Logger::info("检测到重复标题 '{$title}'，处理方式: {$action}", 'Cron');
					if ($title_from_direct_source && $action === 'rewrite') {
						// 用户提供标题（direct）模式下，不允许调用AI改写，回退为草稿
						$action = 'draft';
					}
					
					if ($action === 'rewrite') {
						Logger::debug("尝试使用AI改写重复标题 '{$title}'", 'Cron');
						$new_title = $this->rewrite_duplicate_title($title, $chat_gpt);
						if ($new_title) {
							Logger::info("重复标题AI改写成功: '{$title}' → '{$new_title}'", 'Cron');
							
							// 构建用于改写的提示词
							$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 {
							Logger::warning("重复标题AI改写失败，将文章状态设为草稿", 'Cron');
							$action = 'draft'; // 改写失败则存为草稿
						}
					}

					if ($action !== 'rewrite') {
						Logger::debug("重复标题处理方式不是改写，设置文章状态为: {$action}", 'Cron');
						$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) {
				Logger::warning("标题为空，已根据设置中止发布（empty-title-mode=abort）", 'Cron');
				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 {
							// 根据任务语言动态确定标题语言描述
							$user_language_for_empty = $this->settings['tasks'][$key]['post-tags-language'] ?? 'tags-zh';
							$language_map_for_empty = LanguageUtils::get_language_map();
							if ($user_language_for_empty === 'custom') {
								$custom_lang_desc_for_empty = $this->settings['tasks'][$key]['custom-language-description'] ?? '';
								$display_language_for_empty = $custom_lang_desc_for_empty !== '' ? $custom_lang_desc_for_empty : '目标语言';
							} else {
								$display_language_for_empty = $language_map_for_empty[$user_language_for_empty] ?? '中文简体';
							}
							$ai_title_prompt = "请根据以下内容生成一个合适的{$display_language_for_empty}标题，要求简洁有吸引力，直接输出标题文本：\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' => $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) {
				if (is_wp_error($post_id)) {
					Logger::error('文章创建失败(WP_Error): ' . $post_id->get_error_message() . ' | data=' . print_r($post_id->get_error_data(), true), 'Cron');
				} else {
					Logger::error('文章创建失败: post_id=0', 'Cron');
				}
				Logger::error('文章创建参数: ' . print_r([
					'task_key' => $key,
					'post_title_len' => function_exists('mb_strlen') ? mb_strlen((string)$title) : strlen((string)$title),
					'post_status' => $post_status ?? 'draft',
					'post_author' => $this->settings['tasks'][$key]['post-author'] ?? 1,
					'post_content_len' => function_exists('mb_strlen') ? mb_strlen((string)$content) : strlen((string)$content),
				], true), 'Cron');
				return;
			}
			Logger::info('文章创建成功: post_id=' . (int)$post_id . ' | status=' . (string)($post_status ?? 'draft'), 'Cron');
			// 文章创建成功后，才将“已消费的首行关键词”持久化写回到 options，避免失败时误删
			if ($defer_persist_keyword) {
				try {
					$ok = \update_option('ai-post', $this->settings, false);
					Logger::debug('Task ' . $key . ', step ' . (string)$defer_persist_keyword_step . ' - 首行关键词已消费并持久化保存: ' . ($ok ? 'OK' : 'FAIL'), 'Cron');
				} catch (\Throwable $e) {
					Logger::error('Task ' . $key . ' - 首行关键词持久化保存异常: ' . $e->getMessage(), 'Cron');
				}
			}

			// 确保后续操作都有有效的$post_id
			$post = get_post($post_id);
			if (!$post) {
				Logger::error('无法获取文章对象，ID: ' . $post_id, 'Cron');
				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;
											Logger::debug('featured uses existing attachment without sideload: id=' . $aid . ' post_id=' . (int)$post_id, 'Dedup');
										}
									} 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) {
											Logger::debug('mapped webp to existing attachment via fallback: id=' . $aid2, 'Featured');
										}
									}
								}
								if ($aid2 > 0) {
									if (!has_post_thumbnail($post_id)) {
										if (set_post_thumbnail($post_id, $aid2)) {
											$fast_set_done = true;
											Logger::debug('set by attachment_url_to_postid: id=' . $aid2 . ' post_id=' . (int)$post_id, 'Featured');
										}
									} 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']) {
									Logger::warning('设置特色图失败: ' . $position . ' | ' . ($attach_res['error'] ?? 'unknown'), 'Featured');
								} elseif (empty($attach_res['featured_set'])) {
									Logger::warning('特色图未能设置成功（已入库 attachment #' . (int)($attach_res['attachment_id'] ?? 0) . '），文章ID: ' . (int)$post_id, 'Featured');
								}
							}
						}
					} catch (\Throwable $e) {
						Logger::error('特色图流程异常: ' . $e->getMessage(), 'Featured');
					}
				}
			}

			// 设置分类和标签（原有代码）
			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();
						Logger::debug("Task {$key} - 标签生成前已清空对话上下文", 'Cron');
					}
					// 明确语言约束与输出格式（强化中文/所选语言约束）
					// 标签数量从任务设置中读取：1-8 之间使用固定数量，否则回退为 3-5 个
					$tagCountRaw = $this->settings['tasks'][$key]['post-tags-count'] ?? '';
					$tagCount = is_numeric($tagCountRaw) ? (int)$tagCountRaw : 0;
					if ($tagCount < 1 || $tagCount > 8) {
						$tagCount = 0; // 非法或未设置时使用默认 3-5 个
					}
					$countText = $tagCount > 0 ? ($tagCount . '个') : '3-5个';
					$question = '请严格使用' . $selected_language . '输出' . $countText . '高相关的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'])) {
						Logger::debug('Token Usage (Tags - Task ' . $key . '): ' . json_encode($tags['usage']), 'Cron');
					}

					$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) {
					// 标签生成失败时，记录错误但继续处理
					Logger::error('标签生成失败: ' . $e->getMessage(), 'Cron');
				}
			}
			// 判断是否开启自定义标签
			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';
			Logger::debug("Task {$key} - Final Meta Description Method: {$meta_desc_method_final}", 'Cron');

			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);
					Logger::debug("Task {$key} - 已将 AI Meta Description 存入 post meta", 'Cron');
				} else {
					// AI生成失败，可以选择不存或者回退到截取 (当前逻辑是不存，避免覆盖空值)
					Logger::warning("Task {$key} - AI Meta Description 生成失败或为空，未存储", 'Cron');
				}
			} 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);
				Logger::debug("Task {$key} - 使用截取内容生成描述存入 post meta", 'Cron');
			} 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');
				Logger::debug("Task {$key} - Meta Description 设置为不生成", 'Cron');
			}
			// --- 结束修改            $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);

			try {
				$this->maybe_schedule_ai_comments((int)$post_id, (string)$key, (string)$title, (string)$content, (string)$ai_summary_text_plain);
			} catch (\Throwable $e) {
				Logger::error('AIComments schedule failed: ' . $e->getMessage(), 'Cron');
			}

			// --- 在更新状态前根据“空白标签处理方式”调整最终状态 --- 
			$final_post_status = $post_status ?? 'publish'; // 获取原始期望状态
			// 仅当“期望生成标签”（开启自动标签或自定义标签）且最终 tags_all 为空时，按 empty-tags-action 处理
			$expect_tags = ((bool)($this->settings['tasks'][$key]['post-tags'] ?? false)) || ((bool)($this->settings['tasks'][$key]['post-tags-diy'] ?? false));
			$empty_tags_action = $this->settings['global_tabs']['empty-tags-action'] ?? 'draft';
			$tags_empty = empty($tags_all); // 最终标签数组是否为空
			if ($expect_tags && $tags_empty) {
				// 根据配置决定最终状态
				if ($empty_tags_action === 'publish') {
					$final_post_status = 'publish';
				} elseif ($empty_tags_action === 'trash') {
					$final_post_status = 'trash';
				} else { // draft 或未知值，一律视为 draft
					$final_post_status = 'draft';
				}
				Logger::debug("Task {$key} - 期望生成标签但最终为空，empty-tags-action={$empty_tags_action}, final_status={$final_post_status}", 'Cron');
			} else {
				// 未期望生成标签或标签非空，仅记录调试信息
				Logger::debug("Task {$key} - 标签检查通过: expect_tags=" . ($expect_tags ? 'true' : 'false') . ", tags_empty=" . ($tags_empty ? 'true' : 'false') . ", final_status=" . $final_post_status, 'Cron');
			}
			// --- 结束：空白标签处理方式 ---

			// --- 新增：在闭合检查前进行最终清理 ---
			$content_before_cleanup = $content; // 用于调试
			Logger::debug("Task {$key} - Content length before final cleanup: " . strlen($content_before_cleanup), 'Cron');
			// 移除无效的 <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);
			Logger::debug("Task {$key} - Content length after final cleanup: " . strlen($content), 'Cron');
			// --- 结束新增清理 ---

			// 在最后更新文章之前尝试闭合 HTML 标签
			$original_content_length = strlen($content); // 记录原始长度用于调试
			$content = HtmlUtils::close_html_tags($content);
			$processed_content_length = strlen($content); // 记录处理后长度
			Logger::debug("Task {$key} - HTML tag closing: orig_len={$original_content_length}, processed_len={$processed_content_length}", 'Cron');

			// 最后更新文章状态
			$update_status_res = wp_update_post([
				'ID' => $post_id,
				'post_status' => $final_post_status // 使用最终确定的状态
			], true);
			if (is_wp_error($update_status_res) || (int)$update_status_res === 0) {
				if (is_wp_error($update_status_res)) {
					Logger::error('更新文章状态失败(WP_Error): ' . $update_status_res->get_error_message() . ' | data=' . print_r($update_status_res->get_error_data(), true), 'Cron');
				} else {
					Logger::error('更新文章状态失败: post_id=' . (int)$post_id . ' final_status=' . (string)$final_post_status, 'Cron');
				}
			} else {
				Logger::info('更新文章状态成功: post_id=' . (int)$post_id . ' final_status=' . (string)$final_post_status, 'Cron');
			}

			// 新增：写入运行日志，记录文章ID和任务key
			$extra = 'post_id:' . $post_id . ', task_key:' . $key;
			try {
				$model_used = '';
				if (isset($chat_gpt) && is_object($chat_gpt) && method_exists($chat_gpt, 'getModel')) {
					$model_used = (string)$chat_gpt->getModel();
				}
				if ($model_used !== '') {
					$extra .= ', model:' . $model_used;
				}

				$category_label = '';
				$category_id = $this->settings['tasks'][$key]['post-category'] ?? null;
				if (!empty($category_id) && function_exists('get_term')) {
					$category = get_term($category_id, 'category');
					if (!is_wp_error($category) && $category && isset($category->name)) {
						$category_label = (string)$category->name;
					}
				}
				if ($category_label !== '') {
					$extra .= ', category:' . $category_label;
				}
			} catch (\Throwable $e) {
				// ignore
			}
			// 获取的指令（第一行）
			$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, (int)$post_id);

            // 新增：数据库统计埋点（不依赖 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);
                }
                if (!empty($ai_summary_text_plain)) {
                    \update_post_meta($post_id, '_ai_post_ai_summary', (string)$ai_summary_text_plain);
                }
            } catch (\Throwable $e) {
                Logger::error('写入文章统计埋点失败: ' . $e->getMessage(), 'Cron');
            }

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

            // 新增：强制触发内链处理
			// 已在前面完成内链处理，此处不再重复触发，避免重复更新文章

			// --- START: Generate Schema.org JSON-LD if enabled ---
			$schema_enabled = $this->settings['global_tabs']['enable_schema_org'] ?? false;
			if ($schema_enabled) {
				Logger::debug("Task {$key} - Generating Schema.org JSON-LD for Post ID: {$post_id}", 'Schema');
                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();
						 $faq_content_html_utf8 = (string) $faq_content_html;
						 if (function_exists('wp_check_invalid_utf8')) {
							$faq_content_html_utf8 = (string) wp_check_invalid_utf8($faq_content_html_utf8, true);
						 }
						 $enc = function_exists('mb_detect_encoding') ? @mb_detect_encoding($faq_content_html_utf8, ['UTF-8','GBK','GB2312','BIG5','ISO-8859-1','Windows-1252'], true) : false;
						 if ($enc && strtoupper((string) $enc) !== 'UTF-8') {
							if (function_exists('mb_convert_encoding')) {
								$faq_content_html_utf8 = (string) @mb_convert_encoding($faq_content_html_utf8, 'UTF-8', $enc);
							} elseif (function_exists('iconv')) {
								$faq_content_html_utf8 = (string) @iconv($enc, 'UTF-8//IGNORE', $faq_content_html_utf8);
							}
						 }
						 $fix_mojibake = function($s){
							$s = is_string($s) ? $s : '';
							if ($s === '' || preg_match('/[\x{4E00}-\x{9FFF}]/u', $s)) { return $s; }
							$candidates = [];
							if (function_exists('iconv')) {
								// 将“è¿…”这类 UTF-8 mojibake 回滚成原始 UTF-8 字节
								$candidates[] = @iconv('UTF-8', 'Windows-1252//IGNORE', $s);
								$candidates[] = @iconv('UTF-8', 'ISO-8859-1//IGNORE', $s);
							} elseif (function_exists('mb_convert_encoding')) {
								$candidates[] = @mb_convert_encoding($s, 'Windows-1252', 'UTF-8');
								$candidates[] = @mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');
							}
							foreach ($candidates as $b) {
								if (is_string($b) && $b !== '' && preg_match('/[\x{4E00}-\x{9FFF}]/u', $b)) {
									return $b;
								}
							}
							return $s;
						 };
						 // Use a wrapper div for parsing fragments
						 @$faq_dom->loadHTML('<?xml encoding="UTF-8"><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 = $fix_mojibake(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 = $fix_mojibake(strip_tags($answer_text));
									 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 .= $fix_mojibake(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);
                        Logger::debug("Task {$key} - Schema JSON-LD saved for Post ID: {$post_id}", 'Schema');
                    } else {
                        Logger::warning("Task {$key} - Failed to encode Schema to JSON for Post ID: {$post_id}", 'Schema');
                    }

                } catch (Exception $e) {
                    Logger::error("Task {$key} - Error generating Schema JSON-LD: " . $e->getMessage(), 'Schema');
                }
            } else {
                 // Optionally delete existing schema if setting is disabled
                 delete_post_meta($post_id, '_ai_post_schema_json_ld');
                 Logger::debug("Task {$key} - Schema.org disabled, removed meta for Post ID: {$post_id}", 'Schema');
            }
            // --- 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) {
		Logger::debug("开始重复标题AI改写，原标题: '{$original_title}'，最大尝试: {$retry}", 'Cron');
		
		// 从示例标题数组中随机获取一个示例标题
		$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 {
				Logger::debug("重复标题AI改写尝试 #" . ($i + 1), 'Cron');
				$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']);
				Logger::debug("AI返回的新标题: '{$new_title}'", 'Cron');

				// 添加 Token 使用量日志 (标题重写)
				if (isset($response['usage'])) {
					Logger::debug('Token Usage (TitleRewrite): ' . json_encode($response['usage']), 'Cron');
				}

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

				if (!$this->is_duplicate_title($new_title)) {
					Logger::info("改写成功，新标题不重复: '{$new_title}'", 'Cron');
					
					// 记录改写日志
					\HaoZiTeam\AIPost\Service\Features\RunLog::add(
						$prompt,
						$new_title,
						'success',
						'',
						'duplicate',
						0,
						$original_title  // 添加原始标题
					);
					
					return $new_title;
				} else {
					Logger::debug("改写后的标题仍然重复: '{$new_title}'，尝试下一次", 'Cron');
				}
			} catch (Exception $e) {
				Logger::error("重复标题AI改写异常: " . $e->getMessage(), 'Cron');
				throw $e;
			}
		}
		
		Logger::warning("重复标题AI改写失败，已达到最大尝试次数: {$retry}", 'Cron');
		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'])) {
            Logger::debug('Method-2 generate_thumbnail returned empty, skip title image insert.', 'InsertImage');
            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);
        Logger::debug('Method-2 position_setting=' . $position_setting, 'InsertImage');

        // 表格保护：替换为占位符，避免破坏结构
        $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;
                Logger::debug('Method-2 inserted at head (position=1).', 'InsertImage');
                break;
            case 3:
                $content = $content . $image_html;
                Logger::debug('Method-2 inserted at tail (position=3).', 'InsertImage');
                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);
                        Logger::debug('Method-2 middle index=' . $image_start_num, 'InsertImage');
                    } else {
                        $image_start_num = mt_rand(0, max(0, $p_nums - 1));
                        Logger::debug('Method-2 random index=' . $image_start_num, 'InsertImage');
                    }
                } 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"; // 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
					Logger::debug("SEO: Post ID {$post->ID} has invalid meta description, falling back to live extraction.", 'SEO');
                    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)
				 Logger::debug("SEO: Post ID {$post->ID} has no meta description. Method: {$desc_method}. Falling back.", 'SEO');
                 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"; // 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 "\n";
		}

		echo "\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)) {
			Logger::error('内链: 无法获取最新文章对象，ID: ' . $post_id, 'InternalLink');
			return;
		}
		
		// 检查是否已处理过，防止无限循环 (如果通过 filter 调用)
		if (get_post_meta($post_id, '_ai_post_internal_links_processed', true)) {
			Logger::debug("内链: Post ID {$post_id} 已标记为处理过，跳过", 'InternalLink');
			// return; // 暂时注释掉，以便测试，但生产环境应考虑启用
		}

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

		Logger::debug("内链: Post ID {$post_id} 开始处理 | content_len=" . strlen($original_content), 'InternalLink');

		$extracted_ai_summary_html = $this->extract_first_div_block_by_class($content, 'ai-post-ai-summary');
		if ($extracted_ai_summary_html !== '') {
			Logger::debug("内链: Post ID {$post_id} 已分离 AI Summary", 'InternalLink');
		}

		// --- 分离 TOC ---
		$extracted_toc_html = $this->extract_first_div_block_by_class($content, 'ai-post-toc');
		if ($extracted_toc_html !== '') {
			Logger::debug("内链: Post ID {$post_id} 已分离 TOC", 'InternalLink');
		}

		$card_placeholders = [];
		$card_placeholder_counter = 0;
		$card_classes_to_exclude = ['ai-feature-grid', 'ai-stats-grid', 'ai-tips-card'];
		foreach ($card_classes_to_exclude as $card_class) {
			while (true) {
				$placeholder = '%%AI_CARD_PLACEHOLDER_' . $card_placeholder_counter++ . '%%';
				$block = $this->replace_first_div_block_by_class_with_placeholder($content, $card_class, $placeholder);
				if ($block === '') {
					$card_placeholder_counter--; // 回滚未使用的计数
					break;
				}
				$card_placeholders[$placeholder] = $block;
			}
		}

		$table_placeholders = [];
		$table_placeholder_counter = 0;
		while (true) {
			$placeholder = '%%AI_TABLE_PLACEHOLDER_' . $table_placeholder_counter++ . '%%';
			$block = $this->replace_first_table_block_with_placeholder($content, $placeholder);
			if ($block === '') {
				$table_placeholder_counter--; // 回滚未使用的计数
				break;
			}
			$table_placeholders[$placeholder] = $block;
		}

		$heading_placeholders = [];
		$heading_placeholder_counter = 0;
		$content = preg_replace_callback('/<h([2-5])\\b[^>]*>.*?<\\/h\\1>/is', function($match) use (&$heading_placeholders, &$heading_placeholder_counter) {
			$placeholder = '%%AI_H_PLACEHOLDER_' . $heading_placeholder_counter++ . '%%';
			$heading_placeholders[$placeholder] = $match[0];
			return $placeholder;
		}, $content);

		// --- 保护现有链接和图片 ---
		$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);
		Logger::debug("内链: Post ID {$post_id} 已保护 " . count($placeholders) . " 个 a 标签", 'InternalLink');

		// 保护 <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);
		Logger::debug("内链: Post ID {$post_id} 已保护 " . (count($placeholders) - $initial_img_count) . " 个 img 标签", 'InternalLink');

		// --- 准备内链替换 ---
		$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'])) {
			Logger::debug("内链: Post ID {$post_id} 开始处理自定义内链", 'InternalLink');
			$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) {
					Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 已达上限 ({$replace_limit})", 'InternalLink');
					continue;
				}

				$nofollow_attr = ($this->settings['global_tabs']['link-nofollow'] ?? false) ? ' rel="nofollow"' : '';
				$pattern = '/(' . preg_quote($keyword, '/') . ')/iu'; // 移除单词边界 \b，进行更宽松匹配
				Logger::debug("内链: Post ID {$post_id} 尝试匹配关键词 '{$keyword}'", 'InternalLink');

				$content = $this->preg_replace_callback_text_nodes($content, $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
						];
						Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 匹配成功 (第 {$keyword_replacements_count[$keyword]} 次)", 'InternalLink');
						return $link_placeholder;
					} else {
						// 已达到限制，返回原始匹配文本
						Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 已达上限，保留原文", 'InternalLink');
						return $match[0];
					}
				}, -1, $count); // 设置 limit 为 -1 (无限)，在回调中控制

				if ($count > 0) {
					$content_modified = true; // 标记内容已被修改
					Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 替换完成，找到 {$count} 处，总替换: {$keyword_replacements_count[$keyword]}", 'InternalLink');
				}
			}
			Logger::debug("内链: Post ID {$post_id} 自定义内链处理完成", 'InternalLink');
		} else {
			Logger::debug("内链: Post ID {$post_id} 未配置自定义内链规则", 'InternalLink');
		}

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

			if (!empty($tags)) {
				Logger::debug("内链: Post ID {$post_id} 找到 " . count($tags) . " 个标签", 'InternalLink');
				// 按标签名长度降序排序，优先处理长标签，减少部分包含问题 (可选优化)
				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;

					Logger::debug("内链: Post ID {$post_id} 处理标签: '{$keyword}'", 'InternalLink');
					if (!isset($keyword_replacements_count[$keyword])) {
						$keyword_replacements_count[$keyword] = 0;
					}

					// 提前检查替换限制
					if ($keyword_replacements_count[$keyword] >= $replace_limit) {
						Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 已达上限，跳过", 'InternalLink');
						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);
						Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 找到相关文章链接", 'InternalLink');
					} 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) {
							    Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 回退到标签链接", 'InternalLink');
							} else {
							    Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 回退标签链接失败", 'InternalLink');
                                $link_url = ''; // 确保 $link_url 为空
							}
						} else {
							Logger::debug("内链: Post ID {$post_id} 关键词 '{$keyword}' 未找到相关文章且未启用备用", 'InternalLink');
                            // $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，进行更宽松匹配
						Logger::debug("内链: Post ID {$post_id} 尝试匹配关键词 '{$keyword}'", 'InternalLink');

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

						$count = 0;
						$content = $this->preg_replace_callback_text_nodes($content, $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];
							}
						}, -1, $count); // $count 参数在这里不再直接使用，我们在回调内外追踪

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

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

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

		// --- 最后：替换关键词占位符为最终链接 ---
		if (!empty($final_link_data)) {
			Logger::debug("内链: Post ID {$post_id} 转换 " . count($final_link_data) . " 个占位符为链接", 'InternalLink');
			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);
			}
			Logger::debug("内链: Post ID {$post_id} 占位符转换完成", 'InternalLink');
			$content_modified = true; // 只要有最终链接数据，就认为内容已修改
		}

		if (!empty($table_placeholders)) {
			krsort($table_placeholders);
			$content = str_replace(array_keys($table_placeholders), array_values($table_placeholders), $content);
		}

		if (!empty($card_placeholders)) {
			krsort($card_placeholders);
			$content = str_replace(array_keys($card_placeholders), array_values($card_placeholders), $content);
		}

		if (!empty($heading_placeholders)) {
			krsort($heading_placeholders);
			$content = str_replace(array_keys($heading_placeholders), array_values($heading_placeholders), $content);
		}

		if (!empty($extracted_toc_html)) {
			$content = $extracted_toc_html . $content;
			Logger::debug("内链: Post ID {$post_id} 已恢复 TOC", 'InternalLink');
		}
		if (!empty($extracted_ai_summary_html)) {
			$content = $extracted_ai_summary_html . $content;
			Logger::debug("内链: Post ID {$post_id} 已恢复 AI Summary", 'InternalLink');
		}

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

		// --- 更新文章内容 (仅当内容实际修改时) ---
		if ($content_modified || $content !== $original_content) {
			Logger::debug("内链: Post ID {$post_id} 内容已修改，准备更新数据库", 'InternalLink');

			// 标记文章已处理 (应在更新前或更新后立即执行)
			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)) {
				Logger::error("内链: Post ID {$post_id} 更新文章失败: " . $update_result->get_error_message(), 'InternalLink');
			} else {
				Logger::debug("内链: Post ID {$post_id} 数据库更新完成", 'InternalLink');
			}

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

		} else {
			Logger::debug("内链: Post ID {$post_id} 内容未改变，无需更新", 'InternalLink');
		}

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

	} // End function add_internal_links

	private function preg_replace_callback_text_nodes(string $html, string $pattern, callable $callback, int $limit = -1, ?int &$total_count = null): string
	{
		$parts = preg_split('/(<[^>]+>)/u', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
		if (!is_array($parts)) {
			$local_count = 0;
			$out = preg_replace_callback($pattern, $callback, $html, $limit, $local_count);
			if ($total_count !== null) { $total_count += $local_count; }
			return is_string($out) ? $out : $html;
		}

		$local_total = 0;
		foreach ($parts as $i => $part) {
			if ($part === '' || $part[0] === '<') {
				continue;
			}
			$local_count = 0;
			$replaced = preg_replace_callback($pattern, $callback, $part, $limit, $local_count);
			if (is_string($replaced)) {
				$parts[$i] = $replaced;
				$local_total += $local_count;
			}
		}

		if ($total_count !== null) { $total_count += $local_total; }
		return implode('', $parts);
	}

	private function extract_first_div_block_by_class(string &$html, string $class): string
	{
		$re = '/<div\b[^>]*class\s*=\s*(["\'])\s*[^"\']*\b' . preg_quote($class, '/') . '\b[^"\']*\1[^>]*>/i';
		if (!preg_match($re, $html, $m, PREG_OFFSET_CAPTURE)) {
			return '';
		}

		$start = (int)$m[0][1];
		$offset = $start;
		$depth = 0;
		$tagRe = '/<div\b[^>]*>|<\/div\s*>/i';
		while (preg_match($tagRe, $html, $tm, PREG_OFFSET_CAPTURE, $offset)) {
			$tag = $tm[0][0];
			$pos = (int)$tm[0][1];
			if ($pos < $start) {
				$offset = $pos + strlen($tag);
				continue;
			}
			if (stripos($tag, '</div') === 0) {
				$depth--;
			} else {
				$depth++;
			}
			$offset = $pos + strlen($tag);
			if ($depth === 0) {
				$end = $offset;
				$extracted = substr($html, $start, $end - $start);
				$html = substr($html, 0, $start) . substr($html, $end);
				return $extracted;
			}
		}

		return '';
	}

	private function replace_first_div_block_by_class_with_placeholder(string &$html, string $class, string $placeholder): string
	{
		$re = '/<div\b[^>]*class\s*=\s*(["\'])\s*[^"\']*\b' . preg_quote($class, '/') . '\b[^"\']*\1[^>]*>/i';
		if (!preg_match($re, $html, $m, PREG_OFFSET_CAPTURE)) {
			return '';
		}

		$start = (int)$m[0][1];
		$offset = $start;
		$depth = 0;
		$tagRe = '/<div\b[^>]*>|<\/div\s*>/i';
		while (preg_match($tagRe, $html, $tm, PREG_OFFSET_CAPTURE, $offset)) {
			$tag = $tm[0][0];
			$pos = (int)$tm[0][1];
			if ($pos < $start) {
				$offset = $pos + strlen($tag);
				continue;
			}
			if (stripos($tag, '</div') === 0) {
				$depth--;
			} else {
				$depth++;
			}
			$offset = $pos + strlen($tag);
			if ($depth === 0) {
				$end = $offset;
				$extracted = substr($html, $start, $end - $start);
				$html = substr($html, 0, $start) . $placeholder . substr($html, $end);
				return $extracted;
			}
		}

		return '';
	}

	private function replace_first_table_block_with_placeholder(string &$html, string $placeholder): string
	{
		if (!preg_match('/<table\b[^>]*>/i', $html, $m, PREG_OFFSET_CAPTURE)) {
			return '';
		}
		$start = (int)$m[0][1];
		$offset = $start;
		$depth = 0;
		$tagRe = '/<table\b[^>]*>|<\/table\s*>/i';
		while (preg_match($tagRe, $html, $tm, PREG_OFFSET_CAPTURE, $offset)) {
			$tag = $tm[0][0];
			$pos = (int)$tm[0][1];
			if ($pos < $start) {
				$offset = $pos + strlen($tag);
				continue;
			}
			if (stripos($tag, '</table') === 0) {
				$depth--;
			} else {
				$depth++;
			}
			$offset = $pos + strlen($tag);
			if ($depth === 0) {
				$end = $offset;
				$extracted = substr($html, $start, $end - $start);
				$html = substr($html, 0, $start) . $placeholder . substr($html, $end);
				return $extracted;
			}
		}

		return '';
	}

	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)) {
			Logger::warning('图片下载失败: ' . $tmp->get_error_message(), 'ImageSideload');
			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);
			Logger::warning('媒体处理失败: ' . $attachment_id->get_error_message(), 'ImageSideload');
			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)) {
							Logger::debug("成功将图片转换为WebP: {$webp_path}", 'ImageSideload');
							// 更新附件元数据
							$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'] ?? [];
		
		// 添加调试日志
		Logger::debug('当前选择位置：'.$position_setting.'，实际索引：'.max(0, $position_setting - 1).'，图片数量：'.count($images), 'InsertImage');
		
		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 {
			$original_content = $content;
			$figures = array_values(array_filter($figures, function($x){ return is_string($x) && trim($x) !== ''; }));
			$img_n = count($figures);
			if ($img_n === 0) { return $content; }

			$placeholders = [];
			$placeholder_index = 0;
			$content_with_placeholders = preg_replace_callback(
				'/<div[^>]*class=["\'][^"\']*\bai-faq\b[^"\']*["\'][^>]*>.*?<\/div>/is',
				function($matches) use (&$placeholders, &$placeholder_index) {
					$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
					$placeholders[$placeholder] = $matches[0];
					return $placeholder;
				},
				$content
			);
			if ($content_with_placeholders) {
				$content_with_placeholders = preg_replace_callback(
					'/<table\b[^>]*>.*?<\/table>/is',
					function($matches) use (&$placeholders, &$placeholder_index) {
						$placeholder = '%%AI_IMG_TABLE_PROTECT_' . $placeholder_index++ . '%%';
						$placeholders[$placeholder] = $matches[0];
						return $placeholder;
					},
					$content_with_placeholders
				);
			}
			if (!empty($placeholders) && is_string($content_with_placeholders) && $content_with_placeholders !== '') {
				$content = $content_with_placeholders;
			}

			// 分段
			$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);
			Logger::debug('分散插入前统计: paragraphs=' . $total . ' | images=' . $img_n . ' | content_len=' . strlen($content), 'InsertImage');

			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);
			Logger::debug('分散插入完成: inserted=' . $inserted . ' | interval=' . $interval . ' | final_len=' . strlen($final), 'InsertImage');
			if (!empty($placeholders)) {
				$final = str_replace(array_keys($placeholders), array_values($placeholders), $final);
			}
			return $final;
		} catch (\Throwable $e) {
			Logger::error('分散插入异常: ' . $e->getMessage(), 'InsertImage');
			// 兜底：全部头部插入
			return implode('', $figures) . $original_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);
		
		Logger::debug("当前分钟数 {$current_minutes}，开始 {$start_minutes}，结束 {$end_minutes}", 'Cron');
		
		// 处理跨天的情况
		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 = [])
	{
		// 开始执行任务，记录基本参数
		Logger::info("AI任务执行: 模型={$model}, 主题={$topic}" . ($proxy ? ", 代理={$proxy}" : ""), 'RunTask');
		
		try {
			// 记录关键参数
			Logger::debug("任务参数: " . json_encode([
				'model' => $model,
				'topic' => $topic,
				'has_proxy' => !empty($proxy),
				'options_count' => count($options)
			], JSON_UNESCAPED_UNICODE), 'RunTask');

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

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

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

			// 发送API请求
			try {
				$response = $apiClient->ask($content);
				Logger::debug("API请求成功，响应ID: " . ($response['id'] ?? 'unknown'), 'RunTask');
				
				// 记录Token使用情况
				if (isset($response['usage'])) {
					Logger::debug("Token使用: " . json_encode($response['usage']), 'RunTask');
				}
			} catch (Exception $e) {
				Logger::error("API请求失败: " . $e->getMessage(), 'RunTask');
				$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);
			Logger::debug("响应处理完成，状态: " . $result['status'], 'RunTask');
			
			// 文章生成成功后写入日志
			$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) {
			// 捕获所有未处理的异常
			Logger::error("任务执行未处理异常: " . $e->getMessage(), 'RunTask');
			
			return [
				'status' => 'error',
				'code' => 500,
				'message' => '执行过程中发生错误: ' . $e->getMessage(),
				'data' => null
			];
		}
	}

	/**
	 * 验证账户信息
	 * 
	 * @return array 包含验证结果和令牌信息
	 */
	private function validateAccountInfo()
	{
		// 获取当前设置
		$settings = $this->settings;
		$apiServerType = $settings['api-server-type'] ?? 'custom';
		Logger::debug("验证账户信息，服务器类型: " . $apiServerType, 'RunTask');
		
		// 根据不同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 === 'moonshot') {
			if (!empty($settings['moonshot-accounts'])) {
				$accounts = $settings['moonshot-accounts'];
				$index = rand(1, count($accounts));
				$account = $accounts[$index - 1];
				$token = $account['account-secret-key'] ?? '';
				$valid = !empty($token);
				$message = $valid ? 'Moonshot账户验证成功' : 'Moonshot账户密钥为空';
			} else {
				$message = '未配置Moonshot账户';
			}
		} 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 === 'moonshot') {
			$apiServer = 'https://api.moonshot.cn/v1/';
			$selectedModel = !empty($model) ? $model : 'moonshot-v1-8k';
			
			return new V2(
				$token,
				$apiServer,
				$selectedModel,
				null,
				null,
				360,
				[
					'is_moonshot' => 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) {
					Logger::warning("JSON解析失败: " . json_last_error_msg(), 'RunTask');
					throw new Exception("无法解析JSON响应");
				}
				
				return [
					'status' => 'success',
					'code' => 200,
					'message' => '成功',
					'data' => $jsonData
				];
			} catch (Exception $e) {
				Logger::error("处理JSON响应失败: " . $e->getMessage(), 'RunTask');
				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，记录错误或跳过
			   Logger::debug('TOC generation encountered a non-element node.', 'TOC'); 
			   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);
			 }
			 Logger::debug('TOC: Extracted content using root element iteration.', 'TOC');
		} else {
			// Fallback 1: If the root element isn't the div (unexpected), try finding the body's first child
			Logger::debug('TOC: Root element was not expected div ('.($doc_element ? $doc_element->nodeName : 'null').'). Trying body->firstChild.', 'TOC');
			$body = $dom->getElementsByTagName('body')->item(0);
			if ($body && $body->firstChild) {
				foreach ($body->firstChild->childNodes as $node) {
					$modified_content .= $dom->saveHTML($node);
				}
				Logger::debug('TOC: Extracted content using body->firstChild iteration.', 'TOC');
			} else {
				// Fallback 2: Final Fallback - If all else fails, save the whole document and attempt regex/stripping
				Logger::warning('TOC: FINAL FALLBACK EXTRACTION TRIGGERED (body->firstChild failed)', 'TOC');
				$saved_html = $dom->saveHTML();
				Logger::debug('TOC: Full saved HTML in fallback (first 200): ' . substr($saved_html, 0, 200), 'TOC');

				// 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];
					Logger::debug('TOC: Extracted content using fallback regex.', 'TOC');
				} else {
					// Last resort: basic tag stripping
					Logger::warning('TOC: Fallback regex failed. Resorting to basic tag stripping.', 'TOC');
					$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);
					Logger::debug('TOC: Content after basic stripping (first 100): ' . substr($modified_content, 0, 100), 'TOC');
				}
			}
		}

		// Final cleanup: Decode HTML entities regardless of extraction method
		$modified_content = html_entity_decode($modified_content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		Logger::debug('TOC: Final modified_content length after decode: ' . strlen($modified_content), 'TOC');
		// --- 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);
	}

	/**
	 * 读取内置的“特性卡片指令模板”。
	 * 从 Service/Features/AIcommand.txt 中提取以“##特性卡片指令模板”开头到文件末尾的内容，
	 * 去掉首行标题后返回剩余文本；若文件不存在或解析失败则返回空字符串。
	 *
	 * @return string
	 */
	private function get_feature_cards_instruction(): string
	{
		try {
			$service_dir = dirname(__FILE__); // .../Service
			$plugin_root = dirname($service_dir); // 插件根目录
			$file = rtrim($plugin_root, "/\\") . DIRECTORY_SEPARATOR . 'Service' . DIRECTORY_SEPARATOR . 'Features' . DIRECTORY_SEPARATOR . 'AIcards.txt';
			if (!@file_exists($file)) {
				return '';
			}
			$raw = @file_get_contents($file);
			if ($raw === false || $raw === '') {
				return '';
			}
			// 确保是 UTF-8
			$enc = function_exists('mb_detect_encoding') ? @mb_detect_encoding($raw, ['UTF-8','GBK','GB2312','BIG5','ISO-8859-1'], true) : false;
			if ($enc && strtoupper($enc) !== 'UTF-8') {
				if (function_exists('mb_convert_encoding')) {
					$raw = @mb_convert_encoding($raw, 'UTF-8', $enc);
				} elseif (function_exists('iconv')) {
					$raw = @iconv($enc, 'UTF-8//IGNORE', $raw);
				}
			}
			$content = (string)$raw;
			$marker = '##特性卡片指令模板';
			$pos = mb_strpos($content, $marker, 0, 'UTF-8');
			if ($pos === false) {
				return '';
			}
			$len = mb_strlen($content, 'UTF-8');
			$segment = mb_substr($content, $pos, $len - $pos, 'UTF-8');
			// 去掉首行标题
			$lines = preg_split("/\r?\n/", (string)$segment);
			if (!is_array($lines) || count($lines) === 0) {
				return '';
			}
			// 删除第一行（标题）
			array_shift($lines);
			return trim(implode("\n", $lines));
		} catch (\Throwable $e) {
			// 安静失败，避免影响主流程
			return '';
		}
	}

	/**
	 * 读取内置的“实用技巧提示卡指令模板”。
	 * 从 Service/Features/AIcommand.txt 中提取以“##实用技巧提示卡指令模板”开头到文件末尾的内容，
	 * 去掉首行标题后返回剩余文本；若文件不存在或解析失败则返回空字符串。
	 *
	 * @return string
	 */
	private function get_tips_card_instruction(): string
	{
		try {
			$service_dir = dirname(__FILE__); // .../Service
			$plugin_root = dirname($service_dir); // 插件根目录
			$file = rtrim($plugin_root, "/\\") . DIRECTORY_SEPARATOR . 'Service' . DIRECTORY_SEPARATOR . 'Features' . DIRECTORY_SEPARATOR . 'AIcards.txt';
			if (!@file_exists($file)) {
				return '';
			}
			$raw = @file_get_contents($file);
			if ($raw === false || $raw === '') {
				return '';
			}
			// 确保是 UTF-8
			$enc = function_exists('mb_detect_encoding') ? @mb_detect_encoding($raw, ['UTF-8','GBK','GB2312','BIG5','ISO-8859-1'], true) : false;
			if ($enc && strtoupper($enc) !== 'UTF-8') {
				if (function_exists('mb_convert_encoding')) {
					$raw = @mb_convert_encoding($raw, 'UTF-8', $enc);
				} elseif (function_exists('iconv')) {
					$raw = @iconv($enc, 'UTF-8//IGNORE', $raw);
				}
			}
			$content = (string)$raw;
			$marker = '##实用技巧提示卡指令模板';
			$pos = mb_strpos($content, $marker, 0, 'UTF-8');
			if ($pos === false) {
				return '';
			}
			$len = mb_strlen($content, 'UTF-8');
			$segment = mb_substr($content, $pos, $len - $pos, 'UTF-8');
			// 去掉首行标题
			$lines = preg_split("/\r?\n/", (string)$segment);
			if (!is_array($lines) || count($lines) === 0) {
				return '';
			}
			// 删除第一行（标题）
			array_shift($lines);
			return trim(implode("\n", $lines));
		} catch (\Throwable $e) {
			// 安静失败，避免影响主流程
			return '';
		}
	}
	
	/**
	 * 读取内置的“数据统计卡片指令模板”。
	 * 从 Service/Features/AIcommand.txt 中提取以“##数据统计卡片指令模板”开头到文件末尾的内容，
	 * 去掉首行标题后返回剩余文本；若文件不存在或解析失败则返回空字符串。
	 *
	 * @return string
	 */
	private function get_stats_cards_instruction(): string
	{
		try {
			$service_dir = dirname(__FILE__); // .../Service
			$plugin_root = dirname($service_dir); // 插件根目录
			$file = rtrim($plugin_root, "/\\") . DIRECTORY_SEPARATOR . 'Service' . DIRECTORY_SEPARATOR . 'Features' . DIRECTORY_SEPARATOR . 'AIcards.txt';
			if (!@file_exists($file)) {
				return '';
			}
			$raw = @file_get_contents($file);
			if ($raw === false || $raw === '') {
				return '';
			}
			// 确保是 UTF-8
			$enc = function_exists('mb_detect_encoding') ? @mb_detect_encoding($raw, ['UTF-8','GBK','GB2312','BIG5','ISO-8859-1'], true) : false;
			if ($enc && strtoupper($enc) !== 'UTF-8') {
				if (function_exists('mb_convert_encoding')) {
					$raw = @mb_convert_encoding($raw, 'UTF-8', $enc);
				} elseif (function_exists('iconv')) {
					$raw = @iconv($enc, 'UTF-8//IGNORE', $raw);
				}
			}
			$content = (string)$raw;
			$marker = '##数据统计卡片指令模板';
			$pos = mb_strpos($content, $marker, 0, 'UTF-8');
			if ($pos === false) {
				return '';
			}
			$len = mb_strlen($content, 'UTF-8');
			$segment = mb_substr($content, $pos, $len - $pos, 'UTF-8');
			// 去掉首行标题
			$lines = preg_split("/\r?\n/", (string)$segment);
			if (!is_array($lines) || count($lines) === 0) {
				return '';
			}
			// 删除第一行（标题）
			array_shift($lines);
			return trim(implode("\n", $lines));
		} catch (\Throwable $e) {
			// 安静失败，避免影响主流程
			return '';
		}
	}

	/**
	 * 将特性卡片网格插入到正文 HTML 中第 N 个有效段落之后。
	 * 有效段落：不包含 <img> 或 <table> 标签的 <p> 元素。
	 *
	 * @param string $content 当前文章 HTML 内容
	 * @param array  $cards   特性卡片数组（最多使用前 6 条）
	 * @param int    $paragraphIndex 目标段落序号（从 1 开始）
	 * @return string 插入特性卡片后的 HTML
	 */
	private function insert_feature_cards_into_content(string $content, array $cards, int $paragraphIndex): string
	{
		try {
			if (empty($cards)) {
				return $content;
			}
			$paragraphIndex = max(1, (int)$paragraphIndex);

			libxml_use_internal_errors(true);
			$dom = new \DOMDocument('1.0', 'UTF-8');
			// 保证 UTF-8，并用 div 包裹内容，便于插入
			if (function_exists('mb_detect_encoding') && mb_detect_encoding($content, 'UTF-8', true) === false) {
				$content = mb_convert_encoding($content, 'UTF-8');
			}
			$wrapped = '<div>' . $content . '</div>';
			$encoded = function_exists('mb_convert_encoding')
				? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8')
				: $wrapped;
			$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
			libxml_clear_errors();

			$xpath = new \DOMXPath($dom);
			$nodes = $xpath->query('//p');
			$validParas = [];
			if ($nodes instanceof \DOMNodeList) {
				foreach ($nodes as $p) {
					if (!($p instanceof \DOMElement)) {
						continue;
					}
					// 额外防护：跳过已位于特性卡片网格内部的段落
					$ancestor = $p->parentNode;
					$insideFeatureGrid = false;
					while ($ancestor instanceof \DOMElement) {
						$classAttr = $ancestor->getAttribute('class');
						if ($classAttr && strpos($classAttr, 'ai-feature-grid') !== false) {
							$insideFeatureGrid = true;
							break;
						}
						$ancestor = $ancestor->parentNode;
					}
					if ($insideFeatureGrid) {
						continue;
					}
					// 跳过包含图片或表格的段落
					if ($p->getElementsByTagName('img')->length > 0) {
						continue;
					}
					if ($p->getElementsByTagName('table')->length > 0) {
						continue;
					}
					$validParas[] = $p;
				}
			}

			// 优先使用 <body> 作为根容器，其次退回 documentElement
			$body = $dom->getElementsByTagName('body')->item(0);
			$root = $body instanceof \DOMElement ? $body : $dom->documentElement;
			$targetParent = null;
			$refNode = null;

			if (!empty($validParas)) {
				$idx = min($paragraphIndex, count($validParas)) - 1; // 0-based
				$targetP = $validParas[$idx];
				$targetParent = $targetP->parentNode;
				$refNode = $targetP->nextSibling;
			} else {
				$targetParent = $root;
				$refNode = null;
			}

			if (!$targetParent instanceof \DOMNode) {
				return $content;
			}

			// 构建特性卡片网格（最多 6 条）
			$grid = $dom->createElement('div');
			$grid->setAttribute('class', 'ai-feature-grid');
			$maxCards = min(6, count($cards));
			for ($i = 0; $i < $maxCards; $i++) {
				$cardData = $cards[$i];
				if (!is_array($cardData)) {
					continue;
				}
				$icon = isset($cardData['icon']) ? (string)$cardData['icon'] : '';
				$title = isset($cardData['title']) ? (string)$cardData['title'] : '';
				$desc  = isset($cardData['desc']) ? (string)$cardData['desc'] : '';

				$card = $dom->createElement('div');
				$card->setAttribute('class', 'ai-feature-card');

				$iconDiv = $dom->createElement('div');
				$iconDiv->setAttribute('class', 'ai-feature-icon');
				$iconDiv->appendChild($dom->createTextNode($icon));

				$bodyDiv = $dom->createElement('div');
				$bodyDiv->setAttribute('class', 'ai-feature-body');

				$titleEl = $dom->createElement('h3');
				$titleEl->setAttribute('class', 'ai-feature-title');
				$titleEl->appendChild($dom->createTextNode($title));

				$descEl = $dom->createElement('p');
				$descEl->setAttribute('class', 'ai-feature-desc');
				$descEl->appendChild($dom->createTextNode($desc));

				$bodyDiv->appendChild($titleEl);
				$bodyDiv->appendChild($descEl);
				$card->appendChild($iconDiv);
				$card->appendChild($bodyDiv);
				$grid->appendChild($card);
			}

			if ($refNode) {
				$targetParent->insertBefore($grid, $refNode);
			} else {
				$targetParent->appendChild($grid);
			}

			// 取根容器子节点的 HTML 作为新正文
			$result = '';
			if ($root) {
				foreach ($root->childNodes as $child) {
					$result .= $dom->saveHTML($child);
				}
			}
			$result = html_entity_decode($result, ENT_QUOTES | ENT_HTML5, 'UTF-8');
			if ($result !== '' && strpos($result, 'ai-feature-grid') !== false) {
				Logger::debug('insert_feature_cards: 成功生成 ai-feature-grid 容器', 'Cards');
			}
			return $result !== '' ? $result : $content;
		} catch (\Throwable $e) {
			return $content;
		}
	}

	/**
	 * 将数据统计卡片网格插入到正文 HTML 中第 N 个有效段落之后。
	 * 有效段落：不包含 <img> 或 <table> 标签的 <p> 元素，且不位于特性卡片或数据统计卡片网格内部。
	 *
	 * @param string $content 当前文章 HTML 内容
	 * @param array  $cards   数据统计卡片数组（最多使用前 4 条）
	 * @param int    $paragraphIndex 目标段落序号（从 1 开始）
	 * @return string 插入数据统计卡片后的 HTML
	 */
	private function insert_stats_cards_into_content(string $content, array $cards, int $paragraphIndex): string
	{
		try {
			if (empty($cards)) {
				return $content;
			}
			$paragraphIndex = max(1, (int)$paragraphIndex);

			libxml_use_internal_errors(true);
			$dom = new \DOMDocument('1.0', 'UTF-8');
			// 保证 UTF-8，并用 div 包裹内容，便于插入
			if (function_exists('mb_detect_encoding') && mb_detect_encoding($content, 'UTF-8', true) === false) {
				$content = mb_convert_encoding($content, 'UTF-8');
			}
			$wrapped = '<div>' . $content . '</div>';
			$encoded = function_exists('mb_convert_encoding')
				? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8')
				: $wrapped;
			$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
			libxml_clear_errors();

			$xpath = new \DOMXPath($dom);
			$nodes = $xpath->query('//p');
			$validParas = [];
			if ($nodes instanceof \DOMNodeList) {
				foreach ($nodes as $p) {
					if (!($p instanceof \DOMElement)) {
						continue;
					}
					// 额外防护：跳过已位于特性卡片或数据统计卡片网格内部的段落
					$ancestor = $p->parentNode;
					$insideAnyGrid = false;
					while ($ancestor instanceof \DOMElement) {
						$classAttr = $ancestor->getAttribute('class');
						if ($classAttr && (strpos($classAttr, 'ai-feature-grid') !== false || strpos($classAttr, 'ai-stats-grid') !== false)) {
							$insideAnyGrid = true;
							break;
						}
						$ancestor = $ancestor->parentNode;
					}
					if ($insideAnyGrid) {
						continue;
					}
					// 跳过包含图片或表格的段落
					if ($p->getElementsByTagName('img')->length > 0) {
						continue;
					}
					if ($p->getElementsByTagName('table')->length > 0) {
						continue;
					}
					$validParas[] = $p;
				}
			}

			// 优先使用 <body> 作为根容器，其次退回 documentElement
			$body = $dom->getElementsByTagName('body')->item(0);
			$root = $body instanceof \DOMElement ? $body : $dom->documentElement;
			$targetParent = null;
			$refNode = null;

			if (!empty($validParas)) {
				$idx = min($paragraphIndex, count($validParas)) - 1; // 0-based
				$targetP = $validParas[$idx];
				$targetParent = $targetP->parentNode;
				$refNode = $targetP->nextSibling;
			} else {
				$targetParent = $root;
				$refNode = null;
			}

			if (!$targetParent instanceof \DOMNode) {
				return $content;
			}

			// 构建数据统计卡片网格（最多 4 条）
			$grid = $dom->createElement('div');
			$grid->setAttribute('class', 'ai-stats-grid');
			$maxCards = min(4, count($cards));
			for ($i = 0; $i < $maxCards; $i++) {
				$cardData = $cards[$i];
				if (!is_array($cardData)) {
					continue;
				}
				$value = isset($cardData['value']) ? (string)$cardData['value'] : '';
				$label = isset($cardData['label']) ? (string)$cardData['label'] : '';

				$card = $dom->createElement('div');
				$card->setAttribute('class', 'ai-stats-card');

				$valueEl = $dom->createElement('div');
				$valueEl->setAttribute('class', 'ai-stats-value');
				$valueEl->appendChild($dom->createTextNode($value));

				$labelEl = $dom->createElement('div');
				$labelEl->setAttribute('class', 'ai-stats-label');
				$labelEl->appendChild($dom->createTextNode($label));

				$card->appendChild($valueEl);
				$card->appendChild($labelEl);
				$grid->appendChild($card);
			}

			if ($refNode) {
				$targetParent->insertBefore($grid, $refNode);
			} else {
				$targetParent->appendChild($grid);
			}

			// 取根容器子节点的 HTML 作为新正文
			$result = '';
			if ($root) {
				foreach ($root->childNodes as $child) {
					$result .= $dom->saveHTML($child);
				}
			}
			$result = html_entity_decode($result, ENT_QUOTES | ENT_HTML5, 'UTF-8');
			if ($result !== '' && strpos($result, 'ai-stats-grid') !== false) {
				Logger::debug('insert_stats_cards: 成功生成 ai-stats-grid 容器', 'Cards');
			}
			return $result !== '' ? $result : $content;
		} catch (\Throwable $e) {
			return $content;
		}
	}

	/**
	 * 将实用技巧提示卡插入到正文 HTML 中第 N 个有效段落之后。
	 * 有效段落：不包含 <img> 或 <table> 标签的 <p> 元素，且不位于特性卡片 / 数据统计卡片 / 实用技巧提示卡容器内部。
	 *
	 * @param string $content 当前文章 HTML 内容
	 * @param string $tipText 实用技巧提示文案
	 * @param int    $paragraphIndex 目标段落序号（从 1 开始）
	 * @param string $labelText 提示卡标题文本，随任务语言切换
	 * @return string 插入提示卡后的 HTML
	 */
	private function insert_tips_card_into_content(string $content, string $tipText, int $paragraphIndex, string $labelText = '实用技巧'): string
	{
		try {
			$tipText = trim($tipText);
			if ($tipText === '') {
				return $content;
			}
			$paragraphIndex = max(1, (int)$paragraphIndex);

			libxml_use_internal_errors(true);
			$dom = new \DOMDocument('1.0', 'UTF-8');
			// 保证 UTF-8，并用 div 包裹内容，便于插入
			if (function_exists('mb_detect_encoding') && mb_detect_encoding($content, 'UTF-8', true) === false) {
				$content = mb_convert_encoding($content, 'UTF-8');
			}
			$wrapped = '<div>' . $content . '</div>';
			$encoded = function_exists('mb_convert_encoding')
				? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8')
				: $wrapped;
			$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
			libxml_clear_errors();

			$xpath = new \DOMXPath($dom);
			$nodes = $xpath->query('//p');
			$validParas = [];
			if ($nodes instanceof \DOMNodeList) {
				foreach ($nodes as $p) {
					if (!($p instanceof \DOMElement)) {
						continue;
					}
					// 跳过已位于任意卡片容器内部的段落
					$ancestor = $p->parentNode;
					$insideAnyCard = false;
					while ($ancestor instanceof \DOMElement) {
						$classAttr = $ancestor->getAttribute('class');
						if ($classAttr && (strpos($classAttr, 'ai-feature-grid') !== false || strpos($classAttr, 'ai-stats-grid') !== false || strpos($classAttr, 'ai-tips-card') !== false)) {
							$insideAnyCard = true;
							break;
						}
						$ancestor = $ancestor->parentNode;
					}
					if ($insideAnyCard) {
						continue;
					}
					// 跳过包含图片或表格的段落
					if ($p->getElementsByTagName('img')->length > 0) {
						continue;
					}
					if ($p->getElementsByTagName('table')->length > 0) {
						continue;
					}
					$validParas[] = $p;
				}
			}

			// 优先使用 <body> 作为根容器，其次退回 documentElement
			$body = $dom->getElementsByTagName('body')->item(0);
			$root = $body instanceof \DOMElement ? $body : $dom->documentElement;
			$targetParent = null;
			$refNode = null;

			if (!empty($validParas)) {
				$idx = min($paragraphIndex, count($validParas)) - 1; // 0-based
				$targetP = $validParas[$idx];
				$targetParent = $targetP->parentNode;
				$refNode = $targetP->nextSibling;
			} else {
				$targetParent = $root;
				$refNode = null;
			}

			if (!$targetParent instanceof \DOMNode) {
				return $content;
			}

			// 构建实用技巧提示卡
			$card = $dom->createElement('div');
			$card->setAttribute('class', 'ai-tips-card');

			$iconDiv = $dom->createElement('div');
			$iconDiv->setAttribute('class', 'ai-tips-icon');
			$iconDiv->appendChild($dom->createTextNode('💡'));

			$bodyDiv = $dom->createElement('div');
			$bodyDiv->setAttribute('class', 'ai-tips-body');

			$labelEl = $dom->createElement('div');
			$labelEl->setAttribute('class', 'ai-tips-label');
			$labelEl->appendChild($dom->createTextNode($labelText));

			$textEl = $dom->createElement('p');
			$textEl->setAttribute('class', 'ai-tips-text');
			$textEl->appendChild($dom->createTextNode($tipText));

			$bodyDiv->appendChild($labelEl);
			$bodyDiv->appendChild($textEl);
			$card->appendChild($iconDiv);
			$card->appendChild($bodyDiv);

			if ($refNode) {
				$targetParent->insertBefore($card, $refNode);
			} else {
				$targetParent->appendChild($card);
			}

			// 取根容器子节点的 HTML 作为新正文
			$result = '';
			if ($root) {
				foreach ($root->childNodes as $child) {
					$result .= $dom->saveHTML($child);
				}
			}
			$result = html_entity_decode($result, ENT_QUOTES | ENT_HTML5, 'UTF-8');
			if ($result !== '' && strpos($result, 'ai-tips-card') !== false) {
				Logger::debug('insert_tips_card: 成功生成 ai-tips-card 容器', 'Cards');
			}
			return $result !== '' ? $result : $content;
		} catch (\Throwable $e) {
			return $content;
		}
	}

	private function maybe_generate_and_insert_fallback_table(string $content, string $title, $chat_gpt, int $taskKey): string
	{
		try {
			if (trim($title) === '') {
				return $content;
			}
			if (strpos($content, '<table') !== false || strpos($content, 'ai-fallback-table') !== false) {
				return $content;
			}
			$user_language = $this->settings['tasks'][$taskKey]['post-tags-language'] ?? 'tags-zh';
			$language_map = LanguageUtils::get_language_map();
			if ($user_language === 'custom') {
				$custom_language_description = $this->settings['tasks'][$taskKey]['custom-language-description'] ?? '';
				$display_language = $custom_language_description !== '' ? $custom_language_description : '自定义语言';
			} else {
				$display_language = $language_map[$user_language] ?? '中文简体';
			}
			$plain_excerpt = '';
			try {
				$plain_excerpt = wp_strip_all_tags($content);
				$plain_excerpt = trim(preg_replace('/\s+/', ' ', $plain_excerpt));
				$plain_excerpt = mb_substr($plain_excerpt, 0, 600);
			} catch (\Throwable $e) {
				$plain_excerpt = '';
			}
			$format_desc = '使用标准 HTML <table> 结构，包含 <thead>、<tbody>、<tr>、<th>、<td> 等标签。';
			$prompt = "你现在只负责根据文章信息生成一个与主题高度相关的数据表格。\n\n"
				."【文章标题】\n{$title}\n\n"
				.(!empty($plain_excerpt) ? "【文章摘要（截断）】\n{$plain_excerpt}\n\n" : '')
				."【生成要求】\n"
				."1. 表格语言请使用{$display_language}。\n"
				."2. 表格不超过 5 列、不超过 8 行，每列有清晰的列名。\n"
				."3. 仅输出表格本身，不要输出任何解释性文字、标题或前后段落。\n"
				."4. 表格格式：{$format_desc}\n";
			$temp = $chat_gpt->ask($prompt);
			$table_html = isset($temp['answer']) ? trim((string)$temp['answer']) : '';
			if ($table_html === '') {
				return $content;
			}
			libxml_use_internal_errors(true);
			$dom = new \DOMDocument('1.0', 'UTF-8');
			if (function_exists('mb_detect_encoding') && mb_detect_encoding($content, 'UTF-8', true) === false) {
				$content = mb_convert_encoding($content, 'UTF-8');
			}
			$wrapped = '<div>' . $content . '</div>';
			$encoded = function_exists('mb_convert_encoding') ? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8') : $wrapped;
			$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
			libxml_clear_errors();
			$xpath = new \DOMXPath($dom);
			$nodes = $xpath->query('//p');
			$validParas = [];
			if ($nodes instanceof \DOMNodeList) {
				foreach ($nodes as $p) {
					if (!($p instanceof \DOMElement)) {
						continue;
					}
					$ancestor = $p->parentNode;
					$insideRestricted = false;
					while ($ancestor instanceof \DOMElement) {
						$classAttr = $ancestor->getAttribute('class');
						if ($classAttr && (strpos($classAttr, 'ai-feature-grid') !== false || strpos($classAttr, 'ai-stats-grid') !== false || strpos($classAttr, 'ai-tips-card') !== false || strpos($classAttr, 'toc-') !== false || strpos($classAttr, 'table-of-contents') !== false || strpos($classAttr, 'ai-faq') !== false || strpos($classAttr, 'faq-') !== false)) {
							$insideRestricted = true;
							break;
						}
						$ancestor = $ancestor->parentNode;
					}
					if ($insideRestricted) {
						continue;
					}
					if ($p->getElementsByTagName('img')->length > 0) {
						continue;
					}
					if ($p->getElementsByTagName('table')->length > 0) {
						continue;
					}
					$validParas[] = $p;
				}
			}
			$body = $dom->getElementsByTagName('body')->item(0);
			$root = $body instanceof \DOMElement ? $body : $dom->documentElement;
			$targetParent = null;
			$refNode = null;
			if (!empty($validParas)) {
				$idx = (int)floor(count($validParas) / 2);
				if ($idx >= count($validParas)) {
					$idx = count($validParas) - 1;
				}
				$targetP = $validParas[$idx];
				$targetParent = $targetP->parentNode;
				$refNode = $targetP->nextSibling;
			} else {
				$targetParent = $root;
				$refNode = null;
			}
			if (!$targetParent instanceof \DOMNode) {
				return $content;
			}
			$tableWrapper = $dom->createElement('div');
			$tableWrapper->setAttribute('class', 'ai-fallback-table');
			$fragment = $dom->createDocumentFragment();
			$fragment->appendXML($table_html);
			$tableWrapper->appendChild($fragment);
			if ($refNode) {
				$targetParent->insertBefore($tableWrapper, $refNode);
			} else {
				$targetParent->appendChild($tableWrapper);
			}
			$result = '';
			if ($root) {
				foreach ($root->childNodes as $child) {
					$result .= $dom->saveHTML($child);
				}
			}
			$result = html_entity_decode($result, ENT_QUOTES | ENT_HTML5, 'UTF-8');
			if ($result !== '' && strpos($result, 'ai-fallback-table') !== false) {
				return $result;
			}
			return $content;
		} catch (\Throwable $e) {
			return $content;
		}
	}

	/**
	 * 清洗正文中未包裹在 <ul>/<ol> 中的孤立 <li>，避免浏览器自动纠正导致段落被意外打断。
	 *
	 * 处理策略：
	 * - 仅当 <li> 的父节点不是 ul/ol 时才处理；
	 * - 使用一个新的 <p> 元素替换该 <li>，并将其所有子节点迁移到新的 <p> 中；
	 * - 保持其它结构不变。
	 */
	private function sanitize_orphan_list_items(string $content): string
	{
		if (strpos($content, '<li') === false) {
			return $content;
		}

		libxml_use_internal_errors(true);
		$dom = new \DOMDocument('1.0', 'UTF-8');
		try {
			// 包一层根节点，避免片段解析问题
			$wrapped = '<div>' . $content . '</div>';
			$encoded = function_exists('mb_convert_encoding')
				? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8')
				: $wrapped;
			$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
			libxml_clear_errors();

			$lis = $dom->getElementsByTagName('li');
			if (!$lis instanceof \DOMNodeList || $lis->length === 0) {
				return $content;
			}

			// 由于 getElementsByTagName 返回的是实时 NodeList，先复制一份数组再操作
			$targets = [];
			foreach ($lis as $li) {
				if ($li instanceof \DOMElement) {
					$targets[] = $li;
				}
			}

			foreach ($targets as $li) {
				$parent = $li->parentNode;
				if (!$parent instanceof \DOMElement) {
					continue;
				}
				$tag = strtolower($parent->tagName);
				if ($tag === 'ul' || $tag === 'ol') {
					// 合法列表项，跳过
					continue;
				}

				// 将孤立 <li> 转换为一个普通段落 <p>
				$newP = $dom->createElement('p');
				// 迁移所有子节点
				while ($li->firstChild) {
					$newP->appendChild($li->firstChild);
				}
				$parent->replaceChild($newP, $li);
			}

			$body = $dom->getElementsByTagName('body')->item(0);
			$root = $body instanceof \DOMElement ? $body : $dom->documentElement;
			$result = '';
			if ($root) {
				foreach ($root->childNodes as $child) {
					$result .= $dom->saveHTML($child);
				}
			}
			$result = html_entity_decode($result, ENT_QUOTES | ENT_HTML5, 'UTF-8');
			return $result !== '' ? $result : $content;
		} catch (\Throwable $e) {
			// 解析或清洗失败时，返回原始内容以避免破坏
			return $content;
		} finally {
			libxml_clear_errors();
		}
	}

	/**
	 * 处理随机段落内容
	 * 格式为 **段落一**|**段落二**|**段落三**
	 * 
	 * @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];
				
				Logger::debug("随机段落: 从 " . count($matches[1]) . " 个段落中选择了第 " . ($random_index + 1) . " 个", 'Cron');
				
				// 返回选中的段落
				return $selected_paragraph;
			}
		}
		
		// 如果没有使用特殊格式或解析失败，则返回原内容
		return $content;
	}

	public function aipost_ai_comments_add_avatar_comment_type($comment_types)
	{
		if (!is_array($comment_types)) {
			$comment_types = [];
		}
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		$avatar_enabled = (bool)($this->settings['global_tabs']['ai_comments_enable_avatar'] ?? false);
		if ($enabled && $avatar_enabled && !in_array('ai_comment', $comment_types, true)) {
			$comment_types[] = 'ai_comment';
		}
		return $comment_types;
	}

	public function aipost_ai_comments_pre_get_avatar_data($args, $id_or_email)
	{
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		$avatar_enabled = (bool)($this->settings['global_tabs']['ai_comments_enable_avatar'] ?? false);
		if (!$enabled || !$avatar_enabled) {
			return $args;
		}
		$avatar_url = '';
		if (is_object($id_or_email) && $id_or_email instanceof \WP_Comment) {
			if ($id_or_email->comment_type === 'ai_comment') {
				$meta = \get_comment_meta((int)$id_or_email->comment_ID, '_aipost_ai_avatar', true);
				if (is_string($meta) && $meta !== '') {
					$avatar_url = $meta;
				}
			}
		} elseif (is_object($id_or_email) && $id_or_email instanceof \WP_User) {
			$meta = \get_user_meta((int)$id_or_email->ID, '_aipost_ai_avatar', true);
			if (is_string($meta) && $meta !== '') {
				$avatar_url = $meta;
			}
		} elseif (is_numeric($id_or_email)) {
			$meta = \get_user_meta((int)$id_or_email, '_aipost_ai_avatar', true);
			if (is_string($meta) && $meta !== '') {
				$avatar_url = $meta;
			}
		}
		if ($avatar_url !== '' && isset($args['url'])) {
			$args['url'] = esc_url($avatar_url);
		}
		return $args;
	}

	public function aipost_ai_comments_filter_get_avatar($avatar, $id_or_email, $size, $default, $alt, $args)
	{
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		$avatar_enabled = (bool)($this->settings['global_tabs']['ai_comments_enable_avatar'] ?? false);
		if (!$enabled || !$avatar_enabled) {
			return $avatar;
		}
		$comment = null;
		if (is_object($id_or_email) && $id_or_email instanceof \WP_Comment) {
			$comment = $id_or_email;
		} elseif (is_object($id_or_email) && isset($id_or_email->comment_ID)) {
			$maybe = \get_comment((int)$id_or_email->comment_ID);
			if ($maybe instanceof \WP_Comment) {
				$comment = $maybe;
			}
		}
		if (!$comment || !($comment instanceof \WP_Comment)) {
			return $avatar;
		}
		if ($comment->comment_type !== 'ai_comment') {
			return $avatar;
		}
		$url = (string)\get_comment_meta((int)$comment->comment_ID, '_aipost_ai_avatar', true);
		if ($url === '' && !empty($comment->user_id)) {
			$url = (string)\get_user_meta((int)$comment->user_id, '_aipost_ai_avatar', true);
		}
		if ($url === '' && defined('HaoZiTeam\\AIPost\\PLUGIN_FILE')) {
			$url = \plugins_url('Service/assets/images/icon.png', \HaoZiTeam\AIPost\PLUGIN_FILE);
		}
		$url = trim($url);
		if ($url === '') {
			return $avatar;
		}
		$size = (int)$size;
		if ($size <= 0) { $size = 48; }
		$alt_text = is_string($alt) ? $alt : '';
		return sprintf(
			'<img alt="%s" src="%s" class="avatar avatar-%d photo" height="%d" width="%d" loading="lazy" decoding="async" />',
			esc_attr($alt_text),
			esc_url($url),
			$size,
			$size,
			$size
		);
	}

	private function maybe_schedule_ai_comments(int $post_id, string $task_key, string $title, string $content, string $ai_summary_text_plain = ''): void
	{
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		if (!$enabled) {
			return;
		}
		if (!$this->is_task_ai_comments_enabled($task_key)) {
			$this->cleanup_ai_comments_for_post($post_id, $task_key);
			return;
		}
		if ($post_id <= 0 || $task_key === '') {
			return;
		}
		$existing_plan = \get_post_meta($post_id, '_aipost_ai_comments_plan', true);
		if (is_string($existing_plan) && $existing_plan !== '') {
			$decoded = json_decode($existing_plan, true);
			if (is_array($decoded) && !empty($decoded['status']) && $decoded['status'] === 'active') {
				return;
			}
		}
		$min = (int)($this->settings['global_tabs']['ai_comments_total_min'] ?? 6);
		$max = (int)($this->settings['global_tabs']['ai_comments_total_max'] ?? 12);
		if ($min < 0) { $min = 0; }
		if ($max < 0) { $max = 0; }
		if ($max < $min) { $tmp = $min; $min = $max; $max = $tmp; }
		$total = ($max === $min) ? $min : \wp_rand($min, $max);
		if ($total <= 0) {
			return;
		}
		$days = (int)($this->settings['global_tabs']['ai_comments_days'] ?? 3);
		if ($days < 1) { $days = 1; }
		$now = time();
		$end = $now + ($days * 86400);
		$times = [];
		for ($i = 0; $i < $total; $i++) {
			$ts = \wp_rand($now + 120, $end);
			$times[] = $ts;
		}
		sort($times);
		$pack = $this->build_ai_comment_pack($title, $content, $ai_summary_text_plain);
		$plan = [
			'v' => 1,
			'status' => 'active',
			'post_id' => $post_id,
			'task_key' => $task_key,
			'remaining' => $total,
			'times' => $times,
			'pack' => $pack,
			'created_at' => $now,
		];
		\update_post_meta($post_id, '_aipost_ai_comments_plan', \wp_json_encode($plan, JSON_UNESCAPED_UNICODE));
		if (!\wp_next_scheduled('aipost_ai_comments_generate', [$post_id, $task_key])) {
			\wp_schedule_single_event($now + 180, 'aipost_ai_comments_generate', [$post_id, $task_key]);
		}
	}

	private function build_ai_comment_pack(string $title, string $content, string $ai_summary_text_plain = ''): array
	{
		$title = trim($title);
		$summary = trim($ai_summary_text_plain);
		$plain = trim(preg_replace('/\s+/u', ' ', \wp_strip_all_tags($content)));
		if ($summary === '' && $plain !== '') {
			$summary = mb_substr($plain, 0, 160);
		}
		$outline = [];
		if ($content !== '') {
			try {
				libxml_use_internal_errors(true);
				$dom = new \DOMDocument();
				$wrapped = '<div>' . $content . '</div>';
				$encoded = function_exists('mb_convert_encoding') ? mb_convert_encoding($wrapped, 'HTML-ENTITIES', 'UTF-8') : $wrapped;
				$dom->loadHTML($encoded, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
				libxml_clear_errors();
				$xp = new \DOMXPath($dom);
				foreach (['h2', 'h3'] as $hx) {
					$nodes = $xp->query('//' . $hx);
					foreach ($nodes as $n) {
						$t = trim($n->textContent);
						if ($t !== '') { $outline[] = $t; }
					}
				}
			} catch (\Throwable $e) {
				$outline = [];
			}
		}
		if (count($outline) > 18) {
			$outline = array_slice($outline, 0, 18);
		}
		if ($plain !== '' && mb_strlen($plain) > 1200) {
			$plain = mb_substr($plain, 0, 1200);
		}
		return [
			'title' => $title,
			'summary' => $summary,
			'outline' => $outline,
			'plain' => $plain,
		];
	}

	public function aipost_ai_comments_generate($post_id, $task_key): void
	{
		$post_id = (int)$post_id;
		$task_key = (string)$task_key;
		if ($post_id <= 0 || $task_key === '') {
			return;
		}
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		if (!$enabled) {
			return;
		}
		if (!$this->is_task_ai_comments_enabled($task_key)) {
			$this->cleanup_ai_comments_for_post($post_id, $task_key);
			return;
		}
		$lock_key = 'aipost_ai_comments_lock_' . $post_id;
		if (function_exists('get_transient') && \get_transient($lock_key)) {
			return;
		}
		if (function_exists('set_transient')) {
			\set_transient($lock_key, 1, 300);
		}
		try {
			$raw = \get_post_meta($post_id, '_aipost_ai_comments_plan', true);
			$plan = is_string($raw) ? json_decode($raw, true) : null;
			if (!is_array($plan) || empty($plan['status']) || $plan['status'] !== 'active') {
				return;
			}
			$remaining = (int)($plan['remaining'] ?? 0);
			$times = is_array($plan['times'] ?? null) ? $plan['times'] : [];
			if ($remaining <= 0 || empty($times)) {
				$plan['status'] = 'done';
				$plan['remaining'] = 0;
				$plan['times'] = [];
				\update_post_meta($post_id, '_aipost_ai_comments_plan', \wp_json_encode($plan, JSON_UNESCAPED_UNICODE));
				return;
			}
			$max_per_post = (int)($this->settings['global_tabs']['ai_comments_max_per_post'] ?? 50);
			if ($max_per_post > 0) {
				$existing_total = 0;
				try {
					$existing_total = (int)\get_comments(['post_id' => $post_id, 'count' => true, 'status' => 'all']);
				} catch (\Throwable $e) {
					$existing_total = 0;
				}
				if ($existing_total >= $max_per_post) {
					$plan['status'] = 'done';
					$plan['remaining'] = 0;
					$plan['times'] = [];
					\update_post_meta($post_id, '_aipost_ai_comments_plan', \wp_json_encode($plan, JSON_UNESCAPED_UNICODE));
					return;
				}
			}
			$batch_size = (int)($this->settings['global_tabs']['ai_comments_batch_size'] ?? 4);
			if ($batch_size < 1) { $batch_size = 1; }
			$to_generate = min($batch_size, $remaining, count($times));
			if ($to_generate <= 0) {
				return;
			}
			$post = \get_post($post_id);
			if (!$post) {
				return;
			}
			$task = $this->settings['tasks'][$task_key] ?? [];
			$chat_gpt = ApiClientService::create_api_client($this->settings, $task_key, $task);
			if (!$chat_gpt) {
				return;
			}
			$user_language = $this->settings['tasks'][$task_key]['post-tags-language'] ?? 'tags-zh';
			$language_map = LanguageUtils::get_language_map();
			if ($user_language === 'custom') {
				$custom_desc = $this->settings['tasks'][$task_key]['custom-language-description'] ?? '';
				$display_language = $custom_desc !== '' ? $custom_desc : '目标语言';
			} else {
				$display_language = $language_map[$user_language] ?? '中文简体';
			}
			$system_prompt = $this->settings['tasks'][$task_key]['system-prompt'] ?? $this->settings['global_tabs']['system-prompt'] ?? '';
			if (method_exists($chat_gpt, 'clearMessages')) {
				$chat_gpt->clearMessages();
			}
			if ($system_prompt) {
				$chat_gpt->addMessage($system_prompt, 'system');
			}
			$pack = is_array($plan['pack'] ?? null) ? $plan['pack'] : $this->build_ai_comment_pack((string)$post->post_title, (string)$post->post_content, '');
			$existing = [];
			try {
				$existing_comments = \get_comments([
					'post_id' => $post_id,
					'status' => ['approve', 'hold'],
					'number' => 8,
					'orderby' => 'comment_ID',
					'order' => 'DESC',
				]);
				if (is_array($existing_comments)) {
					foreach ($existing_comments as $c) {
						if ($c instanceof \WP_Comment) {
							$t = trim(preg_replace('/\s+/u', ' ', (string)$c->comment_content));
							if ($t !== '') {
								$existing[] = mb_substr($t, 0, 60);
							}
						}
					}
				}
			} catch (\Throwable $e) {
				$existing = [];
			}
			$roles = ['新手提问', '经验补充', '赞同总结', '提醒避坑', '建议优化'];
			$roles_pick = [];
			for ($i = 0; $i < $to_generate; $i++) {
				$roles_pick[] = $roles[array_rand($roles)];
			}
			$payload = [
				'title' => (string)($pack['title'] ?? $post->post_title),
				'summary' => (string)($pack['summary'] ?? ''),
				'outline' => (array)($pack['outline'] ?? []),
				'plain' => (string)($pack['plain'] ?? ''),
			];
			$prompt = "你是网站读者，阅读完文章后要写评论。请基于给定的文章信息生成 {$to_generate} 条{$display_language}评论。\n"
				. "要求：\n"
				. "1) 每条 20~60 字（或同等长度），自然口语，不要套话，不要以‘本文/这篇文章’开头；\n"
				. "2) 不要换行，不要表情符号，不要#标签，不要编号；\n"
				. "3) 评论必须紧扣文章要点，避免编造文章中没有的信息；\n"
				. "4) 只输出 JSON 数组，每个元素包含字段 content（必填）和 name（可选）；\n"
				. "5) 尽量避免与已存在评论重复。\n\n"
				. "【文章信息】\n" . \wp_json_encode($payload, JSON_UNESCAPED_UNICODE) . "\n\n"
				. "【本次角色偏好】\n" . implode(' / ', $roles_pick) . "\n\n"
				. "【已存在评论（节选）】\n" . (empty($existing) ? '无' : implode('；', $existing)) . "\n";
			$resp = $chat_gpt->ask($prompt);
			if (isset($resp['usage'])) {
				Logger::debug('Token Usage (AIComments - Post ' . $post_id . '): ' . json_encode($resp['usage']), 'AIComments');
			}
			$answer = trim((string)($resp['answer'] ?? ''));
			$items = $this->extract_json_array($answer);
			if (empty($items)) {
				return;
			}
			$bind_user = (bool)($this->settings['global_tabs']['ai_comments_bind_user'] ?? false);
			$enable_avatar = (bool)($this->settings['global_tabs']['ai_comments_enable_avatar'] ?? false);
			$auto_approve = (bool)($this->settings['global_tabs']['ai_comments_auto_approve'] ?? false);
			$default_avatar = '';
			if ($enable_avatar && defined('HaoZiTeam\\AIPost\\PLUGIN_FILE')) {
				$default_avatar = \plugins_url('Service/assets/images/icon.png', \HaoZiTeam\AIPost\PLUGIN_FILE);
			}
			$inserted = 0;
			$used_times = [];
			$existing_contents = [];
			foreach ($existing as $e) { $existing_contents[] = trim($e); }
			foreach ($items as $it) {
				if ($inserted >= $to_generate) { break; }
				if (!is_array($it)) { continue; }
				$text = trim((string)($it['content'] ?? ''));
				$text = preg_replace('/\s+/u', ' ', $text);
				$text = trim($text);
				if ($text === '') { continue; }
				if (in_array($text, $existing_contents, true)) { continue; }
				$existing_contents[] = $text;
				$name = trim((string)($it['name'] ?? ''));
				if ($name === '') {
					$name = $this->random_ai_comment_author((string)$user_language);
				}
				$ts = array_shift($times);
				if (!$ts) {
					$ts = time() + 300;
				}
				$comment_date = \date('Y-m-d H:i:s', (int)$ts);
				$user_id = 0;
				if ($bind_user) {
					$user_id = (int)$this->get_or_create_ai_comment_user($name, $enable_avatar ? $default_avatar : '');
				}
				$commentdata = [
					'comment_post_ID' => $post_id,
					'comment_author' => $name,
					'comment_author_email' => $this->generate_ai_comment_email($name),
					'comment_content' => $text,
					'comment_type' => 'ai_comment',
					'comment_approved' => $auto_approve ? 1 : 0,
					'comment_date' => $comment_date,
					'comment_date_gmt' => \get_gmt_from_date($comment_date),
					'user_id' => $user_id,
				];
				$comment_id = \wp_insert_comment($commentdata);
				if ($comment_id) {
					if ($enable_avatar) {
						$avatar_for_comment = '';
						if ($user_id > 0) {
							$avatar_for_comment = (string)\get_user_meta((int)$user_id, '_aipost_ai_avatar', true);
						}
						if ($avatar_for_comment === '' && $default_avatar !== '') {
							$avatar_for_comment = $default_avatar;
						}
						$avatar_for_comment = trim($avatar_for_comment);
						if ($avatar_for_comment !== '') {
							\add_comment_meta((int)$comment_id, '_aipost_ai_avatar', esc_url($avatar_for_comment), true);
						}
					}
					\add_comment_meta((int)$comment_id, '_aipost_ai_comment', '1', true);
					\add_comment_meta((int)$comment_id, '_aipost_ai_task', $task_key, true);
					if ($auto_approve) {
						$approve_at = max(time() + 60, (int)$ts);
						\wp_schedule_single_event($approve_at, 'aipost_ai_comments_approve_comment', [(int)$comment_id]);
					}
					$inserted++;
					$used_times[] = (int)$ts;
				}
			}
			$plan['remaining'] = max(0, $remaining - $inserted);
			$plan['times'] = array_values($times);
			if ($plan['remaining'] <= 0 || empty($plan['times'])) {
				$plan['status'] = 'done';
				$plan['remaining'] = 0;
				$plan['times'] = [];
			}
			\update_post_meta($post_id, '_aipost_ai_comments_plan', \wp_json_encode($plan, JSON_UNESCAPED_UNICODE));
			if ($plan['status'] === 'active' && $plan['remaining'] > 0) {
				$interval = (int)($this->settings['global_tabs']['ai_comments_generation_interval_minutes'] ?? 10);
				if ($interval < 1) { $interval = 1; }
				$next = time() + ($interval * 60);
				if (!\wp_next_scheduled('aipost_ai_comments_generate', [$post_id, $task_key])) {
					\wp_schedule_single_event($next, 'aipost_ai_comments_generate', [$post_id, $task_key]);
				}
			}
		} finally {
			if (function_exists('delete_transient')) {
				\delete_transient($lock_key);
			}
		}
	}

	public function aipost_ai_comments_approve_comment($comment_id): void
	{
		$enabled = (bool)($this->settings['global_tabs']['enable_ai_comments'] ?? false);
		$auto_approve = (bool)($this->settings['global_tabs']['ai_comments_auto_approve'] ?? false);
		if (!$enabled || !$auto_approve) {
			return;
		}
		$comment_id = (int)$comment_id;
		if ($comment_id <= 0) { return; }
		$comment = \get_comment($comment_id);
		if (!$comment || !($comment instanceof \WP_Comment)) { return; }
		if ($comment->comment_type !== 'ai_comment') { return; }
		if ((string)$comment->comment_approved === '1') { return; }
		\wp_set_comment_status($comment_id, 'approve');
	}

	public function aipost_ai_comments_task_cleanup($task_key, $page = 1): void
	{
		$task_key = (string)$task_key;
		$page = (int)$page;
		if ($task_key === '' || $page < 1) {
			return;
		}
		$lock_key = 'aipost_ai_comments_task_cleanup_lock_' . md5($task_key);
		if (function_exists('get_transient') && \get_transient($lock_key)) {
			return;
		}
		if (function_exists('set_transient')) {
			\set_transient($lock_key, 1, 60);
		}
		try {
			$batch = 30;
			$q = new \WP_Query([
				'post_type' => 'any',
				'post_status' => 'any',
				'fields' => 'ids',
				'posts_per_page' => $batch,
				'paged' => $page,
				'no_found_rows' => false,
				'meta_query' => [
					[
						'key' => '_ai_post_task',
						'value' => $task_key,
						'compare' => '=',
					]
				],
			]);
			$ids = is_array($q->posts ?? null) ? $q->posts : [];
			foreach ($ids as $pid) {
				$this->cleanup_ai_comments_for_post((int)$pid, $task_key);
			}
			$max_pages = (int)($q->max_num_pages ?? 0);
			if ($max_pages > $page) {
				\wp_schedule_single_event(time() + 10, 'aipost_ai_comments_task_cleanup', [$task_key, $page + 1]);
			}
		} catch (\Throwable $e) {
			Logger::error('AIComments task cleanup run error: ' . $e->getMessage(), 'AIComments');
		} finally {
			if (function_exists('delete_transient')) {
				\delete_transient($lock_key);
			}
		}
	}

	private function is_task_ai_comments_enabled(string $task_key): bool
	{
		$task_key = (string)$task_key;
		if ($task_key === '') {
			return true;
		}
		$task = $this->settings['tasks'][$task_key] ?? null;
		if (!is_array($task)) {
			return true;
		}
		if (!array_key_exists('task-ai-comments', $task)) {
			return true;
		}
		return (bool)$task['task-ai-comments'];
	}

	private function cleanup_ai_comments_for_post(int $post_id, string $task_key): void
	{
		$post_id = (int)$post_id;
		$task_key = (string)$task_key;
		if ($post_id <= 0 || $task_key === '') {
			return;
		}
		try {
			// 取消计划
			\delete_post_meta($post_id, '_aipost_ai_comments_plan');
			// 取消已排程的生成事件
			while (true) {
				$ts = \wp_next_scheduled('aipost_ai_comments_generate', [$post_id, $task_key]);
				if (!$ts) { break; }
				\wp_unschedule_event((int)$ts, 'aipost_ai_comments_generate', [$post_id, $task_key]);
			}
			// 删除该任务生成的 AI 评论
			$comments = \get_comments([
				'post_id' => $post_id,
				'status' => 'all',
				'type' => 'ai_comment',
				'number' => 200,
				'meta_key' => '_aipost_ai_task',
				'meta_value' => $task_key,
				'fields' => 'ids',
			]);
			if (is_array($comments)) {
				foreach ($comments as $cid) {
					\wp_delete_comment((int)$cid, true);
				}
			}
		} catch (\Throwable $e) {
			Logger::error('AIComments cleanup post ' . $post_id . ' error: ' . $e->getMessage(), 'AIComments');
		}
	}

	private function extract_json_array(string $text): array
	{
		$text = trim($text);
		if ($text === '') { return []; }
		$start = strpos($text, '[');
		$end = strrpos($text, ']');
		if ($start === false || $end === false || $end <= $start) {
			return [];
		}
		$sub = substr($text, $start, $end - $start + 1);
		$decoded = json_decode($sub, true);
		if (is_array($decoded)) {
			return $decoded;
		}
		return [];
	}

	private function random_ai_comment_author(string $task_language = ''): string
	{
		if (class_exists('\\HaoZiTeam\\AIPost\\Service\\Features\\AiUserIdentityService')) {
			return \HaoZiTeam\AIPost\Service\Features\AiUserIdentityService::random_ai_comment_author($task_language);
		}
		return '热心网友' . (string)\wp_rand(10, 99);
	}

	private function generate_ai_comment_email(string $name): string
	{
		if (class_exists('\\HaoZiTeam\\AIPost\\Service\\Features\\AiUserIdentityService')) {
			return \HaoZiTeam\AIPost\Service\Features\AiUserIdentityService::generate_ai_comment_email($name);
		}
		$host = (string)\parse_url(\home_url(), PHP_URL_HOST);
		if ($host === '') { $host = 'example.com'; }
		$local = 'aipost_' . substr(md5((string)$name . '|' . $host), 0, 10);
		return $local . '@' . $host;
	}

	private function get_or_create_ai_comment_user(string $name, string $avatar_url = ''): int
	{
		$name = trim($name);
		if ($name === '') { return 0; }
		if (!function_exists('get_user_by')) { return 0; }
		if (!\get_role('aipost_aiuser')) {
			\add_role('aipost_aiuser', 'AI User', ['read' => true]);
		}
		$email = $this->generate_ai_comment_email($name);
		$existing = \email_exists($email);
		if ($existing) {
			$uid = (int)$existing;
			\HaoZiTeam\AIPost\Service\Features\AiAvatarService::ensure_ai_user_avatar($this->settings, $uid, $avatar_url);
			return $uid;
		}
		$user_login = (class_exists('\\HaoZiTeam\\AIPost\\Service\\Features\\AiUserIdentityService'))
			? \HaoZiTeam\AIPost\Service\Features\AiUserIdentityService::generate_ai_comment_user_login($email)
			: ('aipost_' . substr(md5($email), 0, 12));
		$uid = \wp_insert_user([
			'user_login' => $user_login,
			'user_email' => $email,
			'user_pass' => \wp_generate_password(12, false),
			'display_name' => $name,
			'nickname' => $name,
			'role' => 'aipost_aiuser',
		]);
		if (is_wp_error($uid)) {
			return 0;
		}
		$uid = (int)$uid;
		if ($uid > 0) {
			\HaoZiTeam\AIPost\Service\Features\AiAvatarService::ensure_ai_user_avatar($this->settings, $uid, $avatar_url);
		}
		return $uid;
	}
}
