<?php
/**
 * Plugin Name: ZenBlocks
 * Plugin URI: https://zenblockswp.com/
 * Description: Custom blocks for WordPress block editor (Gutenberg)
 * Version: 1.1.0
 * Author: Tsumiki LLC.
 * Author URI: https://tsumiki.co/
 * License: GPL v2 or later
 * Text Domain: zenblocks
 */

// Exit if accessed directly
if (!defined('ABSPATH')) {
    exit;
}

// Include required files
require_once plugin_dir_path(__FILE__) . 'includes/class-responsive-css-generator.php';
require_once plugin_dir_path(__FILE__) . 'includes/class-block-css-generator.php';

// Define constants
define('ZENBLOCKS_VERSION', '1.1.0');
define('ZENBLOCKS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('ZENBLOCKS_PLUGIN_URL', set_url_scheme(plugin_dir_url(__FILE__)));

// Pro plugin active check helper.
if ( ! function_exists( 'is_plugin_active' ) ) {
    require_once ABSPATH . 'wp-admin/includes/plugin.php';
}

class ZenBlocks {

    private $default_breakpoints = array(
        'enabled' => array('pc', 'md', 'tb', 'sp'),
        'values' => array(
            'lg' => 1280,
            'md' => 1140,
            'tb' => 840,
            'sp' => 540,
            'mn' => 320,
        ),
    );

    private $legacy_breakpoints = array(
        'sp_max' => 781,
        'tb_min' => 782,
        'tb_max' => 1024,
        'pc_min' => 1025,
    );

    public function __construct() {
        add_action('init', array($this, 'init'));
        add_action('enqueue_block_editor_assets', array($this, 'enqueue_editor_assets'));
        add_action('enqueue_block_assets', array($this, 'enqueue_frontend_assets'));
        add_filter('style_loader_src', array($this, 'normalize_zenblocks_asset_src'), 20, 2);
        add_filter('script_loader_src', array($this, 'normalize_zenblocks_asset_src'), 20, 2);
        add_filter('block_categories_all', array($this, 'register_block_category'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'register_settings'));
        add_action('admin_init', array($this, 'register_breakpoint_settings'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
        add_filter('plugin_row_meta', array($this, 'plugin_row_meta_blank'), 10, 2);
        // License activation/deactivation is handled by the zenblocks-pro plugin.
        add_action('rest_api_init', array($this, 'register_rest_routes'));
        // PC base styles (no media queries) are needed in both editor and frontend
        // for preset previews and base block styling.
        add_action('enqueue_block_assets', array($this, 'enqueue_responsive_base_css'), 20);
        // Full responsive CSS (with media queries) on frontend only.
        // Editor uses SCSS + inline CSS variables + getPreviewStyles for responsive preview.
        // Loading media query CSS in the editor iframe causes !important rules
        // to override inline styles, breaking responsive preview.
        add_action('wp_enqueue_scripts', array($this, 'enqueue_responsive_css'), 20);
        // Breakpoint CSS variables are needed in both editor and frontend.
        add_action('enqueue_block_assets', array($this, 'enqueue_breakpoint_css'), 21);

        // Graceful degradation: strip Pro-only HTML elements on frontend
        // when zenblocks-pro plugin is not active.
        add_filter('render_block', array($this, 'strip_pro_elements_fallback'), 10, 2);

        // Block CSS generation on save
        add_action('save_post', array($this, 'generate_block_css'), 10, 2);
        add_action('wp_enqueue_scripts', array($this, 'enqueue_block_css'), 21);

        // CSS cache management
        add_action('wp_ajax_zenblocks_clear_css_cache', array($this, 'ajax_clear_css_cache'));
        add_action('upgrader_process_complete', array($this, 'on_plugin_update'), 10, 2);
    }

    /**
     * Normalize malformed ZenBlocks asset URLs generated by external URL rewriters.
     *
     * Some environments can rewrite asset URLs into:
     * /wp-content/themes/<theme>/Users/.../wp-content/plugins/zenblocks/...
     * which returns 500. This restores canonical plugin asset URLs.
     *
     * @param string $src    Asset URL.
     * @param string $handle Enqueued asset handle.
     * @return string
     */
    public function plugin_row_meta_blank($plugin_meta, $plugin_file) {
        $our_plugins = array(
            'zenblocks/zenblocks.php',
            'zenblocks-plus/zenblocks-plus.php',
            'zenblocks-pro/zenblocks-pro.php',
        );
        if (in_array($plugin_file, $our_plugins, true)) {
            $lang_path = $this->get_locale_url_path();
            foreach ($plugin_meta as $key => $value) {
                // Add language path to tsumiki.co URLs (Author URI)
                if (strpos($value, 'tsumiki.co') !== false && $lang_path) {
                    $value = preg_replace(
                        '#(https?://tsumiki\.co)/?(["\'])#',
                        '$1/' . $lang_path . '/$2',
                        $value
                    );
                }
                // Add language path to zenblockswp.com URLs (Plugin URI)
                if (strpos($value, 'zenblockswp.com') !== false) {
                    if ($lang_path) {
                        $value = preg_replace(
                            '#(https?://zenblockswp\.com)/?(["\'])#',
                            '$1/' . $lang_path . '/$2',
                            $value
                        );
                    }
                }
                // Add target="_blank" if not already present
                if ((strpos($value, 'tsumiki.co') !== false || strpos($value, 'zenblockswp.com') !== false)
                    && strpos($value, 'target=') === false) {
                    $value = str_replace('<a ', '<a target="_blank" rel="noopener noreferrer" ', $value);
                }
                $plugin_meta[$key] = $value;
            }
        }
        return $plugin_meta;
    }

    /**
     * Get URL path segment based on WordPress locale.
     */
    private function get_locale_url_path() {
        $locale = get_user_locale();
        $locale_map = array(
            'ja'             => 'ja',
            'en_US'          => 'en',
            'en_GB'          => 'en',
            'en_AU'          => 'en',
            'en_CA'          => 'en',
            'en_NZ'          => 'en',
            'en_ZA'          => 'en',
            'de_DE'          => 'de',
            'de_DE_formal'   => 'de',
            'de_AT'          => 'de',
            'de_CH'          => 'de',
            'de_CH_informal' => 'de',
            'fr_FR'          => 'fr',
            'fr_BE'          => 'fr',
            'fr_CA'          => 'fr',
            'nl_NL'          => 'nl',
            'nl_NL_formal'   => 'nl',
            'nl_BE'          => 'nl',
            'ko_KR'          => 'ko',
            'it_IT'          => 'it',
            'es_ES'          => 'es',
            'es_MX'          => 'es',
            'es_AR'          => 'es',
            'es_CO'          => 'es',
            'es_CL'          => 'es',
            'es_VE'          => 'es',
            'es_CR'          => 'es',
            'es_GT'          => 'es',
            'es_PE'          => 'es',
            'zh_TW'          => 'zh-tw',
            'pt_BR'          => 'pt-br',
            'id_ID'          => 'id',
        );
        return isset($locale_map[$locale]) ? $locale_map[$locale] : 'en';
    }

    public function normalize_zenblocks_asset_src($src, $handle = '') {
        if (!is_string($src) || $src === '') {
            return $src;
        }

        $decoded_src = rawurldecode($src);
        $normalized_src = wp_normalize_path($decoded_src);
        $needle = '/wp-content/plugins/zenblocks/';
        $needle_pos = strpos($normalized_src, $needle);

        if ($needle_pos === false) {
            return $src;
        }

        $has_malformed_prefix = (
            strpos($normalized_src, '/wp-content/themes/') !== false ||
            strpos($normalized_src, '/Users/') !== false
        );

        if (!$has_malformed_prefix) {
            return $src;
        }

        $plugin_relative_with_query = substr($decoded_src, $needle_pos + strlen($needle));
        $query_string = '';
        $query_pos = strpos($plugin_relative_with_query, '?');
        if ($query_pos !== false) {
            $query_string = substr($plugin_relative_with_query, $query_pos);
            $plugin_relative_with_query = substr($plugin_relative_with_query, 0, $query_pos);
        }

        $plugin_relative = ltrim(wp_normalize_path($plugin_relative_with_query), '/');
        if ($plugin_relative === '') {
            return $src;
        }

        return ZENBLOCKS_PLUGIN_URL . $plugin_relative . $query_string;
    }


    /**
     * Graceful degradation: strip Pro-only HTML elements from saved block
     * content when the zenblocks-pro add-on is not active.
     *
     * This ensures that if a user deactivates zenblocks-pro, orphaned Pro
     * elements (thumbnail, band, bg-movie, frame, micro/sub text, hover spans)
     * are cleaned up on the frontend. When zenblocks-pro IS active, it handles
     * its own filtering via render_block, so this method does nothing.
     *
     * @param string $block_content Rendered block HTML.
     * @param array  $block Parsed block data.
     * @return string
     */
    public function strip_pro_elements_fallback( $block_content, $block ) {
        // Skip if zenblocks-pro is active (it handles its own filtering).
        if ( defined( 'ZENBLOCKS_PRO_VERSION' ) ) {
            return $block_content;
        }

        if ( is_admin() || empty( $block_content ) || ! is_array( $block ) ) {
            return $block_content;
        }

        if ( ( $block['blockName'] ?? '' ) !== 'zenblocks/button-basic' ) {
            return $block_content;
        }

        // Check if this block actually contains Pro elements before parsing DOM.
        $pro_markers = array(
            'button-basic-thumbnail',
            'button-basic-band',
            'button-basic-bg-movie',
            'button-basic-bg-poster',
            'button-basic-frame',
            'button-basic-text-micro',
            'button-basic-text-sub',
        );
        $has_pro = false;
        foreach ( $pro_markers as $marker ) {
            if ( strpos( $block_content, $marker ) !== false ) {
                $has_pro = true;
                break;
            }
        }
        if ( ! $has_pro ) {
            return $block_content;
        }

        if ( ! class_exists( 'DOMDocument' ) || ! class_exists( 'DOMXPath' ) ) {
            return $block_content;
        }

        $internal_errors = libxml_use_internal_errors( true );

        $dom = new DOMDocument( '1.0', 'UTF-8' );
        $loaded = $dom->loadHTML(
            '<?xml encoding="utf-8" ?>' . $block_content,
            LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
        );

        if ( ! $loaded ) {
            libxml_clear_errors();
            libxml_use_internal_errors( $internal_errors );
            return $block_content;
        }

        $xpath = new DOMXPath( $dom );

        // Remove Pro-only visual elements.
        $pro_only_classes = array(
            'wp-block-zenblocks-button-basic-thumbnail',
            'wp-block-zenblocks-button-basic-band',
            'wp-block-zenblocks-button-basic-bg-movie',
            'wp-block-zenblocks-button-basic-bg-poster',
            'wp-block-zenblocks-button-basic-frame',
            'wp-block-zenblocks-button-basic-text-micro',
            'wp-block-zenblocks-button-basic-text-sub',
        );

        foreach ( $pro_only_classes as $class_name ) {
            $query = '//*[contains(concat(" ", normalize-space(@class), " "), " ' . $class_name . ' ")]';
            $nodes = $xpath->query( $query );
            if ( ! $nodes ) {
                continue;
            }
            $to_remove = array();
            foreach ( $nodes as $node ) {
                $to_remove[] = $node;
            }
            foreach ( $to_remove as $node ) {
                if ( $node->parentNode ) {
                    $node->parentNode->removeChild( $node );
                }
            }
        }

        // Strip Pro animation classes from the root wrapper.
        $root = $dom->documentElement;
        if ( $root instanceof DOMElement ) {
            $class_attr = $root->getAttribute( 'class' );
            if ( '' !== $class_attr ) {
                $classes = preg_split( '/\s+/', trim( $class_attr ) );
                $classes = array_filter(
                    (array) $classes,
                    function ( $cls ) {
                        return ! (
                            0 === strpos( $cls, 'zenblocks-button-hover-animation-icon-' ) ||
                            0 === strpos( $cls, 'zenblocks-button-hover-animation-text-' ) ||
                            0 === strpos( $cls, 'zenblocks-button-frame-animation-' ) ||
                            0 === strpos( $cls, 'zenblocks-button-animation-' ) ||
                            0 === strpos( $cls, 'zenblocks-hidden-' ) ||
                            'is-bg-video' === $cls
                        );
                    }
                );
                $root->setAttribute( 'class', trim( implode( ' ', $classes ) ) );
            }
        }

        $result = $dom->saveHTML();
        libxml_clear_errors();
        libxml_use_internal_errors( $internal_errors );

        return is_string( $result ) ? $result : $block_content;
    }

    public function init() {
        // Plugin text domain is automatically loaded by WordPress 4.6+ for plugins hosted on WordPress.org.

        // Register blocks
        $this->register_blocks();

        // Register pattern categories for each block
        $this->register_pattern_categories();
    }

    public function register_pattern_categories() {
        $block_categories = array(
            'buttonbasic' => __('Button Basic', 'zenblocks'),
            'textbasic' => __('Text Basic', 'zenblocks'),
        );

        foreach ($block_categories as $slug => $label) {
            if (!term_exists($slug, 'wp_pattern_category')) {
                wp_insert_term($label, 'wp_pattern_category', array(
                    'slug' => $slug,
                ));
            }
        }
    }

    public function register_blocks() {
        // Register sample basic element block (disabled for initial release)
        // if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/sampleBasic/')) {
        //     register_block_type(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/sampleBasic/');
        // }

        // Register text basic element block (disabled for initial release)
        // if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/textBasic/')) {
        //     register_block_type(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/textBasic/');
        // }

        // Register button basic element block
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/buttonBasic/')) {
            register_block_type(ZENBLOCKS_PLUGIN_DIR . 'build/blocks/block-element/buttonBasic/');
        }
    }

    public function enqueue_editor_assets() {
        // Enqueue editor-only CSS
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/index.css')) {
            wp_enqueue_style(
                'zenblocks-editor',
                ZENBLOCKS_PLUGIN_URL . 'build/index.css',
                array(),
                ZENBLOCKS_VERSION
            );
        }

        // Enqueue editor extensions CSS
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/editor/style-index.css')) {
            wp_enqueue_style(
                'zenblocks-editor-extensions',
                ZENBLOCKS_PLUGIN_URL . 'build/editor/style-index.css',
                array(),
                ZENBLOCKS_VERSION
            );
        }

        // Enqueue main editor script
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/index.js')) {
            wp_enqueue_script(
                'zenblocks-editor-script',
                ZENBLOCKS_PLUGIN_URL . 'build/index.js',
                array('wp-blocks', 'wp-element', 'wp-editor'),
                ZENBLOCKS_VERSION,
                true
            );

            wp_localize_script('zenblocks-editor-script', 'zenBlocks', array(
                'license' => $this->get_license_data(),
                'breakpoints' => $this->get_breakpoints(),
                'pluginUrl' => ZENBLOCKS_PLUGIN_URL,
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'adminUrl' => admin_url(),
                'isBlockTheme' => wp_is_block_theme(),
                'cssCacheNonce' => wp_create_nonce('zenblocks_clear_css_cache')
            ));

            wp_set_script_translations('zenblocks-editor-script', 'zenblocks', ZENBLOCKS_PLUGIN_DIR . 'languages');
        }

        // Enqueue editor extensions script
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/editor/index.js')) {
            wp_enqueue_script(
                'zenblocks-editor-extensions-script',
                ZENBLOCKS_PLUGIN_URL . 'build/editor/index.js',
                array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-data', 'wp-plugins', 'wp-edit-post', 'wp-i18n'),
                ZENBLOCKS_VERSION,
                true
            );

            wp_set_script_translations('zenblocks-editor-extensions-script', 'zenblocks', ZENBLOCKS_PLUGIN_DIR . 'languages');
        }

        // Block scripts registered via block.json
        wp_set_script_translations('zenblocks-button-basic-editor-script', 'zenblocks', ZENBLOCKS_PLUGIN_DIR . 'languages');
    }

    public function enqueue_frontend_assets() {
        // Enqueue frontend styles if they exist
        if (file_exists(ZENBLOCKS_PLUGIN_DIR . 'build/style-index.css')) {
            wp_enqueue_style(
                'zenblocks-frontend',
                ZENBLOCKS_PLUGIN_URL . 'build/style-index.css',
                array(),
                ZENBLOCKS_VERSION
            );
        }
    }

    public function register_block_category($categories) {
        return array_merge(
            array(
                array(
                    'slug'  => 'zenblocks',
                    'title' => __('ZenBlocks Elements', 'zenblocks'),
                    'icon'  => 'screenoptions',
                ),
                array(
                    'slug'  => 'zenblocks-layouts',
                    'title' => __('ZenBlocks Layouts', 'zenblocks'),
                    'icon'  => 'layout',
                ),
            ),
            $categories
        );
    }

    public function add_admin_menu() { add_menu_page( __('ZenBlocks Settings', 'zenblocks' ),
    __('ZenBlocks', 'zenblocks' ), 'manage_options' , 'zenblocks' , array($this, 'admin_page'
    ), 'data:image/svg+xml;base64,' . base64_encode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 299">
    <path fill="black"
      d="M222.12,68.81c.63-1.06-.13-2.41-1.37-2.41H88.85c-.66,0-1.2.54-1.2,1.2v35.18c0,.66.54,1.2,1.2,1.2h63.24c1.24,0,2,1.35,1.37,2.41l-75.58,123.8c-.63,1.06.13,2.41,1.37,2.41h131.91c.66,0,1.2-.54,1.2-1.2v-35.18c0-.66-.54-1.2-1.2-1.2h-65.4c-1.24,0-2-1.35-1.37-2.41l77.74-123.8Z" />
    </svg>'),
    99
    );
    }

    public function register_settings() {
    register_setting('zenblocks_settings', 'zenblocks_unsplash_access_key', array(
    'type' => 'string',
    'sanitize_callback' => 'sanitize_text_field',
    'default' => '',
    ));
    register_setting('zenblocks_settings', 'zenblocks_pexels_api_key', array(
    'type' => 'string',
    'sanitize_callback' => 'sanitize_text_field',
    'default' => '',
    ));
    register_setting('zenblocks_settings', 'zenblocks_pixabay_api_key', array(
    'type' => 'string',
    'sanitize_callback' => 'sanitize_text_field',
    'default' => '',
    ));
    }

    /**
     * Enqueue admin page assets.
     *
     * @param string $hook_suffix Current admin page hook suffix.
     */
    /**
     * Enqueue admin page assets.
     *
     * @param string $hook_suffix Current admin page hook suffix.
     */
    public function enqueue_admin_assets( $hook_suffix ) {
        if ( 'toplevel_page_zenblocks' !== $hook_suffix ) {
            return;
        }

        $css = '
.zb-settings { max-width: 720px; margin: 40px auto 60px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
.zb-header { display: flex; align-items: center; gap: 14px; margin-bottom: 32px; }
.zb-header svg { width: 36px; height: 36px; }
.zb-header h1 { font-size: 22px; font-weight: 600; margin: 0; color: #1d2327; }
.zb-header .zb-ver { font-size: 12px; color: #999; font-weight: 400; margin-left: 4px; }
.zb-tabs { display: flex; gap: 0; border-bottom: 1px solid #e0e0e0; margin-bottom: 24px; }
.zb-tab { padding: 10px 20px; font-size: 14px; font-weight: 500; color: #666; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; margin-bottom: -1px; transition: color .15s; }
.zb-tab:hover { color: #1d2327; }
.zb-tab.is-active { color: #1d2327; border-bottom-color: #1d2327; font-weight: 600; }
.zb-tab-panel { display: none; }
.zb-tab-panel.is-active { display: block; }
.zb-card { background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; padding: 28px 32px; margin-bottom: 20px; }
.zb-card h2 { font-size: 15px; font-weight: 600; color: #1d2327; margin: 0 0 16px; padding: 0; }
.zb-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.zb-card-header h2 { margin: 0; }
.zb-license-status { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 500; }
.zb-license-status.is-free { background: #f0f0f0; color: #666; }
.zb-license-status.is-plus { background: #2563eb; color: #fff; }
.zb-license-status.is-pro { background: #e6f4ea; color: #1a7431; }
.zb-license-detail { margin-top: 20px; }
.zb-license-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.zb-license-detail-item { background: #f9fafb; border: 1px solid #eee; border-radius: 6px; padding: 14px 16px; }
.zb-license-detail-label { font-size: 11px; font-weight: 500; color: #888; text-transform: uppercase; letter-spacing: .5px; margin-bottom: 4px; }
.zb-license-detail-value { font-size: 14px; font-weight: 600; color: #1d2327; }
.zb-license-detail-sub { font-size: 11px; color: #999; margin-top: 6px; line-height: 1.4; }
.zb-license-actions { display: flex; align-items: center; gap: 12px; margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
.zb-manage-link { font-size: 13px; color: #2271b1; text-decoration: none; }
.zb-manage-link:hover { text-decoration: underline; }
.zb-cancel-link { font-size: 12px; color: #999; text-decoration: none; margin-left: auto; }
.zb-cancel-link:hover { color: #d63638; text-decoration: underline; }
.zb-plus-banner { background: #f3f4f6; border: 1px solid #e5e7eb; border-radius: 4px; padding: 32px; margin-top: 20px; color: #1d2327; }
.zb-plus-banner h3 { font-size: 18px; font-weight: 600; margin: 0 0 8px; color: #1d2327; }
.zb-plus-banner p { color: #6b7280; font-size: 14px; margin: 0 0 20px; line-height: 1.6; }
.zb-plus-features { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; }
.zb-plus-features span { font-size: 13px; color: #374151; padding-left: 20px; position: relative; }
.zb-plus-features span::before { content: "\2713"; position: absolute; left: 0; color: #2563eb; font-weight: 700; }
.zb-plus-price { display: inline-block; font-size: 32px; font-weight: 700; color: #1d2327; line-height: 1; margin-top: 24px; padding-top: 24px; border-top: 1px solid #e5e7eb; }
.zb-plus-price-unit { font-size: 14px; font-weight: 400; color: #6b7280; margin-left: 4px; }
.zb-plus-cta { display: inline-flex; align-items: center; gap: 6px; margin-top: 20px; background: #1d1d1f; color: #fff; padding: 12px 24px; border-radius: 4px; text-decoration: none; font-size: 14px; font-weight: 600; transition: opacity .2s; }
.zb-plus-cta:hover { opacity: .85; color: #fff; }
.zb-pro-banner { background: linear-gradient(135deg, #1d1d1f 0%, #2d2d30 100%); border-radius: 4px; padding: 32px; margin-top: 20px; color: #fff; }
.zb-pro-banner h3 { font-size: 18px; font-weight: 600; margin: 0 0 8px; color: #fff; }
.zb-pro-banner p { color: #a0a0a5; font-size: 14px; margin: 0 0 20px; line-height: 1.6; }
.zb-pro-features { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; }
.zb-pro-features span { font-size: 13px; color: #d0d0d5; padding-left: 20px; position: relative; }
.zb-pro-features span::before { content: "\2713"; position: absolute; left: 0; color: #4ade80; font-weight: 700; }
.zb-pro-pricing { margin-top: 24px; padding-top: 24px; border-top: 1px solid rgba(255,255,255,.12); display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.zb-pro-price { font-size: 32px; font-weight: 700; color: #fff; line-height: 1; }
.zb-pro-price-unit { font-size: 14px; font-weight: 400; color: #a0a0a5; }
.zb-pro-price-annual { font-size: 13px; color: #a0a0a5; }
.zb-pro-price-note { width: 100%; font-size: 11px; color: #777; margin-top: 4px; }
.zb-banner-step-header { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.zb-banner-step-header h3 { margin: 0 !important; }
.zb-banner-step-num { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; font-size: 14px; font-weight: 700; flex-shrink: 0; }
.zb-plus-banner .zb-banner-step-num { background: #1d1d1f; color: #fff; }
.zb-pro-banner .zb-banner-step-num { background: #fff; color: #1d1d1f; }
.zb-pro-steps { margin-top: 24px; padding-top: 24px; border-top: 1px solid rgba(255,255,255,.12); display: flex; flex-direction: column; gap: 12px; }
.zb-pro-step { font-size: 13px; color: #a0a0a5; line-height: 1.5; }
.zb-pro-step strong { display: block; color: #d0d0d5; font-size: 14px; margin-bottom: 2px; }
.zb-steps { display: flex; flex-direction: column; gap: 0; margin-top: 20px; }
.zb-step { position: relative; padding: 24px 28px 24px 72px; background: #f9fafb; border: 1px solid #eee; border-radius: 4px; margin-bottom: 12px; }
.zb-step:last-child { margin-bottom: 0; }
.zb-step-num { position: absolute; left: 20px; top: 24px; display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: #1d1d1f; color: #fff; font-size: 14px; font-weight: 700; }
.zb-step-title { font-size: 15px; font-weight: 700; color: #1d2327; margin-bottom: 4px; }
.zb-step-desc { font-size: 13px; color: #666; line-height: 1.5; }
.zb-step-action { margin-top: 14px; }
.zb-step-action.is-centered { margin-left: -44px; text-align: center; }
.zb-pro-cta { display: inline-block; background: #1d1d1f; color: #fff; font-size: 15px; font-weight: 600; padding: 16px 36px; border-radius: 4px; text-decoration: none; transition: opacity .15s; }
.zb-pro-cta:hover { opacity: .85; color: #fff; }
.zb-activate-row { display: flex; gap: 8px; }
.zb-activate-row input[type="text"] { flex: 1; max-width: 380px; padding: 8px 12px; border: 1px solid #d0d0d0; border-radius: 4px; font-size: 14px; background: #fff; }
.zb-activate-row input[type="text"]:focus { border-color: #1d1d1f; box-shadow: 0 0 0 1px #1d1d1f; outline: none; }
.zb-btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 20px; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; transition: opacity .15s; }
.zb-btn:disabled { opacity: .5; cursor: not-allowed; }
.zb-btn-primary { background: #1d1d1f; color: #fff; }
.zb-btn-primary:hover { opacity: .85; }
.zb-btn-secondary { background: transparent; color: #d63638; border: 1px solid #d63638; padding: 6px 16px; font-size: 13px; }
.zb-btn-secondary:hover { background: #fef2f2; }
.zb-pro-info { display: flex; gap: 24px; margin-top: 12px; font-size: 13px; color: #666; }
.zb-pro-info code { background: #f5f5f5; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
.zb-divider { border: 0; border-top: 1px solid #eee; margin: 24px 0; }
.zb-api-grid { display: grid; gap: 16px; }
.zb-api-field label { display: block; font-size: 13px; font-weight: 500; color: #1d2327; margin-bottom: 6px; }
.zb-api-field input[type="text"] { width: 100%; max-width: 480px; padding: 8px 12px; border: 1px solid #d0d0d0; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
.zb-api-field input[type="text"]:focus { border-color: #1d1d1f; box-shadow: 0 0 0 1px #1d1d1f; outline: none; }
.zb-api-field .description { font-size: 12px; color: #999; margin-top: 4px; }
#zenblocks-license-message { margin-top: 8px; font-size: 13px; }
';

        wp_register_style( 'zenblocks-admin', false, array(), ZENBLOCKS_VERSION );
        wp_enqueue_style( 'zenblocks-admin' );
        wp_add_inline_style( 'zenblocks-admin', $css );
    }

    public function admin_page() {
    $license_data = $this->get_license_data();
    $pricing = $this->get_pro_pricing();
    ?>
    <div class="zb-settings">
      <div class="zb-header">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="87.65 56.27 124.51 179.33">
          <path
            d="M222.12,68.81c.63-1.06-.13-2.41-1.37-2.41H88.85c-.66,0-1.2.54-1.2,1.2v35.18c0,.66.54,1.2,1.2,1.2h63.24c1.24,0,2,1.35,1.37,2.41l-75.58,123.8c-.63,1.06.13,2.41,1.37,2.41h131.91c.66,0,1.2-.54,1.2-1.2v-35.18c0-.66-.54-1.2-1.2-1.2h-65.4c-1.24,0-2-1.35-1.37-2.41l77.74-123.8Z" />
        </svg>
        <h1>ZenBlocks <span class="zb-ver">v<?php echo esc_html( ZENBLOCKS_VERSION ); ?></span></h1>
      </div>

      <div class="zb-tabs">
        <button class="zb-tab is-active" data-tab="pro">ZenBlocks Pro</button>
        <button class="zb-tab" data-tab="api"><?php esc_html_e('API Settings', 'zenblocks'); ?></button>
      </div>

      <!-- Tab: Pro -->
      <div class="zb-tab-panel is-active" data-tab="pro">
        <div class="zb-card">
          <?php if ( $license_data['isPro'] ) : ?>
          <div class="zb-card-header">
            <h2><?php esc_html_e('ZenBlocks Pro', 'zenblocks'); ?></h2>
            <span class="zb-license-status is-pro"><?php esc_html_e('Pro Active', 'zenblocks'); ?></span>
          </div>

          <p style="margin-top: 16px; font-size: 13px; color: #666;">
            <?php esc_html_e( 'ZenBlocks Pro is installed and active. All Pro features are enabled.', 'zenblocks' ); ?>
          </p>
          <?php if ( is_plugin_active( 'zenblocks-plus/zenblocks-plus.php' ) ) : ?>
          <p style="font-size: 13px; color: #666;">
            <?php esc_html_e( 'License management is available in the ZenBlocks Plus settings above.', 'zenblocks' ); ?>
          </p>
          <?php endif; ?>

          <div class="zb-license-actions">
            <a href="https://zenblocks.lemonsqueezy.com/billing" target="_blank" rel="noopener noreferrer"
              class="zb-manage-link">
              <?php esc_html_e( 'Manage Subscription', 'zenblocks' ); ?> &rarr;
            </a>
          </div>

          <?php else :
          $is_plus_active = is_plugin_active( 'zenblocks-plus/zenblocks-plus.php' );
          ?>
          <div class="zb-card-header">
            <h2><?php esc_html_e('License', 'zenblocks'); ?></h2>
            <?php if ( $is_plus_active ) : ?>
            <span class="zb-license-status is-plus">PLUS</span>
            <?php else : ?>
            <span class="zb-license-status is-free"><?php esc_html_e('Free Version', 'zenblocks'); ?></span>
            <?php endif; ?>
          </div>

          <?php
          $lang_map = array(
            'ja' => 'ja', 'de_DE' => 'de', 'de_DE_formal' => 'de',
            'es_ES' => 'es', 'fr_FR' => 'fr', 'id_ID' => 'id',
            'it_IT' => 'it', 'ko_KR' => 'ko', 'nl_NL' => 'nl',
            'pt_BR' => 'pt-br', 'zh_TW' => 'zh-tw',
          );
          $locale      = get_locale();
          $lang_segment = isset( $lang_map[ $locale ] ) ? $lang_map[ $locale ] : 'en';
          $plus_dl_url  = 'https://zenblockswp.com/' . $lang_segment . '/downloads/';
          ?>

          <?php if ( ! $is_plus_active ) : ?>
          <div class="zb-plus-banner">
            <div class="zb-banner-step-header">
              <span class="zb-banner-step-num">1</span>
              <h3><?php esc_html_e('ZenBlocks Plus', 'zenblocks'); ?></h3>
            </div>
            <p><?php esc_html_e('Unlock additional features with the free ZenBlocks Plus addon.', 'zenblocks'); ?></p>
            <div class="zb-plus-features">
              <span><?php esc_html_e('Custom Breakpoints', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Hover Text', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Extended Icon Libraries', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Custom Patterns', 'zenblocks'); ?></span>
              <span><?php esc_html_e('License Management', 'zenblocks'); ?></span>
              <span><?php esc_html_e('One-Click Pro Install', 'zenblocks'); ?></span>
            </div>
            <div class="zb-plus-price">
              <?php esc_html_e('Free', 'zenblocks'); ?>
              <span class="zb-plus-price-unit">/ ¥0</span>
            </div>
            <br>
            <a href="<?php echo esc_url( $plus_dl_url ); ?>"
              target="_blank" rel="noopener noreferrer" class="zb-plus-cta">
              <?php esc_html_e('Download ZenBlocks Plus', 'zenblocks'); ?> &rarr;
            </a>
          </div>
          <?php endif; ?>

          <div class="zb-pro-banner">
            <div class="zb-banner-step-header">
              <span class="zb-banner-step-num"><?php echo $is_plus_active ? '1' : '2'; ?></span>
              <h3><?php esc_html_e('Upgrade to Pro', 'zenblocks'); ?></h3>
            </div>
            <p><?php esc_html_e('Unlock the full potential of ZenBlocks with advanced features.', 'zenblocks'); ?></p>
            <div class="zb-pro-features">
              <span><?php esc_html_e('Button Animations', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Text Animations', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Icon Animations', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Frame Animations', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Thumbnails', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Band / Ribbon', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Icon Frames', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Background Video', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Micro Text', 'zenblocks'); ?></span>
              <span><?php esc_html_e('Sub Text', 'zenblocks'); ?></span>
            </div>
            <?php if ( $pricing && ! empty( $pricing['monthly']['formatted'] ) ) : ?>
            <div class="zb-pro-pricing">
              <div>
                <span class="zb-pro-price"><?php echo esc_html( $pricing['monthly']['formatted'] ); ?></span>
                <span class="zb-pro-price-unit">/ <?php esc_html_e('month', 'zenblocks'); ?></span>
              </div>
              <?php if ( ! empty( $pricing['annual']['formatted'] ) ) : ?>
              <div class="zb-pro-price-annual">
                <?php
                /* translators: %s: annual price amount */
                printf( esc_html__( 'Annual: %s / year', 'zenblocks' ), esc_html( $pricing['annual']['formatted'] ) ); ?>
              </div>
              <?php endif; ?>
              <div class="zb-pro-price-note">
                <?php esc_html_e('* Approximate price. The actual amount will be shown at checkout.', 'zenblocks'); ?>
              </div>
            </div>
            <?php endif; ?>
            <div class="zb-pro-steps">
              <div class="zb-pro-step">
                <strong><?php esc_html_e('Purchase Pro License', 'zenblocks'); ?></strong>
                <span><?php esc_html_e('Purchase a Pro license from the ZenBlocks Plus settings page. A license key will be sent to your email.', 'zenblocks'); ?></span>
              </div>
              <div class="zb-pro-step">
                <strong><?php esc_html_e('Activate & Enjoy', 'zenblocks'); ?></strong>
                <span><?php esc_html_e('Enter your license key in ZenBlocks Plus to activate Pro features. The Pro plugin will be installed automatically.', 'zenblocks'); ?></span>
              </div>
            </div>
          </div>
          <?php endif; ?>
        </div>

      </div><!-- /.zb-tab-panel[pro] -->

      <!-- Tab: API -->
      <div class="zb-tab-panel" data-tab="api">
        <div class="zb-card">
          <h2><?php esc_html_e('External Media API', 'zenblocks'); ?></h2>
          <form method="post" action="options.php">
            <?php settings_fields('zenblocks_settings'); ?>
            <div class="zb-api-grid">
              <div class="zb-api-field">
                <label for="zenblocks_unsplash_access_key"><?php esc_html_e('Unsplash Access Key', 'zenblocks'); ?></label>
                <input type="text" id="zenblocks_unsplash_access_key" name="zenblocks_unsplash_access_key"
                  value="<?php echo esc_attr(get_option('zenblocks_unsplash_access_key', '')); ?>" autocomplete="off" />
                <p class="description">
                  <?php esc_html_e('Start with Demo key, then replace with Production-approved key.', 'zenblocks'); ?>
                </p>
              </div>
              <div class="zb-api-field">
                <label for="zenblocks_pexels_api_key"><?php esc_html_e('Pexels API Key', 'zenblocks'); ?></label>
                <input type="text" id="zenblocks_pexels_api_key" name="zenblocks_pexels_api_key"
                  value="<?php echo esc_attr(get_option('zenblocks_pexels_api_key', '')); ?>" autocomplete="off" />
                <p class="description"><?php esc_html_e('For image and video search.', 'zenblocks'); ?></p>
              </div>
              <div class="zb-api-field">
                <label for="zenblocks_pixabay_api_key"><?php esc_html_e('Pixabay API Key', 'zenblocks'); ?></label>
                <input type="text" id="zenblocks_pixabay_api_key" name="zenblocks_pixabay_api_key"
                  value="<?php echo esc_attr(get_option('zenblocks_pixabay_api_key', '')); ?>" autocomplete="off" />
                <p class="description"><?php esc_html_e('For image and video search.', 'zenblocks'); ?></p>
              </div>
            </div>
            <div style="margin-top: 20px;">
              <button type="submit" class="zb-btn zb-btn-primary"><?php esc_html_e('Save API Settings', 'zenblocks'); ?></button>
            </div>
          </form>
        </div>
      </div><!-- /.zb-tab-panel[api] -->
    </div>

    <?php
    $this->admin_page_inline_script();
    }

    /**
     * Output admin page inline script via wp_add_inline_script.
     */
    private function admin_page_inline_script() {
        $js = "(function() {
      document.querySelectorAll('.zb-tab').forEach(function(tab) {
        tab.addEventListener('click', function() {
          var target = this.getAttribute('data-tab');
          document.querySelectorAll('.zb-tab').forEach(function(t) { t.classList.remove('is-active'); });
          document.querySelectorAll('.zb-tab-panel').forEach(function(p) { p.classList.remove('is-active'); });
          this.classList.add('is-active');
          document.querySelector('.zb-tab-panel[data-tab=\"' + target + '\"]').classList.add('is-active');
        });
      });
    })();";

        wp_register_script( 'zenblocks-admin-inline', false, array(), ZENBLOCKS_VERSION, true );
        wp_enqueue_script( 'zenblocks-admin-inline' );
        wp_add_inline_script( 'zenblocks-admin-inline', $js );
    }

    /**
     * Get Pro pricing via zenblockswp.com API (24-hour cache).
     */
    public function get_pro_pricing() {
        $locale    = determine_locale();
        $cache_key = 'zenblocks_pro_pricing_' . $locale;
        $cached    = get_transient( $cache_key );
        if ( false !== $cached ) {
            return $cached;
        }

        $url = add_query_arg( 'locale', $locale, 'https://zenblockswp.com/wp-json/zenblocks/v1/pricing' );
        $response = wp_remote_get( $url, array( 'timeout' => 5 ) );

        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
            set_transient( $cache_key, null, HOUR_IN_SECONDS );
            return null;
        }

        $data = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( empty( $data ) || ! isset( $data['monthly'] ) ) {
            set_transient( $cache_key, null, HOUR_IN_SECONDS );
            return null;
        }

        set_transient( $cache_key, $data, DAY_IN_SECONDS );
        return $data;
    }

    public function get_license_data() {
        $is_pro  = is_plugin_active( 'zenblocks-pro/zenblocks-pro.php' );
        $is_plus = is_plugin_active( 'zenblocks-plus/zenblocks-plus.php' );

        $data = array(
            'isPro'  => $is_pro,
            'isPlus' => $is_plus,
        );

        /**
         * Filter license data.
         *
         * zenblocks-pro can extend this with status, expiresAt, etc.
         *
         * @param array $data License data array.
         */
        return apply_filters( 'zenblocks_license_data', $data );
    }


    public function register_breakpoint_settings() {
        register_setting('zenblocks_settings', 'zenblocks_breakpoints', array(
            'type' => 'array',
            'default' => $this->default_breakpoints,
            'sanitize_callback' => array($this, 'sanitize_breakpoints'),
        ));
    }

    public function sanitize_breakpoints($input) {
        $sanitized = array(
            'enabled' => array(),
            'values' => array(),
        );

        $valid_keys = array('lg', 'pc', 'md', 'tb', 'sp', 'mn');

        if (isset($input['enabled']) && is_array($input['enabled'])) {
            foreach ($input['enabled'] as $key) {
                if (in_array($key, $valid_keys, true)) {
                    $sanitized['enabled'][] = sanitize_key($key);
                }
            }
        }
        if (empty($sanitized['enabled'])) {
            $sanitized['enabled'] = $this->default_breakpoints['enabled'];
        }

        if (isset($input['values']) && is_array($input['values'])) {
            foreach ($valid_keys as $key) {
                $sanitized['values'][$key] = isset($input['values'][$key])
                    ? absint($input['values'][$key])
                    : $this->default_breakpoints['values'][$key];
            }
        } else {
            $sanitized['values'] = $this->default_breakpoints['values'];
        }

        return $sanitized;
    }

    public function get_breakpoints() {
        $breakpoints = get_option('zenblocks_breakpoints', $this->default_breakpoints);
        return wp_parse_args($breakpoints, $this->default_breakpoints);
    }

    public function register_rest_routes() {
        register_rest_route('zenblocks/v1', '/breakpoints', array(
            array(
                'methods' => 'GET',
                'callback' => array($this, 'rest_get_breakpoints'),
                'permission_callback' => function() {
                    return current_user_can('edit_posts');
                },
            ),
            array(
                'methods' => 'POST',
                'callback' => array($this, 'rest_save_breakpoints'),
                'permission_callback' => function() {
                    return current_user_can('manage_options');
                },
            ),
        ));

        register_rest_route('zenblocks/v1', '/media/openverse/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_openverse_media'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/unsplash/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_unsplash_media'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/unsplash/track-download', array(
            'methods' => 'POST',
            'callback' => array($this, 'rest_track_unsplash_download'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/pexels/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_pexels_media'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/pexels/videos/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_pexels_videos'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/pixabay/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_pixabay_media'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/pixabay/videos/search', array(
            'methods' => 'GET',
            'callback' => array($this, 'rest_search_pixabay_videos'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));

        register_rest_route('zenblocks/v1', '/media/import', array(
            'methods' => 'POST',
            'callback' => array($this, 'rest_import_external_media'),
            'permission_callback' => function() {
                return current_user_can('upload_files');
            },
        ));
    }

    public function rest_get_breakpoints($request) {
        return rest_ensure_response(array(
            'breakpoints' => $this->get_breakpoints(),
        ));
    }

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

        if (!isset($params['breakpoints'])) {
            return new WP_Error('missing_data', __('Breakpoints data is required', 'zenblocks'), array('status' => 400));
        }

        $breakpoints = $this->sanitize_breakpoints($params['breakpoints']);
        update_option('zenblocks_breakpoints', $breakpoints);

        // Regenerate responsive CSS file
        $this->regenerate_responsive_css($breakpoints);

        return rest_ensure_response(array(
            'success' => true,
            'breakpoints' => $breakpoints,
        ));
    }

    /**
     * Search CC0 images from Openverse.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_openverse_media($request) {
        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_ov_' . md5($query . '|' . $page . '|' . $per_page);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'q' => $query,
                'license' => 'cc0',
                'page' => $page,
                'page_size' => $per_page,
            ),
            'https://api.openverse.org/v1/images/'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_openverse_request_failed',
                __('Failed to connect to Openverse API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_openverse_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Openverse API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_openverse_invalid_response',
                __('Invalid response from Openverse API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $results = isset($data['results']) && is_array($data['results']) ? $data['results'] : array();
        $items = array();

        foreach ($results as $item) {
            if (!is_array($item)) {
                continue;
            }

            $thumbnail_url = isset($item['thumbnail']) ? esc_url_raw((string) $item['thumbnail']) : '';
            $full_url = isset($item['url']) ? esc_url_raw((string) $item['url']) : '';

            if ($thumbnail_url === '' || $full_url === '') {
                continue;
            }

            $items[] = array(
                'id' => isset($item['id']) ? sanitize_text_field((string) $item['id']) : md5($full_url),
                'thumbnailUrl' => $thumbnail_url,
                'fullUrl' => $full_url,
                'title' => isset($item['title']) ? sanitize_text_field((string) $item['title']) : '',
                'creator' => isset($item['creator']) ? sanitize_text_field((string) $item['creator']) : '',
                'license' => isset($item['license']) ? sanitize_key((string) $item['license']) : 'cc0',
                'source' => 'openverse',
                'sourceUrl' => isset($item['foreign_landing_url']) ? esc_url_raw((string) $item['foreign_landing_url']) : '',
            );
        }

        $page_count = isset($data['page_count']) ? absint($data['page_count']) : 0;
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => $page_count > 0 ? ($page < $page_count) : (count($items) === $per_page),
            ),
        );

        set_transient($cache_key, $response_data, 10 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Search images from Unsplash API.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_unsplash_media($request) {
        $access_key = trim((string) get_option('zenblocks_unsplash_access_key', ''));
        if ($access_key === '') {
            return new WP_Error(
                'zenblocks_unsplash_key_missing',
                __('Unsplash access key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_unsplash_' . md5($query . '|' . $page . '|' . $per_page . '|' . $access_key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'query' => $query,
                'page' => $page,
                'per_page' => $per_page,
                'client_id' => $access_key,
            ),
            'https://api.unsplash.com/search/photos'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                    'Accept-Version' => 'v1',
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_unsplash_request_failed',
                __('Failed to connect to Unsplash API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_unsplash_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Unsplash API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_unsplash_invalid_response',
                __('Invalid response from Unsplash API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $results = isset($data['results']) && is_array($data['results']) ? $data['results'] : array();
        $items = array();

        foreach ($results as $item) {
            if (!is_array($item)) {
                continue;
            }

            $id = isset($item['id']) ? sanitize_text_field((string) $item['id']) : '';
            $thumbnail_url = isset($item['urls']['small']) ? esc_url_raw((string) $item['urls']['small']) : '';
            $full_url = isset($item['urls']['regular']) ? esc_url_raw((string) $item['urls']['regular']) : '';
            $download_location = isset($item['links']['download_location']) ? esc_url_raw((string) $item['links']['download_location']) : '';
            $photo_url = isset($item['links']['html']) ? esc_url_raw((string) $item['links']['html']) : '';
            $creator_name = isset($item['user']['name']) ? sanitize_text_field((string) $item['user']['name']) : '';
            $creator_page = isset($item['user']['links']['html']) ? esc_url_raw((string) $item['user']['links']['html']) : '';

            if ($id === '' || $thumbnail_url === '' || $full_url === '') {
                continue;
            }

            $utm_params = array(
                'utm_source' => 'zenblocks',
                'utm_medium' => 'referral',
            );
            $creator_url = $creator_page !== '' ? add_query_arg($utm_params, $creator_page) : '';
            $unsplash_url = add_query_arg($utm_params, 'https://unsplash.com/');
            $attribution_text = $creator_name !== ''
                ? sprintf(
                    /* translators: %s: photographer name. */
                    __('Photo by %s on Unsplash', 'zenblocks'),
                    $creator_name
                )
                : __('Photo on Unsplash', 'zenblocks');

            $items[] = array(
                'id' => $id,
                'thumbnailUrl' => $thumbnail_url,
                'fullUrl' => $full_url,
                'title' => isset($item['description']) ? sanitize_text_field((string) $item['description']) : '',
                'creator' => $creator_name,
                'license' => 'unsplash',
                'source' => 'unsplash',
                'sourceUrl' => $photo_url,
                'downloadLocation' => $download_location,
                'attributionText' => $attribution_text,
                'attributionUrl' => $creator_url,
                'unsplashUrl' => $unsplash_url,
            );
        }

        $total_pages = isset($data['total_pages']) ? absint($data['total_pages']) : 0;
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => $total_pages > 0 ? ($page < $total_pages) : (count($items) === $per_page),
            ),
        );

        set_transient($cache_key, $response_data, 5 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Notify Unsplash download endpoint when image is selected.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_track_unsplash_download($request) {
        $access_key = trim((string) get_option('zenblocks_unsplash_access_key', ''));
        if ($access_key === '') {
            return new WP_Error(
                'zenblocks_unsplash_key_missing',
                __('Unsplash access key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $params = $request->get_json_params();
        $download_location = isset($params['download_location']) ? esc_url_raw((string) $params['download_location']) : '';

        if ($download_location === '' || strpos($download_location, 'https://api.unsplash.com/') !== 0) {
            return new WP_Error(
                'zenblocks_invalid_download_location',
                __('Invalid Unsplash download location.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $track_url = add_query_arg(
            array(
                'client_id' => $access_key,
            ),
            $download_location
        );

        $response = wp_remote_get(
            $track_url,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                    'Accept-Version' => 'v1',
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_unsplash_tracking_failed',
                __('Failed to notify Unsplash download endpoint.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_unsplash_tracking_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Unsplash tracking returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        return rest_ensure_response(array('success' => true));
    }

    /**
     * Search images from Pexels API.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_pexels_media($request) {
        $api_key = trim((string) get_option('zenblocks_pexels_api_key', ''));
        if ($api_key === '') {
            return new WP_Error(
                'zenblocks_pexels_key_missing',
                __('Pexels API key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_pexels_' . md5($query . '|' . $page . '|' . $per_page . '|' . $api_key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'query' => $query,
                'page' => $page,
                'per_page' => $per_page,
            ),
            'https://api.pexels.com/v1/search'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                    'Authorization' => $api_key,
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_pexels_request_failed',
                __('Failed to connect to Pexels API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_pexels_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Pexels API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_pexels_invalid_response',
                __('Invalid response from Pexels API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $photos = isset($data['photos']) && is_array($data['photos']) ? $data['photos'] : array();
        $items = array();

        foreach ($photos as $photo) {
            if (!is_array($photo)) {
                continue;
            }

            $id = isset($photo['id']) ? sanitize_text_field((string) $photo['id']) : '';
            $thumbnail_url = isset($photo['src']['medium']) ? esc_url_raw((string) $photo['src']['medium']) : '';
            $full_url = isset($photo['src']['large2x']) ? esc_url_raw((string) $photo['src']['large2x']) : '';
            $source_url = isset($photo['url']) ? esc_url_raw((string) $photo['url']) : '';
            $creator = isset($photo['photographer']) ? sanitize_text_field((string) $photo['photographer']) : '';

            if ($id === '' || $thumbnail_url === '' || $full_url === '') {
                continue;
            }

            $items[] = array(
                'id' => $id,
                'thumbnailUrl' => $thumbnail_url,
                'fullUrl' => $full_url,
                'title' => '',
                'creator' => $creator,
                'license' => 'pexels',
                'source' => 'pexels',
                'sourceUrl' => $source_url,
            );
        }

        $next_page = isset($data['next_page']) ? (string) $data['next_page'] : '';
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => $next_page !== '',
            ),
        );

        set_transient($cache_key, $response_data, 5 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Search images from Pixabay API.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_pixabay_media($request) {
        $api_key = trim((string) get_option('zenblocks_pixabay_api_key', ''));
        if ($api_key === '') {
            return new WP_Error(
                'zenblocks_pixabay_key_missing',
                __('Pixabay API key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_pixabay_' . md5($query . '|' . $page . '|' . $per_page . '|' . $api_key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'key' => $api_key,
                'q' => $query,
                'image_type' => 'photo',
                'safesearch' => 'true',
                'page' => $page,
                'per_page' => $per_page,
            ),
            'https://pixabay.com/api/'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_pixabay_request_failed',
                __('Failed to connect to Pixabay API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_pixabay_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Pixabay API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_pixabay_invalid_response',
                __('Invalid response from Pixabay API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $hits = isset($data['hits']) && is_array($data['hits']) ? $data['hits'] : array();
        $items = array();

        foreach ($hits as $hit) {
            if (!is_array($hit)) {
                continue;
            }

            $id = isset($hit['id']) ? sanitize_text_field((string) $hit['id']) : '';
            $thumbnail_url = isset($hit['webformatURL']) ? esc_url_raw((string) $hit['webformatURL']) : '';
            $full_url = isset($hit['largeImageURL']) ? esc_url_raw((string) $hit['largeImageURL']) : '';
            $source_url = isset($hit['pageURL']) ? esc_url_raw((string) $hit['pageURL']) : '';
            $creator = isset($hit['user']) ? sanitize_text_field((string) $hit['user']) : '';

            if ($id === '' || $thumbnail_url === '' || $full_url === '') {
                continue;
            }

            $items[] = array(
                'id' => $id,
                'thumbnailUrl' => $thumbnail_url,
                'fullUrl' => $full_url,
                'title' => '',
                'creator' => $creator,
                'license' => 'pixabay',
                'source' => 'pixabay',
                'sourceUrl' => $source_url,
            );
        }

        $total_hits = isset($data['totalHits']) ? absint($data['totalHits']) : 0;
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => ($page * $per_page) < $total_hits,
            ),
        );

        set_transient($cache_key, $response_data, 5 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Search videos from Pexels API.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_pexels_videos($request) {
        $api_key = trim((string) get_option('zenblocks_pexels_api_key', ''));
        if ($api_key === '') {
            return new WP_Error(
                'zenblocks_pexels_key_missing',
                __('Pexels API key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_pexels_videos_' . md5($query . '|' . $page . '|' . $per_page . '|' . $api_key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'query' => $query,
                'page' => $page,
                'per_page' => $per_page,
            ),
            'https://api.pexels.com/videos/search'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                    'Authorization' => $api_key,
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_pexels_request_failed',
                __('Failed to connect to Pexels API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_pexels_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Pexels API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_pexels_invalid_response',
                __('Invalid response from Pexels API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $videos = isset($data['videos']) && is_array($data['videos']) ? $data['videos'] : array();
        $items = array();

        foreach ($videos as $video) {
            if (!is_array($video)) {
                continue;
            }

            $id = isset($video['id']) ? sanitize_text_field((string) $video['id']) : '';
            $thumbnail_url = isset($video['image']) ? esc_url_raw((string) $video['image']) : '';
            $source_url = isset($video['url']) ? esc_url_raw((string) $video['url']) : '';
            $creator = isset($video['user']['name']) ? sanitize_text_field((string) $video['user']['name']) : '';

            $video_url = '';
            $files = isset($video['video_files']) && is_array($video['video_files']) ? $video['video_files'] : array();
            foreach ($files as $file) {
                if (!is_array($file)) {
                    continue;
                }
                $file_type = isset($file['file_type']) ? (string) $file['file_type'] : '';
                $link = isset($file['link']) ? esc_url_raw((string) $file['link']) : '';
                if ($link === '' || stripos($file_type, 'video/mp4') === false) {
                    continue;
                }
                $video_url = $link;
                $quality = isset($file['quality']) ? (string) $file['quality'] : '';
                if ($quality === 'sd') {
                    break;
                }
            }

            if ($id === '' || $video_url === '') {
                continue;
            }

            $items[] = array(
                'id' => $id,
                'thumbnailUrl' => $thumbnail_url,
                'videoUrl' => $video_url,
                'creator' => $creator,
                'license' => 'pexels',
                'source' => 'pexels',
                'sourceUrl' => $source_url,
            );
        }

        $next_page = isset($data['next_page']) ? (string) $data['next_page'] : '';
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => $next_page !== '',
            ),
        );

        set_transient($cache_key, $response_data, 5 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Search videos from Pixabay API.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_search_pixabay_videos($request) {
        $api_key = trim((string) get_option('zenblocks_pixabay_api_key', ''));
        if ($api_key === '') {
            return new WP_Error(
                'zenblocks_pixabay_key_missing',
                __('Pixabay API key is not configured.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $query = sanitize_text_field((string) $request->get_param('q'));
        $page = max(1, absint($request->get_param('page') ?: 1));
        $per_page = absint($request->get_param('per_page') ?: 20);
        $per_page = max(1, min(20, $per_page));

        if ($query === '') {
            return new WP_Error(
                'zenblocks_missing_query',
                __('Search query is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        $cache_key = 'zenblocks_pixabay_videos_' . md5($query . '|' . $page . '|' . $per_page . '|' . $api_key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return rest_ensure_response($cached);
        }

        $endpoint = add_query_arg(
            array(
                'key' => $api_key,
                'q' => $query,
                'safesearch' => 'true',
                'page' => $page,
                'per_page' => $per_page,
            ),
            'https://pixabay.com/api/videos/'
        );

        $response = wp_remote_get(
            $endpoint,
            array(
                'timeout' => 15,
                'headers' => array(
                    'Accept' => 'application/json',
                ),
            )
        );

        if (is_wp_error($response)) {
            return new WP_Error(
                'zenblocks_pixabay_request_failed',
                __('Failed to connect to Pixabay API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code < 200 || $status_code >= 300) {
            return new WP_Error(
                'zenblocks_pixabay_http_error',
                sprintf(
                    /* translators: %d: HTTP status code. */
                    __('Pixabay API returned an error (%d).', 'zenblocks'),
                    $status_code
                ),
                array('status' => 502)
            );
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return new WP_Error(
                'zenblocks_pixabay_invalid_response',
                __('Invalid response from Pixabay API.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $hits = isset($data['hits']) && is_array($data['hits']) ? $data['hits'] : array();
        $items = array();

        foreach ($hits as $hit) {
            if (!is_array($hit)) {
                continue;
            }

            $id = isset($hit['id']) ? sanitize_text_field((string) $hit['id']) : '';
            $source_url = isset($hit['pageURL']) ? esc_url_raw((string) $hit['pageURL']) : '';
            $creator = isset($hit['user']) ? sanitize_text_field((string) $hit['user']) : '';
            $videos = isset($hit['videos']) && is_array($hit['videos']) ? $hit['videos'] : array();

            $video_url = '';
            foreach (array('medium', 'small', 'large', 'tiny') as $size_key) {
                if (isset($videos[$size_key]['url'])) {
                    $candidate = esc_url_raw((string) $videos[$size_key]['url']);
                    if ($candidate !== '') {
                        $video_url = $candidate;
                        break;
                    }
                }
            }

            $thumbnail_url = '';
            foreach (array('medium', 'small', 'large', 'tiny') as $size_key) {
                if (isset($videos[$size_key]['thumbnail'])) {
                    $candidate_thumbnail = esc_url_raw((string) $videos[$size_key]['thumbnail']);
                    if ($candidate_thumbnail !== '') {
                        $thumbnail_url = $candidate_thumbnail;
                        break;
                    }
                }
            }
            if ($id === '' || $video_url === '') {
                continue;
            }

            $items[] = array(
                'id' => $id,
                'thumbnailUrl' => $thumbnail_url,
                'videoUrl' => $video_url,
                'creator' => $creator,
                'license' => 'pixabay',
                'source' => 'pixabay',
                'sourceUrl' => $source_url,
            );
        }

        $total_hits = isset($data['totalHits']) ? absint($data['totalHits']) : 0;
        $response_data = array(
            'items' => $items,
            'pagination' => array(
                'page' => $page,
                'per_page' => $per_page,
                'has_more' => ($page * $per_page) < $total_hits,
            ),
        );

        set_transient($cache_key, $response_data, 5 * MINUTE_IN_SECONDS);

        return rest_ensure_response($response_data);
    }

    /**
     * Import external media into WordPress media library.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function rest_import_external_media($request) {
        $params = $request->get_json_params();
        $image_url = isset($params['image_url']) ? esc_url_raw((string) $params['image_url']) : '';
        $title = isset($params['title']) ? sanitize_text_field((string) $params['title']) : '';
        $creator = isset($params['creator']) ? sanitize_text_field((string) $params['creator']) : '';
        $source = isset($params['source']) ? sanitize_key((string) $params['source']) : '';
        $source_url = isset($params['source_url']) ? esc_url_raw((string) $params['source_url']) : '';
        $license = isset($params['license']) ? sanitize_key((string) $params['license']) : '';

        if ($image_url === '' || !wp_http_validate_url($image_url)) {
            return new WP_Error(
                'zenblocks_invalid_image_url',
                __('A valid image URL is required.', 'zenblocks'),
                array('status' => 400)
            );
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        $tmp_file = download_url($image_url, 20);
        if (is_wp_error($tmp_file)) {
            return new WP_Error(
                'zenblocks_download_failed',
                __('Failed to download the image.', 'zenblocks'),
                array('status' => 502)
            );
        }

        $path = wp_parse_url($image_url, PHP_URL_PATH);
        $filename = $path ? wp_basename(rawurldecode((string) $path)) : '';
        if ($filename !== '') {
            // Some providers (e.g. dummyimage) append text parameters in the path with "&text=".
            $filename_parts = preg_split('/[?#&]/', $filename);
            $filename = isset($filename_parts[0]) ? (string) $filename_parts[0] : '';
        }

        $filename = sanitize_file_name($filename);
        $extension = strtolower((string) pathinfo($filename, PATHINFO_EXTENSION));
        $valid_extensions = array('jpg', 'jpeg', 'png', 'gif', 'webp', 'avif');

        if ($filename === '' || $extension === '' || !in_array($extension, $valid_extensions, true)) {
            $mime = wp_get_image_mime($tmp_file);
            $mime_to_extension = array(
                'image/jpeg' => 'jpg',
                'image/png'  => 'png',
                'image/gif'  => 'gif',
                'image/webp' => 'webp',
                'image/avif' => 'avif',
            );
            $detected_extension = isset($mime_to_extension[$mime]) ? $mime_to_extension[$mime] : 'jpg';
            $base_name = sanitize_file_name($title !== '' ? $title : 'external-image');
            if ($base_name === '') {
                $base_name = 'external-image';
            }
            $filename = $base_name . '.' . $detected_extension;
        }

        $file_array = array(
            'name' => $filename,
            'tmp_name' => $tmp_file,
        );

        $attachment_id = media_handle_sideload($file_array, 0, $title !== '' ? $title : null);
        if (is_wp_error($attachment_id)) {
            wp_delete_file($tmp_file);
            return new WP_Error(
                'zenblocks_media_import_failed',
                __('Failed to import image into media library.', 'zenblocks'),
                array('status' => 500)
            );
        }

        if ($creator !== '') {
            update_post_meta($attachment_id, '_zenblocks_media_creator', $creator);
        }
        if ($source !== '') {
            update_post_meta($attachment_id, '_zenblocks_media_source', $source);
        }
        if ($source_url !== '') {
            update_post_meta($attachment_id, '_zenblocks_media_source_url', $source_url);
        }
        if ($license !== '') {
            update_post_meta($attachment_id, '_zenblocks_media_license', $license);
        }

        $attachment_url = wp_get_attachment_url($attachment_id);

        return rest_ensure_response(array(
            'id' => $attachment_id,
            'url' => $attachment_url ? esc_url_raw($attachment_url) : '',
        ));
    }

    private function get_breakpoint_css() {
        $bp = $this->get_breakpoints();
        $values = $bp['values'];
        $enabled = $bp['enabled'];
        $block_selectors = array(
            '.wp-block-zenblocks-button-basic',
            '.wp-block-zenblocks-text-basic',
        );

        $css  = ":root {\n";
        $css .= "  --zenblocks-bp-lg: " . absint($values['lg']) . "px;\n";
        $css .= "  --zenblocks-bp-md: " . absint($values['md']) . "px;\n";
        $css .= "  --zenblocks-bp-tb: " . absint($values['tb']) . "px;\n";
        $css .= "  --zenblocks-bp-sp: " . absint($values['sp']) . "px;\n";
        $css .= "  --zenblocks-bp-mn: " . absint($values['mn']) . "px;\n";
        $css .= "}\n\n";

        foreach ($enabled as $device) {
            if ($device === 'pc') {
                $pc_min = in_array('md', $enabled, true) ? (absint($values['md']) + 1) : (absint($values['tb']) + 1);
                $css .= "/* ZenBlocks - Display Settings (PC) */\n";
                $css .= "@media (min-width: {$pc_min}px) {\n";
                $css .= "  " . implode(".zenblocks-hidden-pc, ", $block_selectors) . ".zenblocks-hidden-pc {\n";
                $css .= "    display: none !important;\n";
                $css .= "  }\n";
                $css .= "}\n\n";
                continue;
            }

            if ($device === 'md') {
                $md_max = absint($values['md']);
                $tb_min = absint($values['tb']) + 1;
                $css .= "/* ZenBlocks - Display Settings (MD) */\n";
                $css .= "@media (max-width: {$md_max}px) and (min-width: {$tb_min}px) {\n";
                $css .= "  " . implode(".zenblocks-hidden-md, ", $block_selectors) . ".zenblocks-hidden-md {\n";
                $css .= "    display: none !important;\n";
                $css .= "  }\n";
                $css .= "}\n\n";
                continue;
            }

            if ($device === 'tb') {
                $tb_max = absint($values['tb']);
                $sp_min = absint($values['sp']) + 1;
                $css .= "/* ZenBlocks - Display Settings (TB) */\n";
                $css .= "@media (max-width: {$tb_max}px) and (min-width: {$sp_min}px) {\n";
                $css .= "  " . implode(".zenblocks-hidden-tb, ", $block_selectors) . ".zenblocks-hidden-tb {\n";
                $css .= "    display: none !important;\n";
                $css .= "  }\n";
                $css .= "}\n\n";
                continue;
            }

            if ($device === 'sp') {
                $sp_max = absint($values['sp']);
                $css .= "/* ZenBlocks - Display Settings (SP) */\n";
                $css .= "@media (max-width: {$sp_max}px) {\n";
                $css .= "  " . implode(".zenblocks-hidden-sp, ", $block_selectors) . ".zenblocks-hidden-sp {\n";
                $css .= "    display: none !important;\n";
                $css .= "  }\n";
                $css .= "}\n\n";
            }
        }

        return $css;
    }

    public function enqueue_breakpoint_css() {
        wp_register_style('zenblocks-breakpoint-vars', false, array(), ZENBLOCKS_VERSION);
        wp_enqueue_style('zenblocks-breakpoint-vars');
        wp_add_inline_style('zenblocks-breakpoint-vars', wp_strip_all_tags($this->get_breakpoint_css()));
    }

    /**
     * Enqueue PC-only base CSS (editor + frontend).
     * Provides base styles for preset previews and block rendering without media queries.
     */
    public function enqueue_responsive_base_css() {
        $generator = new ZenBlocks_Responsive_CSS_Generator();
        $generator->init($this->get_breakpoints());

        // Generate CSS files if base doesn't exist
        if (!$generator->base_css_file_exists()) {
            $generator->generate_css_file();
        }

        if (!$generator->base_css_file_exists()) {
            return;
        }

        $css_path = $generator->get_base_css_path();
        $css_version = file_exists($css_path) ? filemtime($css_path) : ZENBLOCKS_VERSION;

        wp_enqueue_style(
            'zenblocks-responsive-base',
            $generator->get_base_css_url(),
            array(),
            $css_version
        );
    }

    /**
     * Enqueue full responsive CSS with media queries (frontend only).
     */
    public function enqueue_responsive_css() {
        $generator = new ZenBlocks_Responsive_CSS_Generator();
        $generator->init($this->get_breakpoints());

        // Generate CSS file if it doesn't exist
        if (!$generator->css_file_exists()) {
            $generator->generate_css_file();
        }

        if (!$generator->css_file_exists()) {
            return;
        }

        $css_path = $generator->get_css_path();
        $css_version = file_exists($css_path) ? filemtime($css_path) : ZENBLOCKS_VERSION;

        wp_enqueue_style(
            'zenblocks-responsive',
            $generator->get_css_url(),
            array('zenblocks-responsive-base'),
            $css_version
        );
    }

    /**
     * Regenerate responsive CSS file
     */
    public function regenerate_responsive_css($breakpoints = null) {
        $generator = new ZenBlocks_Responsive_CSS_Generator();
        $generator->init($breakpoints ?: $this->get_breakpoints());
        return $generator->regenerate();
    }

    /**
     * Generate block-specific CSS on post save
     */
    public function generate_block_css($post_id, $post) {
        // Skip autosave and revisions
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        if (wp_is_post_revision($post_id)) {
            return;
        }
        if ($post->post_status === 'auto-draft') {
            return;
        }

        $generator = new ZenBlocks_Block_CSS_Generator();
        $generator->init($this->get_breakpoints());
        $generator->generate_post_css($post_id);
    }

    /**
     * Enqueue block-specific CSS on frontend
     */
    public function enqueue_block_css() {
        global $wp_query;

        $generator = new ZenBlocks_Block_CSS_Generator();
        $generator->init($this->get_breakpoints());
        $loaded_posts = array();

        // 単体ページの場合
        if (is_singular()) {
            $post_id = get_the_ID();
            if ($post_id) {
                // CSSファイルが存在しない場合は生成
                if (!$generator->css_file_exists($post_id)) {
                    $generator->generate_post_css($post_id);
                }

                if ($generator->css_file_exists($post_id)) {
                    wp_enqueue_style(
                        'zenblocks-blocks-' . $post_id,
                        $generator->get_css_url($post_id),
                        array('zenblocks-responsive'),
                        filemtime($generator->get_css_path($post_id))
                    );
                    $loaded_posts[] = $post_id;
                }
            }
        }

        // アーカイブ・ホームページなど：ループ内の投稿からCSSを読み込み
        if ($wp_query && !empty($wp_query->posts)) {
            foreach ($wp_query->posts as $post) {
                $post_id = absint($post->ID);

                // 既に読み込み済みの場合はスキップ
                if (in_array($post_id, $loaded_posts)) {
                    continue;
                }

                // CSSファイルが存在しない場合は生成
                if (!$generator->css_file_exists($post_id)) {
                    $generator->generate_post_css($post_id);
                }

                if ($generator->css_file_exists($post_id)) {
                    wp_enqueue_style(
                        'zenblocks-blocks-' . $post_id,
                        $generator->get_css_url($post_id),
                        array('zenblocks-responsive'),
                        filemtime($generator->get_css_path($post_id))
                    );
                    $loaded_posts[] = $post_id;
                }
            }
        }
    }

    /**
     * AJAX handler for clearing CSS cache
     */
    public function ajax_clear_css_cache() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'zenblocks_clear_css_cache')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'zenblocks')), 403);
        }

        // Check permissions
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'zenblocks')), 403);
        }

        $generator = new ZenBlocks_Block_CSS_Generator();
        $result = $generator->clear_all_css_cache();

        if ($result['success']) {
            wp_send_json_success($result);
        } else {
            wp_send_json_error($result);
        }
    }

    /**
     * Clear CSS cache on plugin update
     *
     * @param WP_Upgrader $upgrader
     * @param array $options
     */
    public function on_plugin_update($upgrader, $options) {
        // Check if this is a plugin update
        if ($options['action'] !== 'update' || $options['type'] !== 'plugin') {
            return;
        }

        // Check if ZenBlocks is being updated
        $zenblocks_updated = false;

        if (isset($options['plugins']) && is_array($options['plugins'])) {
            foreach ($options['plugins'] as $plugin) {
                if (strpos($plugin, 'zenblocks') !== false) {
                    $zenblocks_updated = true;
                    break;
                }
            }
        }

        // Also check single plugin update
        if (isset($options['plugin']) && strpos($options['plugin'], 'zenblocks') !== false) {
            $zenblocks_updated = true;
        }

        if ($zenblocks_updated) {
            $generator = new ZenBlocks_Block_CSS_Generator();
            $generator->clear_all_css_cache();

            if (defined('WP_DEBUG') && WP_DEBUG) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging only when WP_DEBUG is enabled
                error_log('ZenBlocks: CSS cache cleared on plugin update');
            }
        }
    }

}

// Initialize the plugin
new ZenBlocks();