<?php

if (! defined('ABSPATH')) {
    exit;
} class PufferGo_Tailwind_Container
{
    public const EDIT_BLOCK_ENDPOINT = '/api/ai/edit-block';

    private static $instance = null;

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

return self::$instance;
    }

    private function __construct()
    {
        add_filter('wp_kses_allowed_html', array($this, 'allow_data_attributes_in_kses'), 9999, 2);
        add_action('init', array($this, 'modify_global_allowedposttags'), 1);
        add_action('init', array($this, 'modify_global_allowedposttags'), 999);
        add_action('init', array($this, 'register_render_callback'), 20);
        add_action('wp_enqueue_scripts', array($this, 'enqueue_block_styles_in_head'), 10);
        add_action('rest_api_init', array($this, 'register_rest_routes'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts'), 20);
        add_action('enqueue_block_editor_assets', array($this, 'enqueue_workflow_job_poll_config'), 25);
        add_action('enqueue_block_editor_assets', array($this, 'enqueue_alpine_in_editor'), 26);
    }

    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);
        }
    }

    public function enqueue_alpine_in_editor()
    {
        if (! function_exists('register_block_type')) {
            return;
        } $registry = WP_Block_Type_Registry::get_instance();
        $block = $registry->get_registered('puffergo/tailwind-container');

        if (! $block) {
            return;
        } $plugin_url = plugin_dir_url(dirname(__FILE__));
        wp_enqueue_script('puffergo-alpine-editor', $plugin_url . 'assets/js/alpine.min.js', array(), PUFFERGO_CORE_VERSION, true);
    }

    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;
        } $need_alpine = false;

        if (is_singular()) {
            $post = get_queried_object();

            if ($post && function_exists('has_block') && has_block('puffergo/tailwind-container', $post)) {
                $need_alpine = true;
            }
        }

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__));
        $hydrate_script = $plugin_path . 'assets/js/puffergo-ssr-hydrate.js';
        $hydrate_version = file_exists($hydrate_script) ? (string) filemtime($hydrate_script) : PUFFERGO_CORE_VERSION;
        wp_enqueue_script('puffergo-ssr-hydrate', $plugin_url . 'assets/js/puffergo-ssr-hydrate.js', array(), $hydrate_version, true);
        wp_script_add_data('puffergo-ssr-hydrate', 'defer', true);
        wp_enqueue_script('puffergo-alpine', $plugin_url . 'assets/js/alpine.min.js', array('puffergo-ssr-hydrate'), PUFFERGO_CORE_VERSION, true);
        wp_script_add_data('puffergo-alpine', 'defer', true);
        $contact_script = $plugin_path . 'assets/js/puffergo-contact-form.js';
        $contact_version = file_exists($contact_script) ? (string) filemtime($contact_script) : PUFFERGO_CORE_VERSION;
        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'), ), ));

        if (class_exists('PufferGo_Template_Config')) {
            $overlay = PufferGo_Template_Config::get_capability_values('header', 'header-overlay');

            if (isset($overlay['mode']) && 'on' === $overlay['mode']) {
                $overlay_css = $plugin_path . 'assets/css/puffergo-header-overlay.css';
                $overlay_js = $plugin_path . 'assets/js/puffergo-header-overlay.js';
                wp_enqueue_style('puffergo-header-overlay', $plugin_url . 'assets/css/puffergo-header-overlay.css', array(), file_exists($overlay_css) ? (string) filemtime($overlay_css) : PUFFERGO_CORE_VERSION);
                wp_enqueue_script('puffergo-header-overlay', $plugin_url . 'assets/js/puffergo-header-overlay.js', array(), file_exists($overlay_js) ? (string) filemtime($overlay_js) : PUFFERGO_CORE_VERSION, true);
                wp_script_add_data('puffergo-header-overlay', 'defer', true);
            }
        }
    }

    public const COMPILE_ENDPOINT = '/api/ai/compile-tailwind';

    public function register_rest_routes()
    {
        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'); }, ));
        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', ), ), ));
        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', ), ), ));
        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', ), ), ));
        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', ), ), ));
        register_rest_route('puffergo/v1', '/seo-meta', array('methods' => 'GET', 'callback' => array($this, 'rest_get_seo_meta'), 'permission_callback' => function () { return current_user_can('edit_posts'); }, 'args' => array('include' => array('required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), ), ));
        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', ), ), ));
        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'); }, ));
        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'); }, ));
    }

    public function rest_ai_generate($request)
    {
        $params = $request->get_json_params();

        if (empty($params['prompt'])) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('Prompt is required', 'puffergo'), ), 400);
        } $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;
        $ai_scene = isset($params['aiScene']) && is_string($params['aiScene']) ? sanitize_text_field(wp_unslash($params['aiScene'])) : '';

        try {
            $client = PufferGo_API_Client::get_instance();
            $api_payload = array('prompt' => $prompt, 'responseFormat' => $response_format, 'model' => $model, );

            if (! empty($system_prompt)) {
                $api_payload['systemPrompt'] = $system_prompt;
            }

if (! empty($ai_scene)) {
                $api_payload['aiScene'] = $ai_scene;
            }

if ($response_format === 'json_object' && ! empty($json_schema)) {
                $api_payload['jsonSchema'] = $json_schema;
            } 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');
            $result = $client->post('/api/ai/generate', $api_payload, array('timeout' => 120, 'log_prefix' => 'AI Generate', 'debug' => true, ));

            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 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) {
            puffergo_log('[AI Generate] API Exception: ' . $e->getMessage() . ' (Code: ' . $e->getCode() . ')');

            return new WP_REST_Response($e->toArray(), $e->getCode());
        } catch (Exception $e) {
            puffergo_log('[AI Generate] Error: ' . $e->getMessage());

            return new WP_REST_Response(array('success' => false, 'message' => $e->getMessage(), ), 500);
        }
    }

    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 code is required', 'puffergo'), ), 400);
        }

try {
            $result = $this->call_compile_api($html, $scope_id);

            return new WP_REST_Response(array('success' => true, 'data' => array('html' => isset($result['html']) ? $result['html'] : $html, 'css' => isset($result['css']) ? $result['css'] : '', ), ), 200);
        } catch (PufferGo_API_Exception $e) {
            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);
        }
    }

    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__('Search keyword is required', 'puffergo'), ), 400);
        }

try {
            $result = $this->call_images_search_api($keyword, $width, $height);
            $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);
        }
    }

    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);
        $ai_scene_param = $request->get_param('aiScene');
        $ai_scene = is_string($ai_scene_param) ? sanitize_text_field(wp_unslash($ai_scene_param)) : '';

        if (empty($img_keyword)) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('Image keyword is required', 'puffergo'), ), 400);
        }

if (! class_exists('PufferGo_API_Client')) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('API client not available', '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);
                }
            } $seo_suggest_body = array('imgKeyword' => $img_keyword, 'pageKeywords' => $page_keywords, 'siteKeywords' => $site_kw_sanitized, 'language' => $language, 'contextText' => $context_text, );

            if (! empty($ai_scene)) {
                $seo_suggest_body['aiScene'] = $ai_scene;
            } $result = $api_client->post('/api/ai/image-seo-suggest', $seo_suggest_body, array('timeout' => 20, 'log_prefix' => 'PufferGo Image SEO Suggest', ));

            if (empty($result['filename'])) {
                return new WP_REST_Response(array('success' => false, 'message' => esc_html__('AI returned invalid data', '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);
        }
    }

    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__('Page not found', 'puffergo'), ), 404);
        } $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))));
        } $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);
    }

    public function rest_get_seo_meta($request)
    {
        $raw = (string) $request->get_param('include');
        $ids = array_slice(array_values(array_unique(array_filter(array_map('absint', explode(',', $raw))))), 0, 100);
        $provider = $this->puffergo_detect_seo_provider();
        $items = array();

        foreach ($ids as $id) {
            if (! get_post($id) || ! current_user_can('edit_post', $id)) {
                continue;
            } $meta = $this->puffergo_read_seo_meta($id, $provider);
            $meta['id'] = $id;
            $items[] = $meta;
        }

return new WP_REST_Response(array('success' => true, 'provider' => $provider, 'items' => $items, ), 200);
    }

    private function puffergo_detect_seo_provider()
    {
        $provider = 'none';

        if (defined('RANK_MATH_VERSION') || class_exists('RankMath')) {
            $provider = 'rank-math';
        } elseif (defined('WPSEO_VERSION') || defined('WPSEO_FILE')) {
            $provider = 'yoast';
        }

return (string) apply_filters('puffergo_seo_provider', $provider);
    }

    private function puffergo_read_seo_meta($post_id, $provider)
    {
        if ('yoast' === $provider) {
            $meta = array('title' => (string) get_post_meta($post_id, '_yoast_wpseo_title', true), 'description' => (string) get_post_meta($post_id, '_yoast_wpseo_metadesc', true), 'focusKeyword' => (string) get_post_meta($post_id, '_yoast_wpseo_focuskw', true), );
        } else {
            $meta = array('title' => (string) get_post_meta($post_id, 'rank_math_title', true), 'description' => (string) get_post_meta($post_id, 'rank_math_description', true), 'focusKeyword' => (string) get_post_meta($post_id, 'rank_math_focus_keyword', true), );
        }

return (array) apply_filters('puffergo_seo_meta', $meta, $post_id, $provider);
    }

    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'));

        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__('Attachment not found', '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__('Filename must contain only lowercase letters, digits, and hyphens (2-60 chars)', 'puffergo'), ), 400);
            } $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);
                    update_attached_file($attachment_id_param, $new_file);
                    $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);
                }
            }

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

        if (empty($preview_url)) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('Image source URL is required', 'puffergo'), ), 400);
        }

if (empty($alt)) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('Alt text is required', '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__('Filename must contain only lowercase letters, digits, and hyphens (2-60 chars)', 'puffergo'), ), 400);
        } $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', 'puffergo-cdn.pages.dev', 'picsum.photos');
        $allowed_hosts = apply_filters('puffergo_sideload_allowed_hosts', $allowed_hosts);

        if (! in_array($parsed_host, $allowed_hosts, true)) {
            $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__('Image source domain is not allowed', '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__('Image download failed: ', 'puffergo') . $tmp_file->get_error_message(), ), 500);
        } $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;
        $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, );
        $attachment_id = media_handle_sideload($file_array, $post_id, $title ?: $filename_base);

        if (file_exists($file_path)) {
            @unlink($file_path);
        }

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__('Failed to save to media library: ', 'puffergo') . $attachment_id->get_error_message(), ), 500);
        } update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt);

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

if (! empty($author_credit)) {
            wp_update_post(array('ID' => $attachment_id, 'post_excerpt' => sanitize_text_field($author_credit), ));
        } $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);
    }

    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';
        $edit_intent = isset($params['editIntent']) ? sanitize_key(wp_unslash($params['editIntent'])) : 'general';
        $element_id = isset($params['elementId']) ? sanitize_text_field(wp_unslash($params['elementId'])) : '';
        $images = isset($params['images']) && is_array($params['images']) ? $params['images'] : array();
        $ai_scene = isset($params['aiScene']) && is_string($params['aiScene']) ? sanitize_text_field(wp_unslash($params['aiScene'])) : '';

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

if (empty($edit_instruction)) {
            return new WP_REST_Response(array('success' => false, 'message' => esc_html__('Edit instruction is required', 'puffergo'), ), 400);
        }

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

            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);
        }
    }

    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(), $edit_intent = 'general', $element_id = '', $ai_scene = '')
    {
        if (! class_exists('PufferGo_API_Client')) {
            throw new Exception(esc_html__('API client not available', 'puffergo'));
        } $api_client = PufferGo_API_Client::get_instance();
        $request_data = array('sectionHtml' => $section_html, 'editInstruction' => $edit_instruction, 'editIntent' => $edit_intent, '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($ai_scene)) {
            $request_data['aiScene'] = $ai_scene;
        }

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

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

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

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 service returned invalid data format', 'puffergo'));
        }

return $result;
    }

    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 {
            $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;
                } $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(esc_html__('Unsafe interaction tokens detected: %s. Please regenerate.', '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);
        }
    }

    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();
        $request_data = array('message' => $message, 'html' => $html, 'css' => $css, 'conversationHistory' => $conversation_history, 'themeContext' => $this->get_theme_context(), 'generationRules' => $this->get_generation_rules(), 'compilerConfig' => $this->get_compiler_config($scope_id), );
        $result = $api_client->post('/api/ai/chat-edit-block', $request_data, array('timeout' => 120, 'log_prefix' => 'PufferGo Chat Edit', ));

        if (! isset($result['html'])) {
            throw new Exception(esc_html__('AI service returned invalid data format', 'puffergo'));
        }

return $result;
    }

    private function call_images_search_api($keyword, $width = null, $height = null)
    {
        if (! class_exists('PufferGo_API_Client')) {
            throw new Exception(esc_html__('API client not available', '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;
    }

    private function call_compile_api($html, $scope_id)
    {
        if (! class_exists('PufferGo_API_Client')) {
            throw new Exception(esc_html__('API client not available', '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;
    }

    public function build_generate_block_request_body(array $client)
    {
        $prompt = isset($client['prompt']) ? sanitize_textarea_field(wp_unslash((string) $client['prompt'])) : '';

        if ('' === $prompt) {
            throw new Exception(esc_html__('Content description is required', 'puffergo'));
        } $scope_id = isset($client['scopeId']) ? sanitize_text_field(wp_unslash((string) $client['scopeId'])) : '';

        if ('' === $scope_id) {
            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 = null;

        if (isset($client['siteProfile']) && is_array($client['siteProfile'])) {
            $profile = $client['siteProfile'];
        } elseif (isset($client['profile']) && is_array($client['profile'])) {
            $profile = $client['profile'];
        }

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();
        $site_design_contract = isset($client['siteDesignContract']) && is_array($client['siteDesignContract']) ? $client['siteDesignContract'] : null;
        $block_capability = isset($client['blockCapability']) && is_array($client['blockCapability']) ? $client['blockCapability'] : null;
        $adjacent_context = isset($client['adjacentContext']) && is_array($client['adjacentContext']) ? $client['adjacentContext'] : null;
        $page_type = isset($client['pageType']) ? sanitize_text_field(wp_unslash((string) $client['pageType'])) : null;
        $ai_scene = isset($client['aiScene']) && is_string($client['aiScene']) ? sanitize_text_field(wp_unslash($client['aiScene'])) : null;

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

    private function compose_generate_block_request_data($prompt, $model, $block_type, $scope_id, $language, $style_mode, $design_system, $profile, $images = array(), $site_design_contract = null, $block_capability = null, $adjacent_context = null, $page_type = null, $ai_scene = null)
    {
        $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);
        }

if (! empty($site_design_contract) && is_array($site_design_contract)) {
            $request_data['siteDesignContract'] = $site_design_contract;
        }

if (! empty($block_capability) && is_array($block_capability)) {
            if (class_exists('PufferGo_Json_Object_Codec')) {
                $block_capability = PufferGo_Json_Object_Codec::normalize_block_capability($block_capability);
            } $request_data['blockCapability'] = $block_capability;
        }

if (! empty($adjacent_context) && is_array($adjacent_context)) {
            $request_data['adjacentContext'] = $adjacent_context;
        }

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

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

return $request_data;
    }

    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'], );
    }

    private function get_generation_rules()
    {
        return array('themeTokens' => array('ui' => array('rounded-theme' => '组件圆角 (rounded-xl)。用于卡片、大按钮、输入框。', 'rounded-full' => '功能性圆角 (rounded-full)。强制用于：Tag 标签、Icon 容器、头像。', 'shadow-theme' => '卡片投影 (shadow-lg)。用于 bg-neutral-50 材质的卡片悬浮感。', )), '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));
    }

    private function get_compiler_config($scope_id)
    {
        $config = array('important' => '.' . $scope_id, );
        $tokens = PufferGo_Design_System::get_active_tokens();

        if (is_array($tokens) && isset($tokens['decorEffects']) && 'off' === $tokens['decorEffects']) {
            $config['decorEffects'] = 'off';
        }

return $config;
    }

    private function get_theme_bridge()
    {
        static $bridge = null;

        if ($bridge === null) {
            $bridge = new PufferGo_Theme_Bridge();
        }

return $bridge;
    }

    private function get_theme_values()
    {
        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', );
            }
        }

return array('primaryColor' => '#3b82f6', 'secondaryColor' => '#f59e0b', 'baseColor' => '#ffffff', 'borderRadius' => '8px', 'fontHeading' => 'sans-serif', 'fontBody' => 'sans-serif', );
    }

    public function register_render_callback()
    {
        $registry = WP_Block_Type_Registry::get_instance();

        if (! $registry->is_registered('puffergo/tailwind-container')) {
            return;
        } $block_type = $registry->get_registered('puffergo/tailwind-container');

        if ($block_type) {
            $block_type->render_callback = array($this, '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);
        } $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);
            }
        } $required_libs = $this->collect_required_libs_from_blocks($blocks);

        foreach ($required_libs as $lib_handle) {
            if (class_exists('PufferGo_Library_Registry')) {
                PufferGo_Library_Registry::enqueue($lib_handle);
            }
        }
    }

    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 !== '[]');

                if ($has_deps) {
                } 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));
    }

    private function collect_required_libs_from_blocks($blocks)
    {
        $handles = array();

        if (! is_array($blocks)) {
            return $handles;
        }

foreach ($blocks as $block) {
            if (isset($block['blockName']) && $block['blockName'] === 'puffergo/tailwind-container') {
                $attrs = isset($block['attrs']) ? $block['attrs'] : array();
                $req_raw = isset($attrs['requires']) ? $attrs['requires'] : null;

                if (is_array($req_raw)) {
                    foreach ($req_raw as $h) {
                        if (is_string($h) && $h !== '') {
                            $handles[] = sanitize_key($h);
                        }
                    }
                }
            }

if (! empty($block['innerBlocks'])) {
                $inner = $this->collect_required_libs_from_blocks($block['innerBlocks']);
                $handles = array_merge($handles, $inner);
            }
        } $handles = array_filter($handles);

        return array_values(array_unique($handles));
    }

    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'] : '';

                if ($tailwind_css !== '' || $has_deps) {
                } 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;
    }

    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);
    }

    private function strip_editor_attrs($html)
    {
        if (empty($html) || ! is_string($html)) {
            return $html;
        } $editor_attrs = array('data-pg-el-id', 'data-pg-id', 'data-pg-type', 'data-pg-href', 'data-pg-iconify', 'data-pg-unsaved', 'data-pg-preview-url', 'data-pg-author', 'data-pg-author-url', );

        foreach ($editor_attrs as $attr) {
            $html = preg_replace('/\s*' . preg_quote($attr, '/') . '\s*=\s*["\'][^"\']*["\']/i', ' ', $html);
            $html = preg_replace('/\s*' . preg_quote($attr, '/') . '\s*=\s*[^\s>]+/i', ' ', $html);
        }

return $html;
    }

    private static function encode_gt_lt_in_attrs($html)
    {
        return preg_replace_callback('/="([^"]*)"/', static function ($m) { return '="' . str_replace(array('>', '<'), array('&gt;', '&lt;'), $m[1]) . '"'; }, $html);
    }

    public function render_block($attributes, $content, $block)
    {
        if ($block instanceof \WP_Block && property_exists($block, 'attributes')) {
            $attrs = $block->attributes;
        } elseif (is_array($block) && isset($block['attrs'])) {
            $attrs = $block['attrs'];
        } else {
            $attrs = is_array($attributes) ? $attributes : array();
        } $html_content = isset($attrs['content']) ? $attrs['content'] : '';
        $scope_id = isset($attrs['scopeId']) ? $attrs['scopeId'] : '';
        $custom_css_override = isset($attrs['customCss']) ? trim($attrs['customCss']) : '';
        $tailwind_css = $custom_css_override !== '' ? $custom_css_override : (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 '';
        } $is_config_component = ! empty($attrs['configTemplate']);
        $is_trusted_alpine = ! $is_config_component && (bool) preg_match('/\sx-data\s*=/', $html_content);

        if ($is_config_component) {
            $config_template = isset($attrs['configTemplate']) ? $attrs['configTemplate'] : '';
            $config_data_raw = isset($attrs['configData']) ? $attrs['configData'] : array();
            $config_data = is_array($config_data_raw) ? $config_data_raw : array();
            $block_style_raw = isset($attrs['blockStyle']) ? $attrs['blockStyle'] : array();
            $block_style = is_array($block_style_raw) ? $block_style_raw : array();

            if (! empty($config_template) && ! empty($config_data) && class_exists('PufferGo_Alpine_SSR')) {
                $html_content = PufferGo_Alpine_SSR::render($config_template, $config_data, $block_style);
            }
        } elseif ($is_trusted_alpine) {
            $html_content = self::encode_gt_lt_in_attrs($html_content);
        } else {
            $html_content = preg_replace('/(<details(?:\s[^>]*)?)\bopen\b(?!\s*=)/i', '$1open=""', $html_content);
            $allowed_html = $this->get_allowed_html();
            $html_content = wp_kses($html_content, $allowed_html);
        } $html_content = $this->strip_editor_attrs($html_content);
        $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'] : '';
        } $req_raw = isset($attrs['requires']) ? $attrs['requires'] : array();

        if (is_array($req_raw) && ! empty($req_raw) && class_exists('PufferGo_Library_Registry')) {
            foreach ($req_raw as $lib_handle) {
                if (is_string($lib_handle) && '' !== $lib_handle) {
                    PufferGo_Library_Registry::enqueue(sanitize_key($lib_handle));
                }
            }
        } $wrapper_attributes = get_block_wrapper_attributes(array('class' => $scope_id));
        $scope_key = is_string($scope_id) ? sanitize_key($scope_id) : '';
        $accordion_collapse = in_array($scope_key, array('faq-centered-accordion'), true) ? $this->get_faq_accordion_frontend_collapse_inline_script() : '';
        $out = $assets_styles . sprintf('<div %s>%s%s</div>', $wrapper_attributes, $html_content, $accordion_collapse) . $assets_scripts;

        return $out;
    }

    private function get_faq_accordion_frontend_collapse_inline_script()
    {
        $js = '(function(){var b=document.body;if(!b||b.classList.contains("wp-admin")){return;}var s=document.currentScript;if(!s){return;}var r=s.previousElementSibling;if(!r){return;}var d=r.querySelectorAll("details[open]"),i;for(i=0;i<d.length;i++){d[i].removeAttribute("open");}}());';

        return '<script>' . $js . '</script>';
    }

    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;
    }

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

    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;
            }
        }

if (! isset($allowed_html['form']) || ! is_array($allowed_html['form'])) {
            $allowed_html['form'] = array();
        } $allowed_html['form']['data-*'] = true;

        return $allowed_html;
    }

    public function modify_global_allowedposttags()
    {
        global $allowedposttags;

        if (! is_array($allowedposttags)) {
            return;
        }

foreach ($allowedposttags as $tag => $attrs) {
            if (is_array($attrs)) {
                $allowedposttags[ $tag ]['data-*'] = true;
            }
        }

if (! isset($allowedposttags['form'])) {
            $allowedposttags['form'] = array();
        } $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;
        global $allowedtags;

        if (is_array($allowedtags)) {
            foreach ($allowedtags as $tag => $attrs) {
                if (is_array($attrs)) {
                    $allowedtags[ $tag ]['data-*'] = true;
                }
            }
        } $allowedposttags['template'] = array('x-for' => true, 'x-bind:key' => true, 'x-if' => true, );

        if (isset($allowedtags['template']) && is_array($allowedtags['template'])) {
            $allowedtags['template'] = array_merge($allowedtags['template'], array('x-for' => true, 'x-bind:key' => true, 'x-if' => true, ));
        } else {
            $allowedtags['template'] = array('x-for' => true, 'x-bind:key' => true, 'x-if' => true, );
        } $alpine_attrs = array('x-data' => true, 'x-for' => true, 'x-if' => true, 'x-show' => true, 'x-text' => true, 'x-html' => true, 'x-bind:src' => true, 'x-bind:href' => true, 'x-bind:alt' => true, 'x-bind:class' => true, 'x-bind:style' => true, 'x-bind:disabled' => true, 'x-bind:hidden' => true, 'x-bind:value' => true, 'x-bind:checked' => true, 'x-bind:selected' => true, 'x-bind:placeholder' => true, 'x-bind:title' => true, 'x-bind:key' => true, 'x-on:click' => true, 'x-on:click.prevent' => true, 'x-on:keydown' => true, 'x-on:submit' => true, 'x-on:mouseenter' => true, 'x-on:mouseleave' => true, 'x-on:keyup' => true, 'x-model' => true, 'x-ref' => true, 'x-cloak' => true, 'x-transition' => true, );

        foreach ($allowedposttags as $tag => &$attrs) {
            if (is_array($attrs)) {
                $allowedposttags[ $tag ] = array_merge($attrs, $alpine_attrs);
            }
        } unset($attrs);

        foreach ($allowedtags as $tag => &$attrs) {
            if (is_array($attrs)) {
                $allowedtags[ $tag ] = array_merge($attrs, $alpine_attrs);
            }
        } unset($attrs);
    }

    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);
        $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);
        $allowed_html['details'] = array_merge($common_attrs, array('open' => true, ));
        $allowed_html['summary'] = array_merge($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);
        } $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, );
        $allowed_html['template'] = array('x-for' => true, 'x-bind:key' => true, 'x-if' => true, );
        $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, );
        $allowed_html['style'] = array('type' => true, 'id' => true, 'class' => true, );
        $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, );
        $alpine_attrs = array('x-data' => true, 'x-for' => true, 'x-if' => true, 'x-show' => true, 'x-text' => true, 'x-html' => true, 'x-bind:src' => true, 'x-bind:href' => true, 'x-bind:alt' => true, 'x-bind:class' => true, 'x-bind:style' => true, 'x-bind:disabled' => true, 'x-bind:hidden' => true, 'x-bind:value' => true, 'x-bind:checked' => true, 'x-bind:selected' => true, 'x-bind:placeholder' => true, 'x-bind:title' => true, 'x-bind:key' => true, 'x-on:click' => true, 'x-on:click.prevent' => true, 'x-on:keydown' => true, 'x-on:submit' => true, 'x-on:mouseenter' => true, 'x-on:mouseleave' => true, 'x-on:keyup' => true, 'x-model' => true, 'x-ref' => true, 'x-cloak' => true, 'x-transition' => true, 'popovertarget' => true, 'popovertargetaction' => true, );

        foreach ($allowed_html as $tag => &$attrs) {
            if (is_array($attrs)) {
                $allowed_html[ $tag ] = array_merge($attrs, $alpine_attrs);
            }
        } unset($attrs);

        return $allowed_html;
    }

    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;
            }

if (preg_match('/[^a-zA-Z0-9+\/=]/', $data)) {
                continue;
            } $sanitized[] = array('data' => $data, 'mimeType' => $mime_type, );
        }

return $sanitized;
    }
} PufferGo_Tailwind_Container::instance();
