<?php
/**
 * Tailwind 容器区块处理器
 * 
 * 功能说明：
 * 1. 注册区块的服务端渲染
 * 2. 提供 Tailwind 相关 REST（编译、图片搜索等）；HTML 生成仅经 workflow job `tailwind-generate`（见 class-puffergo-workflow-jobs-rest.php）
 *
 * @package PufferGo_Core
 * @since 2.1.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class PufferGo_Tailwind_Container {

    /**
     * AI 精准编辑区块接口端点
     */
    const EDIT_BLOCK_ENDPOINT = '/api/ai/edit-block';

    /**
     * 单例（供异步 Job 组装 generate-block 请求体，避免重复注册钩子）。
     *
     * @var self|null
     */
    private static $instance = null;

    /**
     * @return self
     */
    public static function instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 构造函数 - 注册所有钩子
     */
    private function __construct() {
        // 全局允许 data-* 属性通过 wp_kses（保存和渲染都需要）
        // 使用优先级 9999 确保在所有其他 filter 之后运行
        add_filter( 'wp_kses_allowed_html', array( $this, 'allow_data_attributes_in_kses' ), 9999, 2 );
        
        // 在 WordPress 初始化完成后立即修改全局 allowedposttags
        // 这样即使 WordPress 在 wp_kses_allowed_html 中重置 $tags，我们的修改仍然有效
        add_action( 'init', array( $this, 'modify_global_allowedposttags' ), 1 );
        // 再次在 999 优先级执行，确保覆盖任何其他插件的修改
        add_action( 'init', array( $this, 'modify_global_allowedposttags' ), 999 );

        // 始终用解析后的块数据重新渲染本块，避免缓存或属性裁切导致前端无样式/脚本
        add_filter( 'render_block', array( $this, 'filter_render_block_fresh' ), 10, 3 );

        // 注册区块服务端渲染（优先级 20，确保区块已注册）
        add_action( 'init', array( $this, 'register_render_callback' ), 20 );

        // 在 wp_head 之前入队区块样式（Option A：解析正文中的块，提前输出 CSS）
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_block_styles_in_head' ), 10 );

        // 注册 REST API 端点
        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ), 20 );

        // Tailwind 块编辑器：注入 workflow 轮询配置（generate 走异步 Job）
        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_workflow_job_poll_config' ), 25 );
    }

    /**
     * 为 tailwind-container 编辑器脚本注入 puffergoWorkflowJobPoll（inline JSON，避免 wp_localize_script 把数字转成字符串）。
     *
     * @return void
     */
    public function enqueue_workflow_job_poll_config() {
        if ( ! class_exists( 'PufferGo_Workflow_Jobs_REST' ) ) {
            return;
        }
        if ( ! function_exists( 'register_block_type' ) ) {
            return;
        }
        $registry = WP_Block_Type_Registry::get_instance();
        $block    = $registry->get_registered( 'puffergo/tailwind-container' );
        if ( ! $block || empty( $block->editor_script ) ) {
            return;
        }
        $handles = is_array( $block->editor_script ) ? $block->editor_script : array( $block->editor_script );
        foreach ( $handles as $handle ) {
            PufferGo_Workflow_Jobs_REST::add_poll_config_inline_script( $handle );
        }
    }

    /**
     * 始终用解析后的块数据重新渲染本块，避免使用可能被缓存或属性被裁切的旧输出。
     *
     * @param string   $block_content 已渲染的块 HTML（可能来自缓存或旧逻辑）
     * @param array    $block         解析后的块数组（来自 post_content，含完整 attrs）
     * @param WP_Block $wp_block      块实例
     * @return string
     */
    public function filter_render_block_fresh( $block_content, $block, $wp_block ) {
        if ( ! isset( $block['blockName'] ) || $block['blockName'] !== 'puffergo/tailwind-container' ) {
            return $block_content;
        }
        $attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
        $inner = isset( $block['innerHTML'] ) ? $block['innerHTML'] : '';
        $block_arr = array(
            'blockName' => 'puffergo/tailwind-container',
            'attrs'     => $attrs,
        );
        return $this->render_block( $attrs, $inner, $block_arr );
    }

    public function enqueue_frontend_scripts() {
        if ( is_admin() ) {
            return;
        }

        // 加载 Alpine 的场景：① 正文含 puffergo/tailwind-container  ② puffergo 模板布局命中（产品/博客归档或详情页）
        $need_alpine = false;

        // ① 正文含 puffergo/tailwind-container 区块
        if ( is_singular() ) {
            $post = get_queried_object();
            if ( $post && function_exists( 'has_block' ) && has_block( 'puffergo/tailwind-container', $post ) ) {
                $need_alpine = true;
            }
        }

        // ② 任意 puffergo 模板布局命中（产品/博客归档、详情页、fullpage 页面等）
        if ( ! $need_alpine && class_exists( 'PufferGo_Template_Loader' ) ) {
            if ( PufferGo_Template_Loader::is_puffergo_template() ) {
                $need_alpine = true;
            }
        }

        if ( ! $need_alpine ) {
            return;
        }

        $plugin_url  = plugin_dir_url( dirname( __FILE__ ) );
        $plugin_path = plugin_dir_path( dirname( __FILE__ ) );

        // Alpine 核心：fullpage 模板与含 tailwind-container 的页面均需加载，供 header-2/3 及 x-data/x-show 等内联交互使用。
        wp_enqueue_script(
            'puffergo-alpine',
            $plugin_url . 'assets/js/alpine.min.js',
            array(),
            '3.14.7',
            true
        );
        wp_script_add_data( 'puffergo-alpine', 'defer', true );

        // 联系表单 Ajax 提交：仅处理带 data-pg-submit="contact" 的表单。
        $contact_script = $plugin_path . 'assets/js/puffergo-contact-form.js';
        $contact_version = file_exists( $contact_script ) ? (string) filemtime( $contact_script ) : '1.0.0';
        wp_enqueue_script(
            'puffergo-contact-form',
            $plugin_url . 'assets/js/puffergo-contact-form.js',
            array(),
            $contact_version,
            true
        );
        wp_script_add_data( 'puffergo-contact-form', 'defer', true );
        wp_localize_script(
            'puffergo-contact-form',
            'puffergoContactForm',
            array(
                'endpoint'     => rest_url( 'puffergo/v1/contact' ),
                'i18n'         => array(
                    'submitSuccess' => __( 'Submitted successfully', 'puffergo' ),
                    'submitFailed'  => __( 'Submission failed', 'puffergo' ),
                ),
            )
        );
    }

    /**
     * 编译 Tailwind HTML 接口端点
     */
    const COMPILE_ENDPOINT = '/api/ai/compile-tailwind';

    /**
     * 注册 REST API 端点
     */
    public function register_rest_routes() {
        // Generic AI Generate Endpoint - Proxy to external API
        register_rest_route( 'puffergo/v1', '/ai/generate', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_ai_generate' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
        ) );

        // 编译用户导入的 Tailwind HTML
        register_rest_route( 'puffergo/v1', '/tailwind/compile', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_compile_html' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
            'args'                => array(
                'html' => array(
                    'required'          => true,
                    'type'              => 'string',
                ),
                'scopeId' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
            ),
        ) );

        // 图片搜索 API（返回预览用外链，不落入媒体库）
        register_rest_route( 'puffergo/v1', '/images/search', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_search_images' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
            'args'                => array(
                'keyword' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'width' => array(
                    'required'          => false,
                    'type'              => 'integer',
                    'default'           => null,
                    'sanitize_callback' => 'absint',
                ),
                'height' => array(
                    'required'          => false,
                    'type'              => 'integer',
                    'default'           => null,
                    'sanitize_callback' => 'absint',
                ),
            ),
        ) );

        // AI 图片 SEO 建议代理（转发到 siteForge /api/ai/image-seo-suggest）
        register_rest_route( 'puffergo/v1', '/ai/image-seo-suggest', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_image_seo_suggest' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
            'args'                => array(
                'imgKeyword' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'pageKeywords' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'siteKeywords' => array(
                    'required' => false,
                    'type'     => 'array',
                    'default'  => array(),
                    'items'    => array( 'type' => 'string' ),
                ),
                'language' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => 'en',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'contextText' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_textarea_field',
                ),
            ),
        ) );

        // 读取当前页面 SEO 关键词（RankMath / Yoast + Site Profile）
        register_rest_route( 'puffergo/v1', '/pages/(?P<id>\d+)/seo-keywords', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'rest_get_page_seo_keywords' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
            'args'                => array(
                'id' => array(
                    'required'          => true,
                    'type'              => 'integer',
                    'sanitize_callback' => 'absint',
                ),
            ),
        ) );

        // 图片落库接口：下载外链图片并保存为 WP 媒体库附件
        register_rest_route( 'puffergo/v1', '/images/sideload', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_sideload_image' ),
            'permission_callback' => function() {
                return current_user_can( 'upload_files' );
            },
            'args'                => array(
                'attachmentId' => array(
                    'required'          => false,
                    'type'              => 'integer',
                    'default'           => 0,
                    'sanitize_callback' => 'absint',
                ),
                'previewUrl' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'esc_url_raw',
                ),
                'filename' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_file_name',
                ),
                'alt' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'title' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'postId' => array(
                    'required'          => false,
                    'type'              => 'integer',
                    'default'           => 0,
                    'sanitize_callback' => 'absint',
                ),
                'authorCredit' => array(
                    'required'          => false,
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
            ),
        ) );

        // AI Chat Edit - 通过对话编辑 Tailwind 区块
        register_rest_route( 'puffergo/v1', '/tailwind/chat-edit', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_chat_edit' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
        ) );

        // AI 精准编辑区块 - 使用完整设计系统约束，最小改动原则
        register_rest_route( 'puffergo/v1', '/tailwind/edit-block', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_edit_block' ),
            'permission_callback' => function() {
                return current_user_can( 'edit_posts' );
            },
        ) );
    }

    /**
     * REST API: Generic AI Generate - Proxy to external API
     * 
     * This endpoint proxies requests to the external AI service
     * Accepts any prompt and returns AI-generated content
     * 
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_ai_generate( $request ) {
        $params = $request->get_json_params();
        
        // Validate required fields
        if ( empty( $params['prompt'] ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'Prompt is required', 'puffergo' ),
            ), 400 );
        }
        
        // Sanitize inputs
        $prompt        = isset( $params['prompt'] )        ? sanitize_textarea_field( wp_unslash( $params['prompt'] ) )        : '';
        $system_prompt = isset( $params['systemPrompt'] )  ? sanitize_textarea_field( wp_unslash( $params['systemPrompt'] ) )  : '';
        $response_format = isset( $params['responseFormat'] ) ? sanitize_text_field( wp_unslash( $params['responseFormat'] ) ) : 'text';
        $model         = isset( $params['model'] )         ? sanitize_text_field( wp_unslash( $params['model'] ) )             : 'deepseek-v3';
        $json_schema   = isset( $params['jsonSchema'] )    ? $params['jsonSchema']                                             : null;
        
        try {
            // Get API client instance
            $client = PufferGo_API_Client::get_instance();
            
            // Prepare request payload
            $api_payload = array(
                'prompt'         => $prompt,
                'responseFormat' => $response_format,
                'model'          => $model,
            );

            // 透传 systemPrompt（供调用方分离角色定义与用户输入）
            if ( ! empty( $system_prompt ) ) {
                $api_payload['systemPrompt'] = $system_prompt;
            }
            
            // Add jsonSchema if provided (for json_object format)
            if ( $response_format === 'json_object' && ! empty( $json_schema ) ) {
                $api_payload['jsonSchema'] = $json_schema;
            }
            
            // Log request (debug)
            puffergo_log( '[AI Generate] Calling external API: /api/ai/generate' );
            puffergo_log( '[AI Generate] Model: ' . $model . ', Format: ' . $response_format );
            puffergo_log( '[AI Generate] Prompt length: ' . strlen( $prompt ) . ' chars' );
            
            // Call external API
            $result = $client->post( '/api/ai/generate', $api_payload, array(
                'timeout'    => 120,
                'log_prefix' => 'AI Generate',
                'debug'      => true,
            ) );
            
            // Log response (debug)
            if ( isset( $result['meta']['duration'] ) ) {
                puffergo_log( '[AI Generate] Response received in ' . $result['meta']['duration'] . 'ms' );
            }
            if ( isset( $result['usage']['total_tokens'] ) ) {
                puffergo_log( '[AI Generate] Tokens used: ' . $result['usage']['total_tokens'] );
            }
            
            // Return formatted response
            return new WP_REST_Response( array(
                'success' => true,
                'data'    => isset( $result['data'] ) ? $result['data'] : $result,
                'meta'    => isset( $result['meta'] ) ? $result['meta'] : null,
                'usage'   => isset( $result['usage'] ) ? $result['usage'] : null,
            ), 200 );
            
        } catch ( PufferGo_API_Exception $e ) {
            // Handle authentication/billing errors
            puffergo_log( '[AI Generate] API Exception: ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')' );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
            
        } catch ( Exception $e ) {
            // Handle general errors
            puffergo_log( '[AI Generate] Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * REST API: 编译用户导入的 Tailwind HTML
     * 
     * 用户可以粘贴已有的 Tailwind HTML，通过此接口编译生成 CSS
     * 
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_compile_html( $request ) {
        $html = $request->get_param( 'html' );
        $scope_id = $request->get_param( 'scopeId' );
        
        if ( empty( $html ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '请输入 HTML 代码', 'puffergo' ),
            ), 400 );
        }
        
        try {
            // 调用远程编译接口
            $result = $this->call_compile_api( $html, $scope_id );
            
            return new WP_REST_Response( array(
                'success' => true,
                'data'    => array(
                    'html' => $html,
                    'css'  => isset( $result['css'] ) ? $result['css'] : '',
                ),
            ), 200 );
            
        } catch ( PufferGo_API_Exception $e ) {
            // 认证/计费相关错误 - 透传状态码和操作 URL
            puffergo_log( 'PufferGo Tailwind Compile Error (API): ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')' );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
        } catch ( Exception $e ) {
            puffergo_log( 'PufferGo Tailwind Compile Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * REST API: 搜索图片
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_search_images( $request ) {
        $keyword = $request->get_param( 'keyword' );
        $width = $request->get_param( 'width' );
        $height = $request->get_param( 'height' );

        if ( empty( $keyword ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '请输入搜索关键词', 'puffergo' ),
            ), 400 );
        }

        try {
            $result = $this->call_images_search_api( $keyword, $width, $height );

            // 确保返回 previewUrl（供前端打 data-pg-preview-url 标记）和 author（版权合规）
            $response_data = is_array( $result ) ? $result : array();
            if ( ! isset( $response_data['previewUrl'] ) && isset( $response_data['url'] ) ) {
                $response_data['previewUrl'] = $response_data['url'];
            }

            return new WP_REST_Response( $response_data, 200 );

        } catch ( PufferGo_API_Exception $e ) {
            puffergo_log( 'PufferGo Images Search Error (API): ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')' );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
        } catch ( Exception $e ) {
            puffergo_log( 'PufferGo Images Search Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * REST API: AI 图片 SEO 建议代理
     * 接收图片语义 + 关键词上下文，转发到 siteForge /api/ai/image-seo-suggest，
     * 返回 { filename, alt, title } 建议。
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_image_seo_suggest( $request ) {
        $img_keyword   = $request->get_param( 'imgKeyword' );
        $page_keywords = $request->get_param( 'pageKeywords' );
        $site_keywords = $request->get_param( 'siteKeywords' );
        $language      = $request->get_param( 'language' );
        $context_text  = substr( sanitize_textarea_field( wp_unslash( $request->get_param( 'contextText' ) ) ), 0, 400 );

        if ( empty( $img_keyword ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '图片关键词不能为空', 'puffergo' ),
            ), 400 );
        }

        if ( ! class_exists( 'PufferGo_API_Client' ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'API 客户端不可用', 'puffergo' ),
            ), 500 );
        }

        try {
            $api_client = PufferGo_API_Client::get_instance();

            $site_kw_sanitized = array();
            if ( is_array( $site_keywords ) ) {
                foreach ( array_slice( $site_keywords, 0, 30 ) as $kw ) {
                    $site_kw_sanitized[] = sanitize_text_field( $kw );
                }
            }

            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $result = $api_client->post(
                '/api/ai/image-seo-suggest',
                array(
                    'imgKeyword'   => $img_keyword,
                    'pageKeywords' => $page_keywords,
                    'siteKeywords' => $site_kw_sanitized,
                    'language'     => $language,
                    'contextText'  => $context_text,
                ),
                array(
                    'timeout'    => 20,
                    'log_prefix' => 'PufferGo Image SEO Suggest',
                )
            );
            // phpcs:enable

            // siteForge 返回 { filename, alt, title }（API Client 已解包 data 层）
            if ( empty( $result['filename'] ) ) {
                return new WP_REST_Response( array(
                    'success' => false,
                    'message' => esc_html__( 'AI 返回数据无效', 'puffergo' ),
                ), 500 );
            }

            return new WP_REST_Response( array(
                'success' => true,
                'data'    => array(
                    'filename' => sanitize_text_field( $result['filename'] ),
                    'alt'      => sanitize_text_field( $result['alt'] ?? '' ),
                    'title'    => sanitize_text_field( $result['title'] ?? '' ),
                ),
            ), 200 );

        } catch ( PufferGo_API_Exception $e ) {
            puffergo_log( 'PufferGo Image SEO Suggest Error (API): ' . $e->getMessage() );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
        } catch ( Exception $e ) {
            puffergo_log( 'PufferGo Image SEO Suggest Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * REST API: 读取当前页面 SEO 关键词
     * 优先级：RankMath focus_keyword > Yoast focuskw > Site Profile seo.keywords
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_get_page_seo_keywords( $request ) {
        $post_id = absint( $request->get_param( 'id' ) );

        if ( ! $post_id || ! get_post( $post_id ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '页面不存在', 'puffergo' ),
            ), 404 );
        }

        // 1. 从 RankMath 或 Yoast 读取焦点关键词字符串（逗号分隔）
        $focus_kw_string = get_post_meta( $post_id, 'rank_math_focus_keyword', true );
        if ( empty( $focus_kw_string ) ) {
            $focus_kw_string = get_post_meta( $post_id, '_yoast_wpseo_focuskw', true );
        }

        $page_keywords = array();
        if ( ! empty( $focus_kw_string ) && is_string( $focus_kw_string ) ) {
            $page_keywords = array_values( array_filter( array_map( 'trim', explode( ',', $focus_kw_string ) ) ) );
        }

        // 2. 从 Site Profile 读取站点全局关键词（最多 30 个）
        $profile      = get_option( 'puffergo_site_profile', array() );
        $site_keywords = array();
        if ( isset( $profile['seo']['keywords'] ) && is_array( $profile['seo']['keywords'] ) ) {
            $site_keywords = array_slice(
                array_map( 'sanitize_text_field', $profile['seo']['keywords'] ),
                0,
                30
            );
        }

        return new WP_REST_Response( array(
            'success'       => true,
            'pageKeywords'  => $page_keywords,
            'siteKeywords'  => $site_keywords,
        ), 200 );
    }

    /**
     * REST API: 将外链图片下载并保存为 WP 媒体库附件（sideload）
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_sideload_image( $request ) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $attachment_id_param = absint( $request->get_param( 'attachmentId' ) );

        // ─── 模式 A：仅更新已存在附件的元数据（文件名 / alt / title）────────
        if ( $attachment_id_param > 0 ) {
            $attachment = get_post( $attachment_id_param );
            if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
                return new WP_REST_Response( array(
                    'success' => false,
                    'message' => esc_html__( '附件不存在', 'puffergo' ),
                ), 404 );
            }

            $filename_base = sanitize_text_field( wp_unslash( $request->get_param( 'filename' ) ) );
            $alt           = sanitize_text_field( wp_unslash( $request->get_param( 'alt' ) ) );
            $title         = sanitize_text_field( wp_unslash( $request->get_param( 'title' ) ) );

            // 校验文件名格式
            if ( ! preg_match( '/^[a-z0-9][a-z0-9\-]{0,58}[a-z0-9]$/', $filename_base ) ) {
                return new WP_REST_Response( array(
                    'success' => false,
                    'message' => esc_html__( '文件名只能包含小写英文字母、数字和中划线（2-60位）', 'puffergo' ),
                ), 400 );
            }

            // 更新附件物理文件名（重命名并更新 guid / post_name）
            $current_file = get_attached_file( $attachment_id_param );
            if ( $current_file ) {
                $dir       = trailingslashit( dirname( $current_file ) );
                $ext       = pathinfo( $current_file, PATHINFO_EXTENSION );
                $new_file  = $dir . $filename_base . '.' . $ext;

                if ( $current_file !== $new_file ) {
                    rename( $current_file, $new_file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_rename
                    update_attached_file( $attachment_id_param, $new_file );

                    // 更新 guid 和 post_name（影响 URL）
                    $upload_dir = wp_upload_dir();
                    $new_url    = str_replace( trailingslashit( $upload_dir['basedir'] ), trailingslashit( $upload_dir['baseurl'] ), $new_file );

                    wp_update_post( array(
                        'ID'        => $attachment_id_param,
                        'guid'      => $new_url,
                        'post_name' => $filename_base,
                    ) );

                    // 重新生成元数据（缩略图等）
                    $metadata = wp_generate_attachment_metadata( $attachment_id_param, $new_file );
                    wp_update_attachment_metadata( $attachment_id_param, $metadata );
                }
            }

            // 更新 alt / title
            if ( ! empty( $alt ) ) {
                update_post_meta( $attachment_id_param, '_wp_attachment_image_alt', $alt );
            }
            if ( ! empty( $title ) ) {
                wp_update_post( array( 'ID' => $attachment_id_param, 'post_title' => $title ) );
            }

            $src = wp_get_attachment_image_src( $attachment_id_param, 'full' );
            return new WP_REST_Response( array(
                'success'      => true,
                'attachmentId' => $attachment_id_param,
                'url'          => $src ? esc_url( $src[0] ) : '',
                'width'        => $src ? intval( $src[1] ) : 0,
                'height'       => $src ? intval( $src[2] ) : 0,
                'alt'          => $alt,
                'title'        => $title,
            ), 200 );
        }

        // ─── 模式 B：从外部 URL 下载并新建附件 ────────────────────────────────

        $preview_url   = esc_url_raw( wp_unslash( $request->get_param( 'previewUrl' ) ) );
        $filename_base = sanitize_file_name( wp_unslash( $request->get_param( 'filename' ) ) );
        $alt           = sanitize_text_field( wp_unslash( $request->get_param( 'alt' ) ) );
        $title         = sanitize_text_field( wp_unslash( $request->get_param( 'title' ) ) );
        $post_id       = absint( $request->get_param( 'postId' ) );
        $author_credit = sanitize_text_field( wp_unslash( $request->get_param( 'authorCredit' ) ) );

        // 模式 B 必填参数校验
        if ( empty( $preview_url ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '图片来源 URL 不能为空', 'puffergo' ),
            ), 400 );
        }

        if ( empty( $alt ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'Alt 文本不能为空', 'puffergo' ),
            ), 400 );
        }

        // 校验文件名：只允许英文/数字/中划线
        if ( ! preg_match( '/^[a-z0-9][a-z0-9\-]{0,58}[a-z0-9]$/', $filename_base ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '文件名只能包含小写英文字母、数字和中划线（2-60位）', 'puffergo' ),
            ), 400 );
        }

        // 校验 URL 来自受信任域
        $parsed_host = wp_parse_url( $preview_url, PHP_URL_HOST );
        $allowed_hosts = array( 'images.pexels.com', 'images.unsplash.com', 'live.staticflickr.com', 'wordpress.org', 'i0.wp.com' );
        $allowed_hosts = apply_filters( 'puffergo_sideload_allowed_hosts', $allowed_hosts );
        if ( ! in_array( $parsed_host, $allowed_hosts, true ) ) {
            // 允许 API 服务自有域名（R2/CDN）
            $api_base_host = wp_parse_url( defined( 'PUFFERGO_AI_PAGE_BUILDER_BASE_URL' ) ? PUFFERGO_AI_PAGE_BUILDER_BASE_URL : '', PHP_URL_HOST );
            if ( empty( $api_base_host ) || $parsed_host !== $api_base_host ) {
                return new WP_REST_Response( array(
                    'success' => false,
                    'message' => esc_html__( '图片来源域名不在允许列表中', 'puffergo' ),
                ), 400 );
            }
        }

        // 下载到临时文件
        $tmp_file = download_url( $preview_url, 30 );
        if ( is_wp_error( $tmp_file ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '图片下载失败：', 'puffergo' ) . $tmp_file->get_error_message(),
            ), 500 );
        }

        // 推断扩展名（从 MIME 或原始 URL）
        $mime_type = mime_content_type( $tmp_file );
        $ext_map   = array(
            'image/jpeg' => 'jpg',
            'image/png'  => 'png',
            'image/webp' => 'webp',
            'image/gif'  => 'gif',
            'image/avif' => 'avif',
        );
        $extension = isset( $ext_map[ $mime_type ] ) ? $ext_map[ $mime_type ] : 'jpg';
        $full_filename = $filename_base . '.' . $extension;

        // 重命名临时文件（让 WP 用正确的文件名建立附件）
        $renamed = rename( $tmp_file, dirname( $tmp_file ) . '/' . $full_filename );
        $file_path = $renamed ? ( dirname( $tmp_file ) . '/' . $full_filename ) : $tmp_file;

        $file_array = array(
            'name'     => $full_filename,
            'tmp_name' => $file_path,
        );

        // 插入媒体库（关联到指定页面，若 postId=0 则独立存在）
        $attachment_id = media_handle_sideload( $file_array, $post_id, $title ?: $filename_base );

        // 清理临时文件（media_handle_sideload 失败时可能未清理）
        if ( file_exists( $file_path ) ) {
            @unlink( $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
        }

        if ( is_wp_error( $attachment_id ) ) {
            puffergo_log( 'PufferGo Sideload Error: ' . $attachment_id->get_error_message() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '保存至媒体库失败：', 'puffergo' ) . $attachment_id->get_error_message(),
            ), 500 );
        }

        // 写入 SEO 元数据
        update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );

        if ( ! empty( $title ) ) {
            wp_update_post( array(
                'ID'         => $attachment_id,
                'post_title' => $title,
            ) );
        }

        // 版权信息写入附件说明（post_excerpt）
        if ( ! empty( $author_credit ) ) {
            wp_update_post( array(
                'ID'           => $attachment_id,
                'post_excerpt' => sanitize_text_field( $author_credit ),
            ) );
        }

        // 构建响应：返回本站 URL + 各尺寸
        $src    = wp_get_attachment_image_src( $attachment_id, 'full' );
        $srcset = wp_get_attachment_image_srcset( $attachment_id, 'large' );

        return new WP_REST_Response( array(
            'success'       => true,
            'attachmentId'  => $attachment_id,
            'url'           => $src ? esc_url( $src[0] ) : '',
            'width'         => $src ? intval( $src[1] ) : 0,
            'height'        => $src ? intval( $src[2] ) : 0,
            'srcset'        => $srcset ? $srcset : '',
            'alt'           => $alt,
            'title'         => $title,
        ), 200 );
    }

    /**
     * REST API: AI Chat Edit - 通过对话编辑 Tailwind 区块
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function rest_edit_block( $request ) {
        $params = $request->get_json_params();

        $section_html     = isset( $params['sectionHtml'] )     ? wp_kses_post( wp_unslash( $params['sectionHtml'] ) )     : '';
        $edit_instruction = isset( $params['editInstruction'] )  ? sanitize_textarea_field( wp_unslash( $params['editInstruction'] ) ) : '';
        $full_html        = isset( $params['fullHtml'] )         ? wp_kses_post( wp_unslash( $params['fullHtml'] ) )        : '';
        $parent_context   = isset( $params['parentContext'] )    ? wp_kses_post( wp_unslash( $params['parentContext'] ) )   : '';
        $scope_id         = isset( $params['scopeId'] )          ? sanitize_text_field( wp_unslash( $params['scopeId'] ) ) : '';
        $model            = isset( $params['model'] )            ? sanitize_text_field( wp_unslash( $params['model'] ) )   : '';
        $language         = isset( $params['language'] )         ? sanitize_text_field( wp_unslash( $params['language'] ) ) : 'zh_CN';
        $style_mode       = isset( $params['styleMode'] )        ? sanitize_key( wp_unslash( $params['styleMode'] ) )      : 'default';
        $images           = isset( $params['images'] ) && is_array( $params['images'] ) ? $params['images'] : array();

        if ( empty( $section_html ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'sectionHtml 不能为空', 'puffergo' ),
            ), 400 );
        }

        if ( empty( $edit_instruction ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( '编辑指令不能为空', 'puffergo' ),
            ), 400 );
        }

        try {
            $result = $this->call_edit_block_api(
                $section_html,
                $edit_instruction,
                $full_html,
                $parent_context,
                $scope_id,
                $model,
                $language,
                $style_mode,
                $images
            );

            return new WP_REST_Response( array(
                'success' => true,
                'data'    => array(
                    'sectionHtml' => isset( $result['sectionHtml'] ) ? $result['sectionHtml'] : $section_html,
                    'fullHtml'    => isset( $result['fullHtml'] )    ? $result['fullHtml']    : '',
                    'css'         => isset( $result['css'] )         ? $result['css']         : '',
                ),
            ), 200 );

        } catch ( PufferGo_API_Exception $e ) {
            puffergo_log( '[Edit Block] API Exception: ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')' );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
        } catch ( Exception $e ) {
            puffergo_log( '[Edit Block] Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * 调用远程 /api/ai/edit-block 接口
     *
     * @param string $section_html     用户选中的 HTML 片段
     * @param string $edit_instruction 用户的自然语言编辑指令
     * @param string $full_html        完整区块 HTML（用于 CSS 重新编译）
     * @param string $parent_context   父元素开标签（用于布局约束）
     * @param string $scope_id         区块作用域 ID
     * @param string $model            AI 模型
     * @param string $language         语言
     * @param string $style_mode       风格模式
     * @return array { sectionHtml: string, fullHtml: string, css: string }
     * @throws Exception
     */
    private function call_edit_block_api(
        $section_html,
        $edit_instruction,
        $full_html,
        $parent_context,
        $scope_id,
        $model = '',
        $language = 'zh_CN',
        $style_mode = 'default',
        $images = array()
    ) {
        if ( ! class_exists( 'PufferGo_API_Client' ) ) {
            throw new Exception( esc_html__( 'API 客户端不可用', 'puffergo' ) );
        }

        $api_client = PufferGo_API_Client::get_instance();

        $request_data = array(
            'sectionHtml'     => $section_html,
            'editInstruction' => $edit_instruction,
            'pageSummary'     => array(),
            'language'        => $language,
            'styleMode'       => $style_mode,
            'themeContext'    => $this->get_theme_context(),
            'generationRules' => $this->get_generation_rules(),
            'compilerConfig'  => $this->get_compiler_config( $scope_id ),
        );

        if ( ! empty( $full_html ) ) {
            $request_data['fullHtml'] = $full_html;
        }

        if ( ! empty( $parent_context ) ) {
            $request_data['parentContext'] = $parent_context;
        }

        if ( ! empty( $model ) ) {
            $request_data['model'] = $model;
        }

        if ( ! empty( $images ) && is_array( $images ) ) {
            $request_data['images'] = self::sanitize_images_array( $images );
        }

        puffergo_log( '[Edit Block] Calling: ' . self::EDIT_BLOCK_ENDPOINT );
        puffergo_log( '[Edit Block] Instruction: ' . $edit_instruction );

        $result = $api_client->post(
            self::EDIT_BLOCK_ENDPOINT,
            $request_data,
            array(
                'timeout'    => 120,
                'log_prefix' => 'PufferGo Edit Block',
            )
        );

        if ( ! isset( $result['sectionHtml'] ) ) {
            throw new Exception( esc_html__( 'AI 服务返回数据格式不正确', 'puffergo' ) );
        }

        return $result;
    }

    /**
     * @return WP_REST_Response
     */
    public function rest_chat_edit( $request ) {
        $params = $request->get_json_params();
        
        $message = isset( $params['message'] ) ? sanitize_textarea_field( $params['message'] ) : '';
        $conversation_history = isset( $params['conversationHistory'] ) ? $params['conversationHistory'] : array();
        $blocks = isset( $params['blocks'] ) ? $params['blocks'] : array();
        
        if ( empty( $message ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'Please enter a message', 'puffergo' ),
            ), 400 );
        }
        
        if ( empty( $blocks ) ) {
            return new WP_REST_Response( array(
                'success' => false,
                'message' => esc_html__( 'No blocks selected', 'puffergo' ),
            ), 400 );
        }
        
        try {
            // Process each block
            $updated_blocks = array();
            
            foreach ( $blocks as $block ) {
                $html = isset( $block['html'] ) ? $block['html'] : '';
                $css = isset( $block['css'] ) ? $block['css'] : '';
                $scope_id = isset( $block['scopeId'] ) ? sanitize_text_field( $block['scopeId'] ) : '';
                
                if ( empty( $html ) || empty( $scope_id ) ) {
                    continue;
                }
                
                // Call AI service to edit the block
                $result = $this->call_chat_edit_api( $message, $html, $css, $scope_id, $conversation_history );

                $unsafe_tokens = $this->find_unsafe_html_tokens( $result['html'] );
                if ( ! empty( $unsafe_tokens ) ) {
                    $tokens_list = implode( '、', array_map( 'esc_html', $unsafe_tokens ) );
                    throw new Exception(
                        sprintf(
                            /* translators: %s: comma-separated list of unsafe tokens */
                            esc_html__( '检测到不安全的交互指令：%s，请重新生成', 'puffergo' ),
                            $tokens_list
                        )
                    );
                }
                
                $updated_blocks[] = array(
                    'clientId' => isset( $block['clientId'] ) ? $block['clientId'] : '',
                    'html'     => isset( $result['html'] ) ? $result['html'] : $html,
                    'css'      => isset( $result['css'] ) ? $result['css'] : $css,
                );
            }
            
            return new WP_REST_Response( array(
                'success' => true,
                'data'    => array(
                    'message' => esc_html__( 'Changes generated successfully. Click "Save Changes" to apply.', 'puffergo' ),
                    'blocks'  => $updated_blocks,
                ),
            ), 200 );
            
        } catch ( PufferGo_API_Exception $e ) {
            puffergo_log( 'PufferGo Chat Edit Error (API): ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')' );
            return new WP_REST_Response( $e->toArray(), $e->getCode() );
        } catch ( Exception $e ) {
            puffergo_log( 'PufferGo Chat Edit Error: ' . $e->getMessage() );
            return new WP_REST_Response( array(
                'success' => false,
                'message' => $e->getMessage(),
            ), 500 );
        }
    }

    /**
     * 调用远程 AI Chat Edit 接口
     *
     * @param string $message            用户消息
     * @param string $html               当前 HTML
     * @param string $css                当前 CSS
     * @param string $scope_id           区块作用域 ID
     * @param array  $conversation_history 对话历史
     * @return array { html: string, css: string }
     * @throws Exception
     */
    private function call_chat_edit_api( $message, $html, $css, $scope_id, $conversation_history = array() ) {
        if ( ! class_exists( 'PufferGo_API_Client' ) ) {
            throw new Exception( esc_html__( 'API client not available', 'puffergo' ) );
        }
        
        $api_client = PufferGo_API_Client::get_instance();
        
        // Build request data
        $request_data = array(
            'message'             => $message,
            'html'                => $html,
            'css'                 => $css,
            'conversationHistory' => $conversation_history,
            // Theme context for AI
            'themeContext'        => $this->get_theme_context(),
            // Generation rules
            'generationRules'     => $this->get_generation_rules(),
            // Compiler config
            'compilerConfig'      => $this->get_compiler_config( $scope_id ),
        );
        
        // Call remote AI chat edit service
        $result = $api_client->post(
            '/api/ai/chat-edit-block',
            $request_data,
            array(
                'timeout'    => 120,
                'log_prefix' => 'PufferGo Chat Edit',
            )
        );
        
        // Validate response
        if ( ! isset( $result['html'] ) ) {
            throw new Exception( esc_html__( 'AI service returned invalid data format', 'puffergo' ) );
        }
        
        return $result;
    }

    /**
     * 调用远程图片搜索接口
     *
     * @param string $keyword 搜索关键词
     * @param int|null $width 期望宽度
     * @param int|null $height 期望高度
     * @return array 图片搜索结果
     * @throws Exception
     */
    private function call_images_search_api( $keyword, $width = null, $height = null ) {
        if ( ! class_exists( 'PufferGo_API_Client' ) ) {
            throw new Exception( esc_html__( 'API 客户端不可用', 'puffergo' ) );
        }

        $api_client = PufferGo_API_Client::get_instance();

        // 构建请求数据
        $request_data = array(
            'keyword' => $keyword,
        );

        if ( $width ) {
            $request_data['width'] = $width;
        }

        if ( $height ) {
            $request_data['height'] = $height;
        }

        // 调用远程图片搜索服务
        $result = $api_client->get(
            '/api/images/search',
            $request_data,
            array(
                'timeout'    => 30,
                'log_prefix' => 'PufferGo Images Search',
            )
        );

        return $result;
    }

    /**
     * 调用远程 Tailwind 编译接口
     *
     * @param string $html     用户导入的 HTML
     * @param string $scope_id 区块唯一作用域 ID
     * @return array { css: string }
     * @throws Exception
     */
    private function call_compile_api( $html, $scope_id ) {
        if ( ! class_exists( 'PufferGo_API_Client' ) ) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message for internal use
            throw new Exception( esc_html__( 'API 客户端不可用', 'puffergo' ) );
        }
        
        $api_client = PufferGo_API_Client::get_instance();
        
        // 构建请求数据
        $request_data = array(
            'html'           => $html,
            'compilerConfig' => $this->get_compiler_config( $scope_id ),
        );
        
        // 调用远程编译服务
        $result = $api_client->post(
            self::COMPILE_ENDPOINT,
            $request_data,
            array(
                'timeout'    => 30,
                'log_prefix' => 'PufferGo Tailwind Compile',
            )
        );
        
        return $result;
    }

    /**
     * 自异步 Job 入口组装与 POST /api/ai/generate-block 相同的 JSON 体（供 siteForge worker 使用）。
     *
     * @param array<string, mixed> $client Keys: prompt, scopeId, model?, blockType?, language?, styleMode?, designSystem?, profile?
     * @return array<string, mixed>
     * @throws Exception When validation fails.
     */
    public function build_generate_block_request_body( array $client ) {
        $prompt = isset( $client['prompt'] ) ? sanitize_textarea_field( wp_unslash( (string) $client['prompt'] ) ) : '';
        if ( '' === $prompt ) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- user-facing validation
            throw new Exception( esc_html__( '请输入内容描述', 'puffergo' ) );
        }
        $scope_id = isset( $client['scopeId'] ) ? sanitize_text_field( wp_unslash( (string) $client['scopeId'] ) ) : '';
        if ( '' === $scope_id ) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- user-facing validation
            throw new Exception( esc_html__( 'Missing scope id.', 'puffergo' ) );
        }
        $model       = isset( $client['model'] ) ? sanitize_text_field( wp_unslash( (string) $client['model'] ) ) : 'gemini-3-pro-preview';
        $block_type  = isset( $client['blockType'] ) ? sanitize_text_field( wp_unslash( (string) $client['blockType'] ) ) : 'auto';
        $language    = isset( $client['language'] ) ? sanitize_text_field( wp_unslash( (string) $client['language'] ) ) : 'zh-CN';
        $style_mode  = isset( $client['styleMode'] ) ? sanitize_key( (string) $client['styleMode'] ) : 'default';
        if ( ! in_array( $style_mode, array( 'default', 'creative' ), true ) ) {
            $style_mode = 'default';
        }
        $design_system = isset( $client['designSystem'] ) && is_array( $client['designSystem'] ) ? $client['designSystem'] : null;
        $profile       = isset( $client['profile'] ) && is_array( $client['profile'] ) ? $client['profile'] : null;
        if ( empty( $profile ) ) {
            $profile = get_option( 'puffergo_site_profile', array() );
            if ( ! is_array( $profile ) ) {
                $profile = array();
            }
            if ( isset( $profile['updated_at'] ) ) {
                unset( $profile['updated_at'] );
            }
        }

        $images = isset( $client['images'] ) && is_array( $client['images'] ) ? $client['images'] : array();

        return $this->compose_generate_block_request_data( $prompt, $model, $block_type, $scope_id, $language, $style_mode, $design_system, $profile, $images );
    }

    /**
     * 组装 generate-block 请求体（供 workflow job 代理至 siteForge）。
     *
     * @param string          $prompt         Prompt.
     * @param string          $model          Model id.
     * @param string          $block_type     Block type.
     * @param string          $scope_id       Scope id.
     * @param string          $language       Language.
     * @param string          $style_mode     default|creative.
     * @param array|null      $design_system  Design tokens.
     * @param array|null      $profile        Site profile.
     * @return array<string, mixed>
     */
    private function compose_generate_block_request_data( $prompt, $model, $block_type, $scope_id, $language, $style_mode, $design_system, $profile, $images = array() ) {
        $is_follow_theme = ( 'default' === $style_mode );

        $theme_context = null;
        if ( $is_follow_theme ) {
            if ( ! empty( $design_system ) && is_array( $design_system ) ) {
                $theme_context = array(
                    'primaryColor' => isset( $design_system['primaryColor'] ) ? sanitize_hex_color( $design_system['primaryColor'] ) : '#3b82f6',
                    'isDarkTheme'  => ! empty( $design_system['isDarkTheme'] ),
                    'borderRadius' => isset( $design_system['borderRadius'] ) ? sanitize_text_field( $design_system['borderRadius'] ) : '6px',
                    'fontStyle'    => isset( $design_system['fontStyle'] ) ? sanitize_text_field( $design_system['fontStyle'] ) : 'sans-serif',
                );
            } else {
                $theme_context = $this->get_theme_context();
            }
        }

        $request_data = array(
            'prompt'          => $prompt,
            'model'           => $model,
            'blockType'       => $block_type,
            'language'        => $language,
            'styleMode'       => $style_mode,
            'themeContext'    => $theme_context,
            'generationRules' => $is_follow_theme ? $this->get_generation_rules() : null,
            'compilerConfig'  => $this->get_compiler_config( $scope_id ),
        );
        if ( ! empty( $profile ) ) {
            $request_data['profile'] = $profile;
        }
        if ( ! empty( $images ) && is_array( $images ) ) {
            $request_data['images'] = self::sanitize_images_array( $images );
        }

        return $request_data;
    }

    /**
     * 获取主题视觉上下文（供 AI 理解设计风格）
     * 
     * @return array
     */
    private function get_theme_context() {
        $theme_bridge = $this->get_theme_bridge();
        $defaults = $this->get_theme_values();

        return array(
            'primaryColor'  => $defaults['primaryColor'],
            'isDarkTheme'   => false,
            'borderRadius'  => $defaults['borderRadius'],
            'fontStyle'     => $defaults['fontBody'],
        );
    }

    /**
     * 获取 AI 生成规则（WordPress 深度优化版）
     * * 优化点：
     * 1. SVG 可见性：强制使用“图标容器”模式，确图标永远清晰。
     * 2. WP 环境适配：不生成 Header/Footer，标题从 H2 开始。
     * 3. 语义化增强：强制 Section/Article 结构。
     * 4.采用 bg-opacity-* 显式写法，兼容所有 HEX 颜色变量。
     * * @return array
     */
    private function get_generation_rules() {
        return array(
            // 1. Tokens: 3-palette 色阶系统的 UI 定义
            'themeTokens' => array(
                'ui' => array(
                    'rounded-theme' => '组件圆角 (rounded-xl)。用于卡片、大按钮、输入框。',
                    'rounded-full'  => '功能性圆角 (rounded-full)。强制用于：Tag 标签、Icon 容器、头像。',
                    'shadow-theme'  => '卡片投影 (shadow-lg)。用于 bg-neutral-50 材质的卡片悬浮感。',
                )
            ),

            // 2. Instructions: 角色扮演 + 硬性工程约束
            'instructions' => array(
                'iconStyle' => '
                    - 小图标尽可能不缩放，特别是在父级是flex/grid布局的时候，图标不要缩放。
                ',
            ),
        );
    }

    private function find_unsafe_html_tokens( $html ) {
        if ( empty( $html ) ) {
            return array();
        }

        $unsafe = array();
        $previous = libxml_use_internal_errors( true );
        $dom = new DOMDocument();
        $dom->loadHTML( '<div>' . $html . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
        libxml_clear_errors();
        libxml_use_internal_errors( $previous );

        $nodes = $dom->getElementsByTagName( '*' );
        foreach ( $nodes as $node ) {
            $tag_name = strtolower( $node->nodeName );
            if ( $tag_name === 'script' ) {
                $unsafe[] = 'script';
            }
            if ( $node->hasAttributes() ) {
                foreach ( $node->attributes as $attr ) {
                    $name = strtolower( $attr->nodeName );
                    if ( strpos( $name, 'on' ) === 0 ) {
                        $unsafe[] = $name;
                    }
                }
            }
        }

        return array_values( array_unique( $unsafe ) );
    }

    /**
     * 获取编译器配置（用于 Node.js JIT 编译）
     * 
     * 复用 Theme Bridge 的 Tailwind 配置，避免重复维护
     * 
     * @param string $scope_id 区块唯一作用域 ID（用于 CSS 隔离）
     * @return array
     */
    private function get_compiler_config( $scope_id ) {
        $theme_config = $this->get_theme_bridge()->get_tailwind_theme_config();
        
        return array(
            'important' => '.' . $scope_id,
            'theme'     => array(
                'extend' => $theme_config,
            ),
        );
    }

    /**
     * 获取 Theme Bridge 实例
     * 
     * @return PufferGo_Theme_Bridge
     */
    private function get_theme_bridge() {
        static $bridge = null;
        if ( $bridge === null ) {
            $bridge = new PufferGo_Theme_Bridge();
        }
        return $bridge;
    }

    /**
     * 获取主题颜色/样式值（委托给 Theme Bridge）
     * 
     * @return array
     */
    private function get_theme_values() {
        // 优先从 AI 设计系统读取 tokens
        if ( class_exists( 'PufferGo_Design_System' ) ) {
            $tokens = PufferGo_Design_System::get_active_tokens();
            if ( $tokens ) {
                return array(
                    'primaryColor'   => $tokens['primaryColor']   ?? '#3b82f6',
                    'secondaryColor' => $tokens['secondaryColor'] ?? '#f59e0b',
                    'baseColor'      => $tokens['baseColor']      ?? '#ffffff',
                    'borderRadius'   => $tokens['borderRadius']   ?? '8px',
                    'fontHeading'    => $tokens['fontHeading']    ?? 'sans-serif',
                    'fontBody'       => $tokens['fontBody']       ?? 'sans-serif',
                );
            }
        }
        // 无 AI 方案时使用默认值
        return array(
            'primaryColor'   => '#3b82f6',
            'secondaryColor' => '#f59e0b',
            'baseColor'      => '#ffffff',
            'borderRadius'   => '8px',
            'fontHeading'    => 'sans-serif',
            'fontBody'       => 'sans-serif',
        );
    }

    /**
     * 注册 Tailwind 容器区块的服务端渲染
     */
    public function register_render_callback() {
        if ( ! function_exists( 'block_type_exists' ) || ! block_type_exists( 'puffergo/tailwind-container' ) ) {
            return;
        }
        
        $registry = WP_Block_Type_Registry::get_instance();
        $block_type = $registry->get_registered( 'puffergo/tailwind-container' );
        
        if ( $block_type ) {
            $block_type->render_callback = array( $this, 'render_block' );
        }
    }

    /**
     * 在 wp_head 之前入队当前页面中所有 Tailwind 容器区块的样式（Option A）
     * 
     * 通过解析正文中的块，在 wp_enqueue_scripts 阶段注册并 enqueue 样式，
     * 确保 CSS 输出在 <head> 中，避免在 render_block 时入队过晚导致样式不生效。
     */
    public function enqueue_block_styles_in_head() {
        if ( ! is_singular() ) {
            return;
        }
        $post_id = get_queried_object_id();
        if ( ! $post_id ) {
            return;
        }
        $post = get_post( $post_id );
        if ( ! $post || ! isset( $post->post_content ) ) {
            return;
        }
        $blocks = parse_blocks( $post->post_content );
        $styles = $this->collect_tailwind_container_styles( $blocks );
        foreach ( $styles as $scope_id => $custom_css ) {
            $this->register_and_enqueue_block_style( $scope_id, $custom_css );
        }

        // Enqueue external scripts from imported HTML (same pass as styles).
        $external_scripts = $this->collect_external_scripts_from_blocks( $blocks );
        foreach ( $external_scripts as $script_url ) {
            $script_url = esc_url_raw( $script_url );
            if ( empty( $script_url ) || strpos( $script_url, 'https://' ) !== 0 ) {
                continue;
            }
            $handle = 'puffergo-ext-' . substr( md5( $script_url ), 0, 12 );
            if ( ! wp_script_is( $handle, 'enqueued' ) ) {
                wp_enqueue_script( $handle, $script_url, array(), 'ext', true );
            }
        }
    }

    /**
     * Recursively collect externalScripts URLs from all puffergo/tailwind-container blocks.
     *
     * @param array $blocks Block list (parse_blocks result or innerBlocks).
     * @return array Deduplicated list of script URLs.
     */
    private function collect_external_scripts_from_blocks( $blocks ) {
        $urls = array();
        if ( ! is_array( $blocks ) ) {
            return $urls;
        }
        foreach ( $blocks as $block ) {
            if ( isset( $block['blockName'] ) && $block['blockName'] === 'puffergo/tailwind-container' ) {
                $attrs    = isset( $block['attrs'] ) ? $block['attrs'] : array();
                $deps_raw = isset( $attrs['dependencies'] ) ? $attrs['dependencies'] : null;
                $has_deps = is_array( $deps_raw ) ? ! empty( $deps_raw ) : ( is_string( $deps_raw ) && $deps_raw !== '' && $deps_raw !== '[]' );
                // 使用 dependencies 的块在 render_block 中输出脚本，此处不收集 externalScripts。
                if ( $has_deps ) {
                    // Skip.
                } else {
                    $list = isset( $attrs['externalScripts'] ) && is_array( $attrs['externalScripts'] )
                        ? $attrs['externalScripts']
                        : array();
                    foreach ( $list as $url ) {
                        if ( is_string( $url ) && $url !== '' ) {
                            $urls[] = $url;
                        }
                    }
                }
            }
            if ( ! empty( $block['innerBlocks'] ) ) {
                $inner = $this->collect_external_scripts_from_blocks( $block['innerBlocks'] );
                $urls  = array_merge( $urls, $inner );
            }
        }
        return array_values( array_unique( $urls ) );
    }

    /**
     * 递归收集所有 puffergo/tailwind-container 块的 scopeId 与 customCss
     *
     * @param array $blocks 块列表（parse_blocks 返回值或 innerBlocks）
     * @return array 键为 scope_id，值为 customCss
     */
    private function collect_tailwind_container_styles( $blocks ) {
        $styles = array();
        if ( ! is_array( $blocks ) ) {
            return $styles;
        }
        foreach ( $blocks as $block ) {
            if ( isset( $block['blockName'] ) && $block['blockName'] === 'puffergo/tailwind-container' ) {
                $attrs        = isset( $block['attrs'] ) ? $block['attrs'] : array();
                $scope_id     = isset( $attrs['scopeId'] ) ? $attrs['scopeId'] : '';
                $custom_css   = isset( $attrs['customCss'] ) ? $attrs['customCss'] : '';
                $deps_raw     = isset( $attrs['dependencies'] ) ? $attrs['dependencies'] : null;
                $has_deps     = is_array( $deps_raw ) ? ! empty( $deps_raw ) : ( is_string( $deps_raw ) && $deps_raw !== '' && $deps_raw !== '[]' );
                $tailwind_css = isset( $attrs['tailwindCss'] ) ? $attrs['tailwindCss'] : '';
                // 使用 tailwindCss 或 dependencies 的块在 render_block 中输出样式，此处不收集。
                if ( $tailwind_css !== '' || $has_deps ) {
                    // Skip.
                } elseif ( ! empty( $scope_id ) && ! empty( $custom_css ) ) {
                    $styles[ $scope_id ] = $custom_css;
                }
            }
            if ( ! empty( $block['innerBlocks'] ) ) {
                $inner  = $this->collect_tailwind_container_styles( $block['innerBlocks'] );
                $styles = array_merge( $styles, $inner );
            }
        }
        return $styles;
    }

    /**
     * 注册并 enqueue 单个区块的 scoped 样式，并添加 inline CSS
     *
     * @param string $scope_id   区块 scopeId（如 pg--hgjvlxz4-s1）
     * @param string $custom_css 已编译的 scoped Tailwind CSS
     */
    private function register_and_enqueue_block_style( $scope_id, $custom_css ) {
        $style_handle = 'puffergo-tailwind-container-' . sanitize_key( $scope_id );
        if ( wp_style_is( $style_handle, 'registered' ) ) {
            return;
        }
        wp_register_style( $style_handle, false, array( 'puffergo-tailwind' ), time() );
        wp_enqueue_style( $style_handle );
        $sanitized_css = wp_strip_all_tags( $custom_css );
        $sanitized_css = preg_replace( '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $sanitized_css );
        wp_add_inline_style( $style_handle, $sanitized_css );
    }

    /**
     * 渲染 Tailwind 容器区块（服务端）
     *
     * 使用 customCss 的块：样式/脚本已在 enqueue_block_styles_in_head 入队。
     * 使用 tailwindCss + dependencies 的块：在下方按 dependencies 顺序输出样式与脚本。
     *
     * @param array    $attributes 区块属性
     * @param string   $content    区块内容
     * @param WP_Block $block      区块实例
     * @return string 渲染后的 HTML
     */
    public function render_block( $attributes, $content, $block ) {
        $attrs = is_array( $block['attrs'] ?? null ) ? $block['attrs'] : ( is_array( $attributes ) ? $attributes : array() );
        $html_content = isset( $attrs['content'] ) ? $attrs['content'] : '';
        $scope_id     = isset( $attrs['scopeId'] ) ? $attrs['scopeId'] : '';
        $tailwind_css = isset( $attrs['tailwindCss'] ) ? $attrs['tailwindCss'] : ( isset( $attrs['tailwind_css'] ) ? $attrs['tailwind_css'] : '' );
        $deps_raw     = isset( $attrs['dependencies'] ) ? $attrs['dependencies'] : array();
        if ( is_string( $deps_raw ) ) {
            $decoded = json_decode( $deps_raw, true );
            $deps    = is_array( $decoded ) ? $decoded : array();
        } else {
            $deps = is_array( $deps_raw ) ? $deps_raw : array();
        }

        if ( empty( $html_content ) ) {
            return '';
        }

        // 允许所有 HTML 标签和属性（用于 Tailwind）
        $allowed_html = $this->get_allowed_html();
        $html_content = wp_kses( $html_content, $allowed_html );

        $assets_styles  = '';
        $assets_scripts = '';
        if ( $tailwind_css !== '' || ! empty( $deps ) ) {
            $parts = $this->render_block_assets_from_dependencies( $tailwind_css, $deps, $scope_id );
            $assets_styles  = isset( $parts['styles'] ) ? $parts['styles'] : '';
            $assets_scripts = isset( $parts['scripts'] ) ? $parts['scripts'] : '';
        }

        $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $scope_id ) );
        // 样式在 DOM 前、脚本在 DOM 后，保证 script 执行时目标节点已存在
        $out = $assets_styles . sprintf(
            '<div %s>%s</div>',
            $wrapper_attributes,
            $html_content
        ) . $assets_scripts;

        return $out;
    }

    /**
     * 从 dependency 项（可能为数组或 stdClass）安全取值
     *
     * @param array|object $d  dependency 项
     * @param string       $key 键名
     * @param mixed        $default 默认值
     * @return mixed
     */
    private function dep_item_value( $d, $key, $default = '' ) {
        if ( is_array( $d ) && array_key_exists( $key, $d ) ) {
            return $d[ $key ];
        }
        if ( is_object( $d ) && property_exists( $d, $key ) ) {
            return $d->$key;
        }
        return $default;
    }

    /**
     * 按 process-html dependencies 顺序生成样式与脚本（外链 + 内联）。
     * 样式在块 DOM 前输出，脚本在块 DOM 后输出，保证 script 执行时目标节点已存在。
     * dependency 项可能是数组或对象（stdClass），统一用 dep_item_value 取值。
     *
     * @param string $tailwind_css Tailwind 编译结果
     * @param array  $dependencies 项含 type(script|style), kind(external|inline), position, url?, content?, attributes?
     * @param string $scope_id     Block scopeId（用于避免重复 style id）
     * @return array { styles: string, scripts: string }
     */
    private function render_block_assets_from_dependencies( $tailwind_css, $dependencies, $scope_id = '' ) {
        $styles  = '';
        $scripts = '';
        $tw = wp_strip_all_tags( $tailwind_css );
        $tw = preg_replace( '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $tw );
        if ( $tw !== '' ) {
            $scope_suffix = $scope_id ? '-' . sanitize_key( $scope_id ) : '';
            // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Block output: Tailwind CSS is built per-request from block attributes.
            $styles .= '<style id="pg-tw-' . esc_attr( substr( md5( $tw ), 0, 8 ) . $scope_suffix ) . '">' . $tw . '</style>';
        }
        foreach ( $dependencies as $d ) {
            $type = $this->dep_item_value( $d, 'type', '' );
            $kind = $this->dep_item_value( $d, 'kind', 'external' );
            if ( $type === 'style' ) {
                $url = $this->dep_item_value( $d, 'url', '' );
                $cnt = $this->dep_item_value( $d, 'content', '' );
                if ( $kind === 'external' && $url !== '' ) {
                    $url = esc_url( $url );
                    if ( $url !== '' && strpos( $url, 'https://' ) === 0 ) {
                        // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Block deps: URL comes from API, enqueue not feasible.
                        $styles .= '<link rel="stylesheet" href="' . $url . '" />';
                    }
                } elseif ( $kind === 'inline' && $cnt !== '' ) {
                    $css = wp_strip_all_tags( $cnt );
                    $css = preg_replace( '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $css );
                    if ( $css !== '' ) {
                        $styles .= '<style>' . $css . '</style>';
                    }
                }
            } elseif ( $type === 'script' ) {
                $url = $this->dep_item_value( $d, 'url', '' );
                $cnt = $this->dep_item_value( $d, 'content', '' );
                if ( $kind === 'external' && $url !== '' ) {
                    $url = esc_url( $url );
                    if ( $url !== '' && strpos( $url, 'https://' ) === 0 ) {
                        $attr_val = $this->dep_item_value( $d, 'attributes', array() );
                        $attr     = is_array( $attr_val ) ? $attr_val : ( is_object( $attr_val ) ? (array) $attr_val : array() );
                        $async    = ! empty( $attr['async'] );
                        $defer    = ! empty( $attr['defer'] );
                        $type_attr = isset( $attr['type'] ) ? sanitize_key( $attr['type'] ) : '';
                        $extra    = ( $async ? ' async' : '' ) . ( $defer ? ' defer' : '' ) . ( $type_attr ? ' type="' . esc_attr( $type_attr ) . '"' : '' );
                        // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Block deps: URL from API, enqueue not feasible.
                        $scripts .= '<script src="' . $url . '"' . $extra . '></script>';
                    }
                } elseif ( $kind === 'inline' && $cnt !== '' ) {
                    $js = is_string( $cnt ) ? $cnt : '';
                    $js = preg_replace( '#</script>#i', '<\/script>', $js );
                    if ( $js !== '' ) {
                        $scripts .= '<script>' . $js . '</script>';
                    }
                }
            }
        }
        return array( 'styles' => $styles, 'scripts' => $scripts );
    }

    /**
     * 允许 data-* 属性通过 wp_kses 检查
     * 
     * @param array  $allowed_html 允许的 HTML 标签和属性
     * @param string $context      上下文
     * @return array
     */
    public function allow_data_attributes_in_kses( $allowed_html, $context ) {
        if ( ! is_array( $allowed_html ) ) {
            return $allowed_html;
        }
        foreach ( $allowed_html as $tag => $attrs ) {
            if ( is_array( $attrs ) ) {
                $allowed_html[ $tag ]['data-*'] = true;
            }
        }
        // 特别处理 form 标签（WordPress 对 form 有硬编码处理）
        if ( ! isset( $allowed_html['form'] ) || ! is_array( $allowed_html['form'] ) ) {
            $allowed_html['form'] = array();
        }
        $allowed_html['form']['data-*'] = true;
        
        return $allowed_html;
    }

    /**
     * 直接修改全局 allowedposttags 变量，确保 data-* 属性在所有场景都通过
     */
    public function modify_global_allowedposttags() {
        global $allowedposttags;
        if ( ! is_array( $allowedposttags ) ) {
            return;
        }
        
        // 为所有标签添加 data-* 通配符支持
        foreach ( $allowedposttags as $tag => $attrs ) {
            if ( is_array( $attrs ) ) {
                $allowedposttags[ $tag ]['data-*'] = true;
            }
        }
        
        // 特别处理 form 标签（WordPress 5.0.1 后 form 被移除，需要手动添加）
        if ( ! isset( $allowedposttags['form'] ) ) {
            $allowedposttags['form'] = array();
        }
        // 确保 form 标签有所有必要的属性
        $allowedposttags['form']['action'] = true;
        $allowedposttags['form']['method'] = true;
        $allowedposttags['form']['enctype'] = true;
        $allowedposttags['form']['target'] = true;
        $allowedposttags['form']['name'] = true;
        $allowedposttags['form']['accept'] = true;
        $allowedposttags['form']['accept-charset'] = true;
        $allowedposttags['form']['data-*'] = true;
        
        // 同时修改 allowedtags（用于评论等）
        global $allowedtags;
        if ( is_array( $allowedtags ) ) {
            foreach ( $allowedtags as $tag => $attrs ) {
                if ( is_array( $attrs ) ) {
                    $allowedtags[ $tag ]['data-*'] = true;
                }
            }
        }
    }

    private function get_allowed_html() {
        $allowed_html = wp_kses_allowed_html( 'post' );
        
        // 扩展允许的属性
        foreach ( $allowed_html as $tag => &$attrs ) {
            if ( ! is_array( $attrs ) ) {
                $attrs = array();
            }
            $attrs['class'] = true;
            $attrs['id'] = true;
            $attrs['style'] = true;
        }
        unset( $attrs );
        
        // SVG 相关标签
        $allowed_html['svg'] = array(
            'viewBox'     => true,
            'aria-hidden' => true,
            'class'       => true,
            'fill'        => true,
            'fill-opacity'=> true,
            'xmlns'       => true,
            'style'       => true,
            'width'       => true,
            'height'      => true,
            'stroke'      => true,
            'stroke-width' => true,
        );
        $allowed_html['path'] = array(
            'd'           => true,
            'fill'        => true,
            'fill-rule'   => true,
            'clip-rule'   => true,
            'stroke'      => true,
            'stroke-width' => true,
            'stroke-linecap' => true,
            'stroke-linejoin' => true,
        );
        $allowed_html['circle'] = array(
            'r'           => true,
            'cx'          => true,
            'cy'          => true,
            'fill'        => true,
            'fill-opacity'=> true,
            'stroke'      => true,
            'stroke-width' => true,
        );
        $allowed_html['rect'] = array(
            'x'           => true,
            'y'           => true,
            'width'       => true,
            'height'      => true,
            'rx'          => true,
            'ry'          => true,
            'fill'        => true,
            'stroke'      => true,
        );
        $allowed_html['line'] = array(
            'x1'          => true,
            'y1'          => true,
            'x2'          => true,
            'y2'          => true,
            'stroke'      => true,
            'stroke-width' => true,
        );
        $allowed_html['polyline'] = array(
            'points'      => true,
            'fill'        => true,
            'stroke'      => true,
            'stroke-width' => true,
        );
        $allowed_html['polygon'] = array(
            'points'      => true,
            'fill'        => true,
            'stroke'      => true,
        );
        $allowed_html['g'] = array(
            'fill'        => true,
            'stroke'      => true,
            'transform'   => true,
        );
        $allowed_html['defs'] = array();
        $allowed_html['clipPath'] = array(
            'id'          => true,
        );
        $allowed_html['linearGradient'] = array(
            'id'          => true,
            'x1'          => true,
            'y1'          => true,
            'x2'          => true,
            'y2'          => true,
        );
        $allowed_html['radialGradient'] = array(
            'id'          => true,
            'cx'          => true,
            'cy'          => true,
            'r'           => true,
        );
        $allowed_html['stop'] = array(
            'offset'      => true,
            'stop-color'  => true,
            'stop-opacity' => true,
        );
        
        // 扩展常用标签
        $common_attrs = array(
            'class' => true,
            'id'    => true,
            'style' => true,
        );
        
        $allowed_html['img'] = array_merge( $allowed_html['img'] ?? array(), array(
            'src'     => true,
            'alt'     => true,
            'width'   => true,
            'height'  => true,
            'loading' => true,
            'decoding' => true,
            'srcset'  => true,
            'sizes'   => true,
        ), $common_attrs );
        
        $allowed_html['a'] = array_merge( $allowed_html['a'] ?? array(), array(
            'href'   => true,
            'target' => true,
            'rel'    => true,
        ), $common_attrs );
        
        $allowed_html['button'] = array_merge( $allowed_html['button'] ?? array(), array(
            'type'     => true,
            'disabled' => true,
            'name'     => true,
            'value'    => true,
        ), $common_attrs );
        
        $allowed_html['input'] = array_merge( $allowed_html['input'] ?? array(), array(
            'type'        => true,
            'name'        => true,
            'value'       => true,
            'placeholder' => true,
            'required'    => true,
            'disabled'    => true,
            'readonly'    => true,
            'checked'     => true,
            'min'         => true,
            'max'         => true,
            'step'        => true,
        ), $common_attrs );
        
        $allowed_html['textarea'] = array_merge( $allowed_html['textarea'] ?? array(), array(
            'name'        => true,
            'placeholder' => true,
            'required'    => true,
            'disabled'    => true,
            'readonly'    => true,
            'rows'        => true,
            'cols'        => true,
        ), $common_attrs );
        
        $allowed_html['select'] = array_merge( $allowed_html['select'] ?? array(), array(
            'name'     => true,
            'required' => true,
            'disabled' => true,
            'multiple' => true,
        ), $common_attrs );
        
        $allowed_html['option'] = array(
            'value'    => true,
            'selected' => true,
            'disabled' => true,
        );
        
        $allowed_html['label'] = array_merge( $allowed_html['label'] ?? array(), array(
            'for' => true,
        ), $common_attrs );
        
        $allowed_html['form'] = array_merge( $allowed_html['form'] ?? array(), array(
            'action'  => true,
            'method'  => true,
            'enctype' => true,
            'target'  => true,
        ), $common_attrs );
        
        // 文本标签
        $text_tags = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div', 'section', 'article', 'header', 'footer', 'nav', 'main', 'aside', 'figure', 'figcaption', 'blockquote', 'pre', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'strong', 'em', 'small', 'mark', 'del', 'ins', 'sub', 'sup', 'time', 'address' );
        foreach ( $text_tags as $tag ) {
            if ( ! isset( $allowed_html[ $tag ] ) ) {
                $allowed_html[ $tag ] = array();
            }
            $allowed_html[ $tag ] = array_merge( $allowed_html[ $tag ], $common_attrs );
        }
        
        // video 和 audio 标签
        $allowed_html['video'] = array_merge( $allowed_html['video'] ?? array(), array(
            'src'      => true,
            'controls' => true,
            'autoplay' => true,
            'loop'     => true,
            'muted'    => true,
            'poster'   => true,
            'width'    => true,
            'height'   => true,
            'preload'  => true,
        ), $common_attrs );
        
        $allowed_html['audio'] = array_merge( $allowed_html['audio'] ?? array(), array(
            'src'      => true,
            'controls' => true,
            'autoplay' => true,
            'loop'     => true,
            'muted'    => true,
            'preload'  => true,
        ), $common_attrs );
        
        $allowed_html['source'] = array(
            'src'   => true,
            'type'  => true,
            'media' => true,
        );

        // canvas（编辑模式已有，前端 wp_kses 需显式放行否则会丢失）
        $allowed_html['canvas'] = array_merge( $common_attrs, array(
            'width'   => true,
            'height'  => true,
        ) );

        $allowed_html['iframe'] = array(
            'src'             => true,
            'width'           => true,
            'height'          => true,
            'frameborder'     => true,
            'allow'           => true,
            'allowfullscreen' => true,
            'loading'         => true,
            'title'           => true,
            'class'           => true,
            'id'              => true,
            'style'           => true,
        );
        
        // style 标签（用于内联 CSS）
        $allowed_html['style'] = array(
            'type'  => true,
            'id'    => true,
            'class' => true,
        );

        // Iconify Web Component 自定义元素
        $allowed_html['iconify-icon'] = array(
            'name'   => true,
            'width'  => true,
            'height' => true,
            'class'  => true,
            'id'     => true,
            'style'  => true,
            'inline' => true,
            'mode'   => true,
            'flip'   => true,
            'rotate' => true,
        );
        
        return $allowed_html;
    }

    /**
     * Sanitize images array: validate structure, base64 data, and MIME type.
     *
     * @param array $images Raw images array from client.
     * @return array Sanitized images array.
     */
    private static function sanitize_images_array( $images ) {
        if ( ! is_array( $images ) ) return array();
        $allowed_mime = array( 'image/png', 'image/jpeg', 'image/webp', 'image/gif' );
        $sanitized = array();
        foreach ( $images as $img ) {
            if ( ! is_array( $img ) ) continue;
            $data     = isset( $img['data'] ) && is_string( $img['data'] ) ? $img['data'] : '';
            $mime_type = isset( $img['mimeType'] ) && is_string( $img['mimeType'] ) ? $img['mimeType'] : '';
            if ( empty( $data ) || ! in_array( $mime_type, $allowed_mime, true ) ) continue;
            // Basic base64 validation: only allow valid chars
            if ( preg_match( '/[^a-zA-Z0-9+\/=]/', $data ) ) continue;
            $sanitized[] = array(
                'data'     => $data,
                'mimeType' => $mime_type,
            );
        }
        return $sanitized;
    }
}

// 单例实例化（自动注册钩子）
PufferGo_Tailwind_Container::instance();
