<?php
/**
 * ResponsiveControls Extension.
 * 
 * Provides responsive control functionality for Spectra blocks, allowing
 * different styling for mobile, tablet, and desktop devices. This extension
 * handles the generation of responsive CSS and ensures proper fallback
 * between device sizes following WordPress core patterns.
 * 
 * @package Spectra\Extensions
 * @since 3.0.0-beta.1
 */

namespace Spectra\Extensions;

use Spectra\Extensions\ResponsiveControls\ResponsiveAttributeCSS;
use Spectra\Helpers\Core;
use Spectra\Traits\Singleton;
use WP_HTML_Tag_Processor;

/**
 * Class to manage responsive controls for Spectra blocks.
 * 
 * This class handles:
 * - Device-specific styling (mobile, tablet, desktop)
 * - Responsive CSS generation with proper fallback hierarchy
 * - Layout management across different screen sizes
 * - Conflict resolution with WordPress core styling
 *
 * @since 3.0.0-beta.1
 */
class ResponsiveControls {

	use Singleton;

	/**
	 * Lists of blocks and prefixes for responsive control filtering.
	 *
	 * @var array $excluded_blocks Block names that should not have responsive controls applied.
	 * @since 3.0.0-beta.1
	 */
	private $excluded_blocks = array();

	/**
	 * Lists of blocks and prefixes for responsive control filtering.
	 *
	 * @var array $allowed_prefixes Block name prefixes that identify blocks that should have responsive controls.
	 * @since 3.0.0-beta.1
	 */
	private $allowed_prefixes = array( 'spectra/', 'spectra-pro/' );

	/**
	 * Lists of blocks and prefixes for responsive control filtering.
	 *
	 * @var array $supported_blocks Specific block names that should have responsive controls applied.
	 * @since 3.0.0-beta.1
	 */
	private $supported_blocks = array(
		'core/image',
	);

	/**
	 * Media queries for different screen sizes.
	 *
	 * @var array $media_queries Media queries for different screen sizes.
	 * @since 3.0.0-beta.1
	 */
	private $media_queries = array(
		'sm' => '(max-width: 767.98px)',                 // Scoped to only mobile.
		'md' => '(min-width: 768px) and (max-width: 1023.98px)', // Scoped to tablet.
		'lg' => '(min-width: 1024px)',                   // Scoped to desktop.
	);

	/**
	 * Handle for the responsive styles stylesheet.
	 *
	 * Used to register and enqueue the stylesheet that contains
	 * all responsive CSS rules generated by this extension.
	 *
	 * @var string WordPress stylesheet handle.
	 * @since 3.0.0-beta.1
	 */
	private $style_handle = 'spectra-responsive-styles';

	/**
	 * Add inline responsive CSS only once per request.
	 * 
	 * Stores a list of inline CSS rules already added to avoid duplicates.
	 * 
	 * @var array List of inline CSS rules already added.
	 * @since 3.0.0-beta.1
	 */
	private $inline_css_added = array();

	/**
	 * Track if default CSS has been added for each block type.
	 * 
	 * Prevents duplicate default CSS from being added multiple times
	 * for blocks that appear multiple times on a page.
	 * 
	 * @var array List of block types that have had default CSS added.
	 * @since 3.0.0-beta.1
	 */
	private $default_css_added = array();

	/**
	 * Expiration time for cached responsive CSS.
	 *
	 * Cached CSS is stored in a transient and reused across requests
	 * until this expiration time is reached.
	 *
	 * @var int Seconds to keep the cached CSS.
	 * @since 3.0.0-beta.1
	 */
	private $cache_expiration = DAY_IN_SECONDS;

	/**
	 * Device fallback hierarchy for responsive values.
	 *
	 * Defines the order in which devices should fall back to get values.
	 * If a value is not set for the current device, it will try the next device in the array.
	 *
	 * @var array<string, array<string>> Device => fallback order array.
	 * @since 3.0.0-beta.1
	 */
	private $device_fallback_order = array(
		'sm' => array( 'sm', 'md', 'lg' ), // Mobile: try mobile -> tablet -> desktop for values.
		'md' => array( 'md', 'lg' ),       // Tablet: try tablet -> desktop for values.
		'lg' => array( 'lg' ),             // Desktop: desktop only for values.
	);

	/**
	 * List of attribute keys that should be processed responsively.
	 * 
	 * These keys represent block attributes that can have different values
	 * across different device sizes (mobile, tablet, desktop). The responsive
	 * controls system will track and manage these attributes separately for
	 * each breakpoint.
	 *
	 * @var array<string> List of attribute keys to process responsively.
	 * @since 3.0.0-beta.1
	 */
	private $responsive_keys = array( 'layout', 'fontSize', 'fontFamily', 'borderColor' );

	/**
	 * Core WordPress style properties that conflict with responsive controls.
	 *
	 * These properties need to be removed from block attributes to prevent
	 * conflicts with our responsive control system.
	 *
	 * @var array<string> List of core style property names.
	 * @since 3.0.0-beta.1
	 */
	private $style_responsive_keys = array(
		'spacing',    // Padding, margin, blockGap properties that are critical for layout containers.
		'typography', // Font family, size, weight, line height properties.
		'border',     // Border styles, width, radius properties.
		'shadow',     // Box shadow effect properties.
		'layout',     // Layout properties that conflict with responsive controls.
	);

	/**
	 * Core WordPress block attributes that conflict with responsive controls.
	 *
	 * Individual block attributes that need to be removed when responsive
	 * controls are active for the same properties.
	 *
	 * @var array<string> List of core attribute names.
	 * @since 3.0.0-beta.1
	 */
	private $core_attributes = array(
		'fontSize',    // Typography font size attribute that conflicts with responsive controls.
		'fontFamily',  // Typography font family attribute that conflicts with responsive controls.
		'layout',      // Layout controls attribute (flexbox, grid, etc.) that conflicts with responsive controls.
		'borderColor', // Border color attribute that conflicts with responsive controls.
	);

	/**
	 * Default layout configurations for specific Spectra blocks.
	 * 
	 * This array defines the default layout settings that should be applied
	 * to specific blocks when no custom layout has been defined by the user.
	 * Each block type can have its own predefined layout structure to ensure
	 * consistent appearance and behavior.
	 *
	 * @var array<string, array> Block name => Default layout configuration.
	 * @since 3.0.0-beta.1
	 */
	private $blocks_default_layout = array(
		'spectra/accordion'               => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'stretch',
			),
		),
		'spectra/accordion-child-item'    => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'stretch',
			),
		),
		'spectra/accordion-child-header'  => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/accordion-child-details' => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
				'orientation'       => 'vertical',
			),
		),
		'spectra/buttons'                 => array(
			'layout' => array(
				'type' => 'flex',
			),
		),
		'spectra/container'               => array(
			'layout' => array(
				'type'              => 'flex',
				'orientation'       => 'vertical',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/icons'                   => array(
			'layout' => array(
				'type'              => 'flex',
				'orientation'       => 'horizontal',
				'flexWrap'          => 'wrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/tabs'                    => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'stretch',
			),
		),
		'spectra/tabs-child-tab-wrapper'  => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/tabs-child-tabpanel'     => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'top',
				'orientation'       => 'vertical',
			),
		),
		'spectra/countdown'               => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'orientation'       => 'horizontal',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'top',
			),
		),
		'spectra/countdown-child-day'     => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'center',
			),
		),
		'spectra/countdown-child-hour'    => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'center',
			),
		),
		'spectra/countdown-child-minute'  => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'center',
			),
		),
		'spectra/countdown-child-second'  => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'center',
			),
		),
		'spectra/slider-child'            => array(
			'layout' => array(
				'type'              => 'flex',
				'orientation'       => 'vertical',
				'justifyContent'    => 'center',
				'verticalAlignment' => 'center',
				'flexWrap'          => 'wrap',
			),
		),
		'spectra/list'                    => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'orientation'    => 'vertical',
				'justifyContent' => 'stretch',
			),
		),
		'spectra/list-child-item'         => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'left',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/modal-child-trigger'     => array(
			'layout' => array(
				'type'           => 'flex',
				'flexWrap'       => 'nowrap',
				'justifyContent' => 'left',
			),
		),
		'spectra/modal-child-popup'       => array(
			'layout' => array(
				'type' => 'flex',
			),
		),
		'spectra/popup-builder'           => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'justifyContent'    => 'center',
				'verticalAlignment' => 'center',
				'orientation'       => 'horizontal',
			),
		),
		'spectra/counter'                 => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'orientation'       => 'vertical',
				'justifyContent'    => 'center',
				'verticalAlignment' => 'center',
			),
		),
		'spectra/counter-child-wrapper'   => array(
			'layout' => array(
				'type'              => 'flex',
				'flexWrap'          => 'nowrap',
				'orientation'       => 'vertical',
				'justifyContent'    => 'center',
				'verticalAlignment' => 'center',
			),
		),
	);

	/**
	 * Hooks into WordPress to register responsive stylesheets and
	 * to process responsive block attributes.
	 * 
	 * Sets up all necessary hooks for:
	 * - Registering responsive stylesheet
	 * - Processing responsive attributes during block rendering
	 * - Adding unique block identifiers for CSS targeting
	 * - Managing layout support to prevent conflicts with core
	 * 
	 * @since 3.0.0-beta.1
	 * @return void
	 */
	public function init() {    
		// Register responsive stylesheet early to ensure it's available for all blocks.
		add_action( 'init', array( $this, 'register_responsive_style' ) );

		// Register and enqueue responsive videos script for frontend.
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_responsive_videos_script' ) );

		// Enqueue responsive control injection assets for admin.
		add_action( 'enqueue_block_assets', array( $this, 'enqueue_assets' ) );

		// Process responsive attributes during block rendering to generate CSS.
		add_filter( 'render_block_data', array( $this, 'process_responsive_attributes' ), 10, 1 );

		// Add unique block identifier for CSS targeting and generate responsive styles.
		add_filter( 'render_block', array( $this, 'process_block_and_add_responsive_styles' ), 10, 2 );

		// Remove layout support flag to prevent core from injecting layout classes that conflict.
		remove_filter( 'render_block', 'wp_render_layout_support_flag', 10 );

		// Conditionally suppress layout support for Spectra blocks only to avoid conflicts.
		add_filter( 'render_block', array( $this, 'maybe_skip_layout_support' ), 10, 2 );

		// Handle duplicate spectraIds after post save.
		add_action( 'save_post', array( $this, 'ensure_unique_spectra_ids' ), 20, 2 );
	}

	/**
	 * Registers a responsive stylesheet for all block responsive controls.
	 *
	 * Creates an empty stylesheet that will be dynamically populated with media query-based
	 * CSS rules for responsive block controls. This centralized approach prevents multiple
	 * style enqueues and ensures proper dependency management.
	 *
	 * Note: Uses `wp_register_style()` with `false` source to create a virtual stylesheet
	 * that will be populated with inline styles later. This is registered early in init
	 * to ensure availability for all blocks.
	 *
	 * @since 3.0.0-beta.1
	 * @return void
	 */
	public function register_responsive_style() {
		// Only register on frontend requests (excluding AJAX and admin).
		if ( wp_doing_ajax() || is_admin() ) {
			return;
		}

		// Check if style is already registered to avoid duplicates.
		if ( wp_style_is( $this->style_handle, 'registered' ) ) {
			return;
		}

		/**
		 * Register virtual stylesheet:
		 * - Source: false (indicates inline styles will be added later).
		 * - No dependencies by default.
		 * - Versioned for cache control.
		 */
		wp_register_style(
			$this->style_handle,       // Stylesheet handle.
			false,                     // No source file (inline styles only).
			array(),                   // No dependencies.
			UAGB_VER                   // Version for cache busting.
		);
	}

	/**
	 * Enqueue responsive videos script for frontend.
	 * 
	 * Conditionally enqueues the responsive videos JavaScript file only when
	 * container or slider blocks with responsive video backgrounds are present.
	 * This handles dynamic video source switching based on viewport size.
	 * 
	 * @since 3.0.0-beta.1
	 * @return void
	 */
	public function enqueue_responsive_videos_script() {
		// Only enqueue on frontend requests (excluding AJAX and admin).
		if ( wp_doing_ajax() || is_admin() ) {
			return;
		}

		// Only enqueue if container, slider, or modal blocks are present.
		if ( ! has_block( 'spectra/container' ) && ! has_block( 'spectra/slider' ) && ! has_block( 'spectra/modal' ) && ! has_block( 'spectra/slider-child' ) ) {
			return;
		}

		wp_enqueue_script(
			'spectra-responsive-videos',
			SPECTRA_3_URL . 'assets/js/responsive-videos.js',
			array(),
			filemtime( SPECTRA_3_DIR . 'assets/js/responsive-videos.js' ),
			true
		);
	}

	/**
	 * Enqueues responsive control injection assets for block editor.
	 * 
	 * Enqueues the CSS needed for the responsive control injection system
	 * that adds device buttons to existing Gutenberg controls.
	 *
	 * @since 3.0.0-beta.1.1
	 *
	 * @return void
	 */
	public function enqueue_assets() {
		wp_enqueue_style( 'spectra-extensions-responsive-controls' );
	}

	/**
	 * Processes responsive attributes for a given block.
	 * 
	 * This is the main entry point for responsive control processing:
	 * 1. Checks if the block should be processed
	 * 2. Ensures the block has a unique ID
	 * 3. Removes conflicting core attributes
	 * 4. Generates responsive CSS
	 * 5. Adds the CSS to the responsive stylesheet
	 * 
	 * @since 3.0.0-beta.1
	 *
	 * @param array $block Block data from render_block_data filter.
	 * @return array Modified block data with processed responsive attributes.
	 */
	public function process_responsive_attributes( $block ) {
		// Skip processing if this isn't a Spectra block with responsive controls.
		if ( ! $this->should_apply_responsive_controls( $block ) ) {
			return $block;
		}

		// Get block attributes or initialize empty array if none exist.
		$attrs                  = $block['attrs'] ?? array();
		$responsive_controls    = $attrs['responsiveControls'] ?? array();
		$responsive_controls_lg = $responsive_controls['lg'] ?? array();

		// If no responsive controls exist yet, migrate standard attributes to responsive format.
		if ( empty( $responsive_controls ) ) {
			foreach ( array_merge( array( 'style' ), $this->responsive_keys ) as $key ) {
				// Copy each responsive key from block attributes to responsive controls.
				if ( isset( $attrs[ $key ] ) ) {
					// For style attribute, only copy specific responsive properties.
					if ( 'style' === $key && is_array( $attrs[ $key ] ) ) {

						// If responsive controls (Lg) style is empty, copy all properties.
						if ( empty( $responsive_controls_lg['style'] ) ) {
							$responsive_controls_lg['style'] = $attrs[ $key ];
						} else {
							$responsive_controls_lg[ $key ] = array();
							foreach ( $this->style_responsive_keys as $style_key ) {
								if ( isset( $attrs[ $key ][ $style_key ] ) ) {
									// For border property, copy all properties dynamically.
									if ( 'border' === $style_key && is_array( $attrs[ $key ][ $style_key ] ) ) {
										$responsive_controls_lg[ $key ][ $style_key ] = $attrs[ $key ][ $style_key ];
									} else {
										$responsive_controls_lg[ $key ][ $style_key ] = $attrs[ $key ][ $style_key ];
									}
								}
							}
						}
					} else {
						$responsive_controls_lg[ $key ] = $attrs[ $key ];
					}
				}
			}

			// Migrate block-specific attributes to responsive format.
			$block_specific_attrs = ResponsiveAttributeCSS::get_responsive_attributes( $block['blockName'] );
			foreach ( $block_specific_attrs as $attr ) {
				if ( isset( $attrs[ $attr ] ) ) {
					$responsive_controls_lg[ $attr ] = $attrs[ $attr ];
				}
			}
		}

		/**
		 * Apply filters to modify the default layout for responsive controls.
		 * 
		 * @since 3.0.0-beta.1
		 * 
		 * @param array $this->blocks_default_layout Default layout configurations for blocks.
		 * @return array Modified default layout configurations.
		 */
		$this->blocks_default_layout = apply_filters( 'spectra_blocks_responsive_default_layout', $this->blocks_default_layout );
		
		// If no layout is defined, use the default layout for the block.
		if ( empty( $responsive_controls_lg['layout'] ) && isset( $this->blocks_default_layout[ $block['blockName'] ] ) ) {
			$responsive_controls_lg['layout'] = $this->blocks_default_layout[ $block['blockName'] ]['layout'];
		}

		// Correctly assign updated lg-specific controls back into the block.
		$block['attrs']['responsiveControls']['lg'] = $responsive_controls_lg;

		// Remove any core attributes that would conflict with our responsive controls.
		$this->remove_conflicting_core_attributes( $block['attrs'], $block['blockName'] ?? '' );

		return $block;
	}

	/**
	 * Processes block content and adds responsive styles.
	 * 
	 * This method:
	 * 1. Injects a data-spectra-id attribute into the block's HTML for CSS targeting
	 * 2. Generates responsive CSS for all breakpoints
	 * 3. Adds the generated CSS to the stylesheet
	 * 4. Returns the modified block content
	 * 
	 * @since 3.0.0-beta.1
	 *
	 * @param string $block_content The rendered block HTML content.
	 * @param array  $block The complete block data.
	 * @return string The modified block content with ID attribute and responsive styles.
	 */
	public function process_block_and_add_responsive_styles( $block_content, $block ) {
		// Skip processing if this isn't a Spectra block with responsive controls.
		if ( ! $this->should_apply_responsive_controls( $block ) ) {
			return $block_content;
		}

		// Get block attributes or return early if none exist.
		$attrs = $block['attrs'] ?? array();
		if ( empty( $attrs ) ) {
			return $block_content;
		}
		
		// Ensure the block has a unique ID for CSS targeting.
		$this->ensure_block_has_id( $attrs );

		$spectra_id          = $attrs['spectraId'] ?? '';
		$responsive_controls = $attrs['responsiveControls'] ?? array();
		$block_name          = $block['blockName'] ?? '';

		// Skip processing if no ID or responsive controls exist.
		if ( empty( $spectra_id ) || empty( $responsive_controls ) ) {
			return $block_content;
		}

		// Use WordPress HTML Tag Processor to safely modify the HTML.
		$processor = new WP_HTML_Tag_Processor( $block_content );

		// Find the first HTML tag and add our custom data attribute.
		if ( $processor->next_tag() ) {
			$processor->set_attribute(
				'data-spectra-id',
				esc_attr( $spectra_id )
			);

			// Get the updated HTML with our attribute added.
			$block_content = $processor->get_updated_html();
		}

		// For core/image blocks, remove conflicting inline styles from img elements.
		if ( 'core/image' === $block_name && ! empty( $responsive_controls ) ) {
			$block_content = $this->remove_core_image_inline_styles( $block_content, $responsive_controls );
		}

		// Only generate and add inline CSS once per unique spectraId.
		if ( ! isset( $this->inline_css_added[ $spectra_id ] ) ) {
			$responsive_css = $this->get_cached_responsive_css( $spectra_id, $responsive_controls, $block_name, $attrs );

			// Generate orientation reverse CSS for container blocks
			$orientation_reverse_css = '';
			if ( 'spectra/container' === $block_name ) {
				$orientation_reverse_css = $this->generate_orientation_reverse_css( $spectra_id, $attrs );
			}

			// Combine all CSS
			$combined_css = trim( $responsive_css . "\n" . $orientation_reverse_css );

			// If CSS was generated, add it to the stylesheet.
			if ( ! empty( $combined_css ) ) {
				// Enqueue the stylesheet if not already done.
				wp_enqueue_style( $this->style_handle );
			
				// Add our generated CSS as inline styles.
				wp_add_inline_style( $this->style_handle, $combined_css );

				// Mark as added to avoid duplicates.
				$this->inline_css_added[ $spectra_id ] = true;
			}
		}

		// Return the modified block content with our data attribute.
		return $block_content;
	}

	/**
	 * Conditionally skips layout support injection for Spectra blocks only.
	 * 
	 * Since we removed the core layout support filter globally,
	 * we need to re-apply it for non-Spectra blocks to maintain
	 * compatibility with other plugins and themes.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $block_content The rendered block HTML.
	 * @param array  $block         The block data.
	 * @return string Filtered block HTML with appropriate layout support.
	 */
	public function maybe_skip_layout_support( $block_content, $block ) {
		// Skip layout support for Spectra blocks as we handle it ourselves.
		if ( Core::is_spectra_block( array( 'name' => $block['blockName'] ) ) ) {
			return $block_content;
		}

		// Apply WordPress default layout rendering for all other blocks.
		return wp_render_layout_support_flag( $block_content, $block );
	}

	/**
	 * Ensure all Spectra blocks have unique spectraIds after post save.
	 * 
	 * This method runs after a post is saved and checks for duplicate
	 * spectraIds across all blocks. If duplicates are found, it generates
	 * new unique IDs and updates the post content.
	 * 
	 * @since 3.0.0-beta.1
	 * @param int     $post_id The post ID.
	 * @param WP_Post $post The post object.
	 * @return void
	 */
	public function ensure_unique_spectra_ids( $post_id, $post ) {
		// Skip autosaves, revisions, and posts without content.
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) || empty( $post->post_content ) ) {
			return;
		}

		// Skip if no Spectra blocks in content.
		if ( ! has_blocks( $post->post_content ) || strpos( $post->post_content, 'spectraId' ) === false ) {
			return;
		}

		// Parse blocks from content.
		$blocks = parse_blocks( $post->post_content );
		if ( empty( $blocks ) ) {
			return;
		}

		// Track seen IDs and process blocks.
		$seen_ids         = array();
		$modified         = false;
		$processed_blocks = $this->process_blocks_for_unique_ids( $blocks, $seen_ids, $modified );

		// If any IDs were modified, update the post.
		if ( $modified ) {
			// Remove the action to prevent infinite loop.
			remove_action( 'save_post', array( $this, 'ensure_unique_spectra_ids' ), 20 );

			// Update post content with new block structure.
			// Instead of using serialize_blocks which can strip backslashes,
			// we'll use wp_slash to ensure proper escaping for the database.
			$serialized_content = serialize_blocks( $processed_blocks );
			
			wp_update_post(
				array(
					'ID'           => $post_id,
					'post_content' => wp_slash( $serialized_content ),
				) 
			);

			// Re-add the action.
			add_action( 'save_post', array( $this, 'ensure_unique_spectra_ids' ), 20, 2 );
		}
	}

	/**
	 * Determines if responsive controls should be applied to a block.
	 *
	 * This function checks if a block should have responsive controls applied
	 * based on its name. It verifies that the block is not in the excluded list
	 * and either has an allowed prefix or is explicitly supported.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param array $block The block to check.
	 * @return bool Whether responsive controls should be applied.
	 */
	public function should_apply_responsive_controls( $block ) {
		// Skip if block has no name.
		if ( ! isset( $block['blockName'] ) || empty( $block['blockName'] ) ) {
			return false;
		}
	
		$block_name = $block['blockName'];
	
		// Skip excluded blocks.
		if ( in_array( $block_name, $this->excluded_blocks, true ) ) {
			return false;
		}
	
		// Check if block has allowed prefix.
		foreach ( $this->allowed_prefixes as $prefix ) {
			if ( strpos( $block_name, $prefix ) === 0 ) {
				return true;
			}
		}
	
		// Check if block is explicitly supported.
		return in_array( $block_name, $this->supported_blocks, true );
	}

	/**
	 * Remove conflicting core attributes to prevent style conflicts.
	 * 
	 * Core WordPress attributes and style properties can conflict with
	 * our responsive controls. This method removes them from the block
	 * attributes to ensure our responsive styles take precedence.
	 *
	 * @since 3.0.0-beta.1
	 * @param array  $attrs Block attributes passed by reference.
	 * @param string $block_name The name of the block.
	 * @return void
	 */
	private function remove_conflicting_core_attributes( &$attrs, $block_name ) {
		// Remove conflicting style properties if they exist.
		if ( isset( $attrs['style'] ) && is_array( $attrs['style'] ) ) {
			// Only remove style properties that are in the style_responsive_keys list.
			foreach ( $this->style_responsive_keys as $property ) {
				// Remove all properties including border (which is now handled dynamically).
				unset( $attrs['style'][ $property ] );
			}

			// Remove the entire style attribute if it's now empty.
			if ( empty( $attrs['style'] ) ) {
				unset( $attrs['style'] );
			}
		}

		// Remove conflicting individual attributes that are in the responsive_keys list.
		foreach ( $this->core_attributes as $attribute ) {
			if ( in_array( $attribute, $this->responsive_keys ) ) {
				unset( $attrs[ $attribute ] );
			}
		}

		// Remove block specific attributes.
		$block_specific_attrs = ResponsiveAttributeCSS::get_responsive_attributes( $block_name );
		foreach ( $block_specific_attrs as $attribute ) {
			unset( $attrs[ $attribute ] );
		}
	}

	/**
	 * Ensures block has a unique ID for CSS targeting.
	 *
	 * This method checks if the block has a `spectraId`. If not, it generates one
	 * using `wp_generate_uuid4()` and disables CSS cache to ensure new styles are applied.
	 *
	 * @since 3.0.0-beta.1
	 * @param array $attrs Block attributes passed by reference.
	 * @return void
	 */
	private function ensure_block_has_id( &$attrs ) {
		if ( empty( $attrs['spectraId'] ) ) {
			$attrs['spectraId'] = 'spectra-' . wp_generate_uuid4();

			// Disable caching for dynamically generated IDs to ensure fresh CSS.
			if ( ! has_filter( 'spectra_enable_css_cache', '__return_false' ) ) {
				add_filter( 'spectra_enable_css_cache', '__return_false' );
			}
		}
	}

	/**
	 * Retrieve or generate cached responsive CSS for a block instance.
	 *
	 * Uses a stable cache key (`spectra_responsive_css_{$spectra_id}`) and stores
	 * a hash fingerprint of `responsive_controls` alongside the CSS. If the hash
	 * matches, the cached CSS is returned. Otherwise, new CSS is generated and cached.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param string $spectra_id Unique ID of the block instance.
	 * @param array  $responsive_controls Responsive controls data.
	 * @param string $block_name The name of the block.
	 * @return string Cached or newly generated CSS.
	 */
	private function get_cached_responsive_css( $spectra_id, $responsive_controls, $block_name, $attrs ) {
		/**
		 * Filter whether to enable caching for responsive CSS.
		 * 
		 * @since 3.0.0-beta.1
		 * 
		 * @param bool $enable_cache Whether to enable caching. Default is true.
		 * @return bool True to enable caching, false to disable.
		 */
		$enable_cache = apply_filters( 'spectra_enable_css_cache', true );

		// Generate cache key.
		$cache_key = 'spectra_responsive_css_' . $spectra_id . '_' . UAGB_VER;

		// Generate hash fingerprint including block name for proper cache invalidation.
		$cache_data    = array(
			'controls'   => $responsive_controls,
			'block_name' => $block_name,
		);
		$controls_hash = md5( wp_json_encode( $cache_data ) );

		// Attempt to retrieve cached data.
		$cached = get_transient( $cache_key );

		// If cache is enabled and hash matches, return cached CSS.
		if ( $enable_cache && is_array( $cached ) && isset( $cached['hash'], $cached['css'] ) && $cached['hash'] === $controls_hash ) {
			return $cached['css'];
		}

		// Cache miss or outdated hash: regenerate CSS and store it.
		$css = $this->generate_responsive_css( $spectra_id, $responsive_controls, $block_name, $attrs );

		// Store the hash and CSS in the cache if caching is enabled and CSS is not empty.
		if ( $enable_cache && ! empty( $css ) ) {
			set_transient(
				$cache_key,
				array(
					'hash' => $controls_hash,
					'css'  => $css,
				),
				$this->cache_expiration
			);
		}

		return $css;
	}

	/**
	 * Generate responsive CSS for all breakpoints.
	 * 
	 * Creates media queries for each device size and generates
	 * the appropriate CSS for each breakpoint based on the
	 * responsive controls data.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param string $spectra_id         The unique block instance ID for CSS targeting.
	 * @param array  $responsive_controls The responsive controls data from block attributes.
	 * @param string $block_name         The name of the block.
	 * @return string The complete generated CSS for all breakpoints.
	 */
	public function generate_responsive_css( $spectra_id, $responsive_controls, $block_name, $attrs ) {
		$styles = array();

		// Create CSS selector using the block's unique ID for targeting.
		// Use higher specificity selector to override core styles without !important.
		// Using double class selector for increased specificity.

		// Handle core blocks differently - they use 'wp-block-{type}' not 'wp-block-core-{type}'.
		if ( strpos( $block_name, 'core/' ) === 0 ) {
			$block_type  = str_replace( 'core/', '', $block_name );
			$block_class = '.wp-block-' . $block_type;
		} else {
			$block_class = '.wp-block-' . str_replace( '/', '-', $block_name );
		}

		// This creates specificity of 0-2-1 (2 classes + 1 attribute).
		$selector = "{$block_class}{$block_class}[data-spectra-id='{$spectra_id}']";

		/**
		 * Filter to modify the responsive CSS selector for a block.
		 * 
		 * This filter allows developers to customize the CSS selector used for
		 * responsive styling of Spectra blocks. The selector is used to target
		 * the specific block instance for CSS rules generation.
		 * 
		 * The default selector has specificity 0-2-1 (2 classes + 1 attribute):
		 * `.wp-block-spectra-container.wp-block-spectra-container[data-spectra-id='spectra-123']`
		 * 
		 * Example usage:
		 * ```php
		 * add_filter( 'spectra_responsive_css_selector', function( $selector, $block_name, $spectra_id ) {
		 *     // Use lower specificity for theme compatibility
		 *     if ( 'spectra/container' === $block_name ) {
		 *         return ".wp-block-spectra-container[data-spectra-id='{$spectra_id}']";
		 *     }
		 *     
		 *     // Target blocks within specific contexts
		 *     if ( is_single() && 'spectra/button' === $block_name ) {
		 *         return ".single-post {$selector}";
		 *     }
		 *     
		 *     return $selector;
		 * }, 10, 3 );
		 * ```
		 * 
		 * @since 3.0.0-beta.1
		 *
		 * @param string $selector   The CSS selector for the block.
		 * @param string $block_name The name of the block (e.g., 'spectra/container').
		 * @param string $spectra_id The unique ID of the block instance.
		 * @return string Modified CSS selector.
		 */
		$selector = apply_filters( 'spectra_responsive_css_selector', $selector, $block_name, $spectra_id );

		// Detect if we're in pattern preview context by checking if we're being called from the pattern preview function
		$is_pattern_preview = false;
		
		// Detect pattern preview context using backtrace
		$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 15 );
		foreach ( $backtrace as $trace ) {
			if ( isset( $trace['function'] ) && 
				 ( 'spectra_get_v3_blocks_css_for_preview' === $trace['function'] ||
				   'spectra_get_comprehensive_responsive_css_for_post' === $trace['function'] ||
				   'spectra_process_blocks_for_comprehensive_css' === $trace['function'] ||
				   'spectra_get_static_css_for_pro_v2_blocks' === $trace['function'] ||
				   'spectra_process_blocks_for_responsive_css' === $trace['function'] ) ) {
				$is_pattern_preview = true;
				break;
			}
		}

		// Use appropriate base selector based on context
		$base_selector = $is_pattern_preview ? '.st-block-container' : 'body';

		// For pattern preview, use more robust selectors that don't rely on complex nesting
		if ( $is_pattern_preview ) {
			// Simplified selectors for pattern preview context
			$layout_specificity_selector     = "{$block_class}[data-spectra-id='{$spectra_id}']";
			$background_specificity_selector = "{$block_class}[data-spectra-id='{$spectra_id}']";
			$child_reset_selector            = "{$block_class}[data-spectra-id='{$spectra_id}']";
			
			// Override main selector for pattern preview to be less specific
			$selector = "{$block_class}[data-spectra-id='{$spectra_id}']";

			/**
			 * Filter to modify the responsive CSS selector for a block.
			 * Applied AFTER pattern preview detection to allow overriding preview selectors.
			 *
			 * @since 3.0.0-beta.1
			 *
			 * @param string $selector   The CSS selector for the block.
			 * @param string $block_name The name of the block (e.g., 'spectra/container').
			 * @param string $spectra_id The unique ID of the block instance.
			 * @return string Modified CSS selector.
			 */
			$selector = apply_filters( 'spectra_responsive_css_selector', $selector, $block_name, $spectra_id );

			// Special handling for slider-child in pattern preview
			if ( 'spectra/slider-child' === $block_name ) {
				$layout_specificity_selector = "{$block_class}[data-spectra-id='{$spectra_id}'] .slide-content";
				$child_reset_selector        = "{$block_class}[data-spectra-id='{$spectra_id}'] .slide-content";
			}

			// Special handling for counter in pattern preview
			if ( 'spectra/counter' === $block_name ) {
				$counter_main_preview = "{$block_class}[data-spectra-id='{$spectra_id}']";
				$counter_content_preview = "{$block_class}[data-spectra-id='{$spectra_id}'] .spectra-counter-content-wrapper";
				$layout_specificity_selector = $counter_main_preview . ', ' . $counter_content_preview;
				$child_reset_selector = $counter_main_preview . ', ' . $counter_content_preview;
			}
		} else {
			// Use lower specificity 0-1-1 selector for layout CSS to allow Global Styles to override.
			$layout_specificity_selector = "{$base_selector} {$block_class}:where([data-spectra-id='{$spectra_id}'])";

			// Special handling for slider-child: target the inner .slide-content div for layout CSS.
			if ( 'spectra/slider-child' === $block_name ) {
				$layout_specificity_selector = "{$base_selector} {$block_class}:where([data-spectra-id='{$spectra_id}']) .slide-content";
			}

			// Special handling for counter: target both main block and content wrapper for layout CSS.
			if ( 'spectra/counter' === $block_name ) {
				// Target both the main block container and the content wrapper
				$counter_main_selector = "{$base_selector} {$block_class}:where([data-spectra-id='{$spectra_id}'])";
				$counter_content_selector = "{$base_selector} {$block_class}:where([data-spectra-id='{$spectra_id}']) .spectra-counter-content-wrapper";
				$layout_specificity_selector = $counter_main_selector . ', ' . $counter_content_selector;
			}

			// Use lower specificity 0-1-0 selector for background CSS to allow Global Styles to override.
			$background_specificity_selector = "{$block_class}:where([data-spectra-id='{$spectra_id}'])";

			// Use 0-1-1 specificity to override Astra theme's 0-1-1 via CSS cascade order.
			// Using universal selector + :where() + attribute to work with any HTML tag (div, section, etc.).
			$child_reset_selector = "{$base_selector} *:where({$block_class})[data-spectra-id='{$spectra_id}']";
			
			// For slider-child, also update child reset selector to target content inside .slide-content.
			if ( 'spectra/slider-child' === $block_name ) {
				$child_reset_selector = "{$base_selector} *:where({$block_class})[data-spectra-id='{$spectra_id}'] .slide-content";
			}

			// For counter, also update child reset selector to target both main block and content wrapper.
			if ( 'spectra/counter' === $block_name ) {
				$counter_main_reset = "{$base_selector} *:where({$block_class})[data-spectra-id='{$spectra_id}']";
				$counter_content_reset = "{$base_selector} *:where({$block_class})[data-spectra-id='{$spectra_id}'] .spectra-counter-content-wrapper";
				$child_reset_selector = $counter_main_reset . ', ' . $counter_content_reset;
			}
		}
		
		// Generate CSS for each device breakpoint (mobile, tablet, desktop).
		foreach ( $this->media_queries as $device => $media ) {
			// Get compiled styles for this device with proper fallback.
			$device_styles = $this->get_device_styles( $responsive_controls, $device, $block_name );

			// Generate layout-specific CSS for this device.
			$layout_css = $this->generate_layout_css( $responsive_controls, $device, $layout_specificity_selector, $child_reset_selector );

			// Generate style-layout-specific CSS for this device.
			$style_layout_css = $this->generate_style_layout_css( $responsive_controls, $device, $layout_specificity_selector );

			// Extract text alignment for special handling.
			$text_align = $device_styles['typography']['textAlign'] ?? '';

			// For spectra/popup-builder blocks, separate popup_builder_spacing styles from other styles.
			if ( 'spectra/popup-builder' === $block_name && isset( $device_styles['spacing'] ) ) {
				// Separate popup_builder_spacing styles and other styles.
				$popup_builder_spacing_styles = array( 'spacing' => $device_styles['spacing'] );
				$other_styles                 = array_diff_key( $device_styles, array( 'spacing' => '' ) );

				// Generate popup_builder_spacing CSS with img selector.
				$popup_builder_spacing_css = '';
				if ( ! empty( $popup_builder_spacing_styles ) ) {
					$popup_builder_spacing_css_array = wp_style_engine_get_styles(
						$popup_builder_spacing_styles,
						array( 'selector' => $selector . ' .spectra-popup-builder__container' )
					);
					$popup_builder_spacing_css       = is_array( $popup_builder_spacing_css_array ) ? $popup_builder_spacing_css_array['css'] ?? '' : '';
				}

				// Generate other styles CSS with figure selector.
				$other_css = '';
				if ( ! empty( $other_styles ) ) {
					$other_css_array = wp_style_engine_get_styles(
						$other_styles,
						array( 'selector' => $selector )
					);
					$other_css       = is_array( $other_css_array ) ? $other_css_array['css'] ?? '' : '';
				}

				// Combine both CSS strings.
				$combined_css = trim( $popup_builder_spacing_css . ' ' . $other_css );
				$css_array    = ! empty( $combined_css ) ? array( 'css' => $combined_css ) : false;
			} else {
				$css_array = wp_style_engine_get_styles(
					$device_styles,
					array( 'selector' => $selector )
				);
			}

			// Extract justify-content for flex blocks.
			$justify_content = $device_styles['spectra_flex']['justifyContent'] ?? '';

			// Use WordPress Style Engine to generate standard CSS.
			// For core/image blocks, separate border styles from other styles.
			if ( 'core/image' === $block_name && isset( $device_styles['border'] ) ) {
				// Separate border styles and other styles.
				$border_styles = array( 'border' => $device_styles['border'] );
				$other_styles  = array_diff_key( $device_styles, array( 'border' => '' ) );

				// Generate border CSS with img selector.
				$border_css = '';
				if ( ! empty( $border_styles ) ) {
					$border_css_array = wp_style_engine_get_styles(
						$border_styles,
						array( 'selector' => $selector . ' img' )
					);
					$border_css       = is_array( $border_css_array ) ? $border_css_array['css'] ?? '' : '';
				}

				// Generate other styles CSS with figure selector.
				$other_css = '';
				if ( ! empty( $other_styles ) ) {
					$other_css_array = wp_style_engine_get_styles(
						$other_styles,
						array( 'selector' => $selector )
					);
					$other_css       = is_array( $other_css_array ) ? $other_css_array['css'] ?? '' : '';
				}

				// Combine both CSS strings.
				$combined_css = trim( $border_css . ' ' . $other_css );
				$css_array    = ! empty( $combined_css ) ? array( 'css' => $combined_css ) : false;
			} else {
				$css_array = wp_style_engine_get_styles(
					$device_styles,
					array( 'selector' => $selector )
				);
			}
			
			// Get the device-specific attributes for this breakpoint with fallback.
			$device_attrs = $this->get_device_attributes( $block_name, $responsive_controls, $device );
			
			// Generate block-specific attribute CSS using ResponsiveAttributeCSS.
			// Use low-specificity selector for background CSS, high-specificity for others.
			$attr_css = ResponsiveAttributeCSS::generate_css( $block_name, $device_attrs, $selector, $background_specificity_selector, $attrs );
			
			// Add overflow handling for containers with border-radius and backgrounds.
			$overflow_css = '';
			if ( 'spectra/container' === $block_name || 'spectra/slider' === $block_name || 'spectra/slider-child' === $block_name ) {
				// Check if this device has border-radius.
				$has_border_radius = isset( $device_styles['border']['radius'] ) && 
									! empty( $device_styles['border']['radius'] );
				
				// Check if this device has video or image background.
				$has_background = false;
				if ( isset( $device_attrs['background'] ) && is_array( $device_attrs['background'] ) ) {
					$bg_type = $device_attrs['background']['type'] ?? '';
					if ( in_array( $bg_type, array( 'image', 'video' ), true ) ) {
						$has_background = true;
					}
				}
				
				// Apply overflow clip when has background AND border-radius (matching style.scss).
				// This ensures video/image backgrounds are properly clipped by border-radius.
				if ( $has_border_radius && $has_background ) {
					$overflow_css = "{$selector}{overflow:hidden;overflow:clip !important;}";
				}
			}

			// Build complete CSS for this device, including media queries.
			$css = $this->build_css_for_device(
				$css_array,
				$layout_css,
				$style_layout_css,
				$text_align,
				$justify_content,
				$attr_css . ' ' . $overflow_css,
				$selector,
				$media
			);


			// Add to styles array if CSS was generated.
			if ( $css ) {
				$styles[] = $css;
			}
		}

		// Combine all device CSS into a single string.
		$css = implode( ' ', $styles );
		
		/**
		 * Filter to modify the complete responsive CSS for a block instance.
		 * 
		 * This filter allows developers to modify the complete generated responsive CSS
		 * for a Spectra block instance after all processing is complete. The CSS includes
		 * all media queries, layout styles, typography, spacing, borders, and block-specific
		 * attributes for all breakpoints (mobile, tablet, desktop).
		 * 
		 * The CSS is fully processed and ready to be injected into the page. Use this filter
		 * to add custom CSS rules, modify existing rules, or completely replace the CSS.
		 * 
		 * Example usage:
		 * ```php
		 * add_filter( 'spectra_responsive_css', function( $css, $spectra_id, $block_name ) {
		 *     // Fix z-index issues for modal blocks
		 *     if ( 'spectra/modal' === $block_name ) {
		 *         $css = str_replace( 'z-index: 999', 'z-index: 9999', $css );
		 *     }
		 * 
		 *     // Add dark mode compatibility
		 *     if ( 'spectra/container' === $block_name ) {
		 *         $selector = "[data-spectra-id='{$spectra_id}']";
		 *         $css .= " @media (prefers-color-scheme: dark) { {$selector} { filter: invert(1); } }";
		 *     }
		 * 
		 *     // Override styles for specific post types
		 *     if ( is_singular( 'product' ) && 'spectra/button' === $block_name ) {
		 *         $css .= " body.single-product [data-spectra-id='{$spectra_id}'] { border-radius: 0 !important; }";
		 *     }
		 * 
		 *     return $css;
		 * }, 10, 3 );
		 * ```
		 * 
		 * @since 3.0.0-beta.1
		 * 
		 * @param string $css        The complete generated CSS for the block instance, including media queries.
		 * @param string $spectra_id The unique ID of the block instance (e.g., 'spectra-123abc').
		 * @param string $block_name The name of the block (e.g., 'spectra/container').
		 * @return string Modified CSS string that will be injected into the page.
		 */
		$css = apply_filters( 'spectra_responsive_css', $css, $spectra_id, $block_name );
		
		return $css;
	}
	
	/**
	 * Get device-specific attributes with proper fallback.
	 *
	 * Extracts attributes for a specific device following the fallback hierarchy:
	 * - Mobile: try mobile -> tablet -> desktop
	 * - Tablet: try tablet -> desktop
	 * - Desktop: desktop only
	 *
	 * Also handles mutually exclusive attributes to ensure that conflicting
	 * styles are not applied simultaneously.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param string $block_name          The name of the block.
	 * @param array  $responsive_controls The responsive controls data.
	 * @param string $device              The target device ('sm', 'md', 'lg').
	 * @return array Device-specific attributes with fallback values.
	 */
	private function get_device_attributes( $block_name, $responsive_controls, $device ) {
		// Get all responsive attributes for the block.
		$block_attrs = ResponsiveAttributeCSS::get_responsive_attributes( $block_name );

		// Get fallback device order for the target device.
		$fallback_devices = $this->device_fallback_order[ $device ] ?? array( 'lg' );
		$device_attrs     = array();

		// Resolve normal attributes.
		foreach ( $block_attrs as $attr ) {
			// Skip text shadow attributes for content block - they're handled as a group.
			if ( 'spectra/content' === $block_name && in_array( $attr, array( 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY' ), true ) ) {
				continue;
			}
			
			// Special handling for background attribute - we need to process it even if null.
			// to ensure video wrapper visibility is controlled properly across breakpoints.
			if ( 'background' === $attr ) {
				// Always set background attribute for each device, even if null.
				// This ensures format_background is called to generate display CSS.
				$device_attrs[ $attr ] = null;
				
				// Build background with inner property fallback support.
				$merged_background = array();
				
				// Define background inner properties that should have individual fallback.
				$background_inner_properties = array(
					'type',
					'media',
					'useOverlay',
					'backgroundSize',
					'backgroundWidth',
					'backgroundRepeat',
					'backgroundPosition',
					'backgroundAttachment',
					'positionMode',
					'positionCentered',
					'positionX',
					'positionY',
				);
				
				// Process each inner property with its own fallback.
				foreach ( $background_inner_properties as $prop ) {
					foreach ( $fallback_devices as $fallback_device ) {
						if ( isset( $responsive_controls[ $fallback_device ]['background'][ $prop ] ) ) {
							$merged_background[ $prop ] = $responsive_controls[ $fallback_device ]['background'][ $prop ];
							break;
						}
					}
				}
				
				// If we have any background properties, use the merged result.
				if ( ! empty( $merged_background ) ) {
					$device_attrs[ $attr ] = $merged_background;
				}
			} elseif ( 'spectra/content' === $block_name && 'enableTextShadow' === $attr ) {
				// Special handling for text shadow enable flag.
				// Process enableTextShadow with proper fallback, respecting explicit disable.
				foreach ( $fallback_devices as $fallback_device ) {
					if ( isset( $responsive_controls[ $fallback_device ]['enableTextShadow'] ) ) {
						$device_attrs[ $attr ] = $responsive_controls[ $fallback_device ]['enableTextShadow'];
						
						// If text shadow is enabled, collect all text shadow attributes.
						if ( $device_attrs[ $attr ] ) {
							$text_shadow_attrs = array( 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY' );
							foreach ( $text_shadow_attrs as $shadow_attr ) {
								// Skip if we already processed this attribute.
								if ( isset( $device_attrs[ $shadow_attr ] ) ) {
									continue;
								}
								
								// Find the first available value in the fallback chain.
								foreach ( $fallback_devices as $fb_device ) {
									if ( isset( $responsive_controls[ $fb_device ][ $shadow_attr ] ) ) {
										$device_attrs[ $shadow_attr ] = $responsive_controls[ $fb_device ][ $shadow_attr ];
										break;
									}
								}
							}
						}
						break;
					}
				}
			} else {
				// Normal attribute resolution with fallback.
				foreach ( $fallback_devices as $fallback_device ) {
					if ( isset( $responsive_controls[ $fallback_device ][ $attr ] ) && $this->has_actual_value( $responsive_controls[ $fallback_device ][ $attr ] ) ) {
						$device_attrs[ $attr ] = $responsive_controls[ $fallback_device ][ $attr ];
						break;
					}
				}
			}
		}

		return $device_attrs;
	}

	/**
	 * Build CSS for a specific device.
	 * 
	 * Combines CSS from multiple sources:
	 * - Style engine output
	 * - Layout CSS
	 * - Style layout CSS
	 * - Text alignment
	 * - Block-specific attribute CSS
	 * 
	 * And wraps it in a media query if a breakpoint is provided.
	 *
	 * @since 3.0.0-beta.1
	 * @param array|false $css_array Style engine output from wp_style_engine_get_styles().
	 * @param string      $layout_css Generated layout CSS for this device.
	 * @param string      $style_layout_css Additional layout CSS from style controls.
	 * @param string      $text_align Text align value for this device.
	 * @param string      $justify_content Justify content value for flex blocks.
	 * @param string      $attr_css Block-specific attribute CSS.
	 * @param string      $selector CSS selector for targeting the block.
	 * @param string      $media Media query for this device.
	 * @return string Complete CSS for this device, wrapped in media query if needed.
	 */
	private function build_css_for_device( $css_array, $layout_css, $style_layout_css, $text_align, $justify_content, $attr_css, $selector, $media ) {
		// Start with empty CSS string.
		$css = '';

		// Add CSS from WordPress style engine if available.
		if ( is_array( $css_array ) && ! empty( $css_array['css'] ) ) {
			$css = $css_array['css'];
		}

		// Add text alignment CSS if available.
		if ( ! empty( $text_align ) ) {
			$css .= "{$selector}{text-align:{$text_align};}";
		}

		// Add justify-content CSS for flex blocks if available.
		if ( ! empty( $justify_content ) ) {
			$css .= "{$selector}{justify-content:{$justify_content};}";
		}

		// Add layout CSS if available.
		if ( ! empty( $layout_css ) ) {
			$css .= ' ' . $layout_css;
		}

		// Add additional style layout CSS if available.
		if ( ! empty( $style_layout_css ) ) {
			$css .= ' ' . $style_layout_css;
		}
		
		// Add block-specific attribute CSS if available.
		if ( ! empty( $attr_css ) ) {
			$css .= ' ' . $attr_css;
		}

		// Return empty string if no CSS was generated.
		if ( empty( $css ) ) {
			return '';
		}

		// Wrap in media query if needed.
		return $media
		? "@media {$media} {{$css}}"
		: $css;
	}

	/**
	 * Check if a value is set (not null or empty string).
	 * 
	 * Special handling for:
	 * - Arrays (recursively checks if any value is set)
	 * - Zero values (treats 0, '0', '0px', etc. as valid values)
	 * - Empty strings and null (treats as not set)
	 *
	 * @since 3.0.0-beta.1
	 * @param mixed $value The value to check.
	 * @return bool True if value is set and usable, false otherwise.
	 */
	private function has_actual_value( $value ) {
		// Null and empty string are not valid values.
		if ( null === $value || '' === $value ) {
			return false;
		}

		// For arrays, check if any value inside is valid.
		if ( is_array( $value ) ) {
			foreach ( $value as $item ) {
				if ( $this->has_actual_value( $item ) ) {
					return true;
				}
			}
			return false;
		}

		// Special case for zero values with units (0px, 0em, etc.).
		if ( is_string( $value ) && preg_match( '/^0(px|em|rem|%|vw|vh)?$/', $value ) ) {
			return true;
		}

		// Zero as integer or string is a valid value.
		if ( 0 === $value || '0' === $value ) {
			return true;
		}

		// All other non-empty values are valid.
		return true;
	}

	/**
	 * Get processed styles for a specific device with proper Gutenberg fallback hierarchy.
	 *
	 * Implements individual property fallback (not group fallback) exactly like Gutenberg core:
	 * - Mobile (sm): tries mobile -> tablet -> desktop for EACH property
	 * - Tablet (md): tries tablet -> desktop for EACH property  
	 * - Desktop (lg): desktop only for EACH property
	 *
	 * This ensures proper inheritance: if mobile border color is not set,
	 * it will use tablet border color, then desktop border color as fallback.
	 *
	 * Handles all Gutenberg spacing properties including blockGap for layout containers.
	 *
	 * @since 3.0.0-beta.1
	 * 
	 * @param array  $responsive_controls Complete responsive controls data from block attributes.
	 * @param string $device              Target device key ('sm', 'md', 'lg').
	 * @param string $block_name          The block name for flex text alignment handling.
	 * @return array Processed style array ready for WordPress Style Engine.
	 */
	private function get_device_styles( $responsive_controls, $device, $block_name = '' ) {
		// Get fallback device order for the target device.
		$fallback_devices = $this->device_fallback_order[ $device ] ?? array( 'lg' );

		// Initialize the final compiled styles.
		$compiled_styles = array();

		// Process each style category with proper fallback.
		$this->process_border_styles( $responsive_controls, $fallback_devices, $compiled_styles );
		$this->process_typography_styles( $responsive_controls, $fallback_devices, $compiled_styles, $block_name );
		$this->process_spacing_styles( $responsive_controls, $fallback_devices, $compiled_styles );
		$this->process_shadow_styles( $responsive_controls, $fallback_devices, $compiled_styles );

		return $compiled_styles;
	}

	/**
	 * Process border styles (width, style, radius) with fallback.
	 * 
	 * Handles single borders (borderColor + style.border.width/style/color) and
	 * mixed borders (style.border.top/right/bottom/left) with proper inheritance.
	 * A breakpoint inherits as-is from parent regardless of type mismatch.
	 *
	 * @since 3.0.0-beta.1
	 * @param array $responsive_controls Complete responsive controls data.
	 * @param array $fallback_devices Device fallback order.
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function process_border_styles( $responsive_controls, $fallback_devices, &$compiled_styles ) {
		// Get current device (first in fallback order).
		$current_device = $fallback_devices[0];
		$current_data   = $responsive_controls[ $current_device ] ?? array();
		
		// Check what the current breakpoint has.
		// Border radius is excluded as it should inherit independently.
		$current_has_single_border = isset( $current_data['borderColor'] ) ||
			( isset( $current_data['style']['border'] ) && (
				isset( $current_data['style']['border']['width'] ) ||
				isset( $current_data['style']['border']['style'] ) ||
				isset( $current_data['style']['border']['color'] )
			) );
		
		$current_has_mixed_border = isset( $current_data['style']['border'] ) && (
			isset( $current_data['style']['border']['top'] ) ||
			isset( $current_data['style']['border']['right'] ) ||
			isset( $current_data['style']['border']['bottom'] ) ||
			isset( $current_data['style']['border']['left'] )
		);

		// If current has any border config (excluding radius), use it exclusively (no inheritance).
		if ( $current_has_single_border || $current_has_mixed_border ) {
			// Apply current's border config.
			if ( isset( $current_data['borderColor'] ) ) {
				$compiled_styles['border']['color'] = "var(--wp--preset--color--{$current_data['borderColor']})";
			}
			
			if ( isset( $current_data['style']['border'] ) ) {
				$this->apply_border_data( $current_data['style']['border'], $compiled_styles );
			}
		} else {
			// Current has no border config - inherit from parent breakpoint.
			foreach ( array_slice( $fallback_devices, 1 ) as $parent_device ) {
				$parent_data = $responsive_controls[ $parent_device ] ?? array();
				
				// Check if parent has any border config (excluding radius).
				$parent_has_border = isset( $parent_data['borderColor'] ) || 
					( isset( $parent_data['style']['border'] ) &&
						( isset( $parent_data['style']['border']['width'] ) ||
							isset( $parent_data['style']['border']['style'] ) ||
							isset( $parent_data['style']['border']['color'] ) ||
							isset( $parent_data['style']['border']['top'] ) ||
							isset( $parent_data['style']['border']['right'] ) ||
							isset( $parent_data['style']['border']['bottom'] ) ||
							isset( $parent_data['style']['border']['left'] ) ) );
				
				if ( $parent_has_border ) {
					// Inherit whatever the parent has.
					if ( isset( $parent_data['borderColor'] ) ) {
						$compiled_styles['border']['color'] = "var(--wp--preset--color--{$parent_data['borderColor']})";
					}
					
					if ( isset( $parent_data['style']['border'] ) ) {
						$this->apply_border_data( $parent_data['style']['border'], $compiled_styles );
					}
					break; // Stop after finding first parent with border config.
				}
			}
		}
		
		// Handle border radius inheritance separately - it always inherits if not set.
		if ( ! isset( $current_data['style']['border']['radius'] ) ) {
			foreach ( array_slice( $fallback_devices, 1 ) as $parent_device ) {
				$parent_data = $responsive_controls[ $parent_device ] ?? array();
				
				if ( isset( $parent_data['style']['border']['radius'] ) ) {
					$radius_value = $parent_data['style']['border']['radius'];
					// Convert array format to string format for WordPress Style Engine.
					if ( is_array( $radius_value ) && ! empty( $radius_value ) ) {
						$radius_parts = array();
						$corners      = array( 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' );
						foreach ( $corners as $corner ) {
							$corner_value = isset( $radius_value[ $corner ] ) ? $radius_value[ $corner ] : '';
							// Ensure we have a string value, not an array.
							if ( is_array( $corner_value ) ) {
								$corner_value = '0'; // Default fallback if malformed data.
							}
							$radius_parts[] = '' !== $corner_value ? $corner_value : '0';
						}
						$compiled_styles['border']['radius'] = implode( ' ', $radius_parts );
					} else {
						$compiled_styles['border']['radius'] = $radius_value;
					}
					break;
				}
			}
		} else {
			// Current device has radius set.
			$radius_value = $current_data['style']['border']['radius'];
			// Convert array format to string format for WordPress Style Engine.
			if ( is_array( $radius_value ) && ! empty( $radius_value ) ) {
				$radius_parts = array();
				$corners      = array( 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' );
				foreach ( $corners as $corner ) {
					$corner_value = isset( $radius_value[ $corner ] ) ? $radius_value[ $corner ] : '';
					// Ensure we have a string value, not an array.
					if ( is_array( $corner_value ) ) {
						$corner_value = '0'; // Default fallback if malformed data.
					}
					$radius_parts[] = '' !== $corner_value ? $corner_value : '0';
				}
				$compiled_styles['border']['radius'] = implode( ' ', $radius_parts );
			} else {
				$compiled_styles['border']['radius'] = $radius_value;
			}
		}

		// Handle border styles intelligently.
		$this->ensure_border_styles( $compiled_styles );
	}

	/**
	 * Apply border data to compiled styles.
	 * 
	 * Helper method to process border data and add it to the compiled styles array.
	 * Handles both single border properties (width, style, color, radius) and
	 * mixed border properties (top, right, bottom, left).
	 *
	 * @since 3.0.0-beta.1.1
	 * @param array $border_data Border data from responsive controls.
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function apply_border_data( $border_data, &$compiled_styles ) {
		if ( ! is_array( $border_data ) ) {
			return;
		}

		$sides = array( 'top', 'right', 'bottom', 'left' );

		foreach ( $border_data as $property => $value ) {
			// Skip if no actual value.
			if ( ! $this->has_actual_value( $value ) ) {
				continue;
			}

			// Handle individual sides.
			if ( in_array( $property, $sides ) && is_array( $value ) ) {
				if ( ! isset( $compiled_styles['border'][ $property ] ) ) {
					$compiled_styles['border'][ $property ] = array();
				}

				foreach ( $value as $side_prop => $side_value ) {
					if ( $this->has_actual_value( $side_value ) ) {
						// Handle preset color conversion. for sides.
						if ( 'color' === $side_prop && is_string( $side_value ) ) {
							if ( strpos( $side_value, 'var:preset|color|' ) === 0 ) {
								$color_slug = str_replace( 'var:preset|color|', '', $side_value );
								$compiled_styles['border'][ $property ][ $side_prop ] = "var(--wp--preset--color--{$color_slug})";
							} else {
								$compiled_styles['border'][ $property ][ $side_prop ] = $side_value;
							}
						} elseif ( 'radius' === $side_prop && is_array( $side_value ) && ! empty( $side_value ) ) {
							// Handle border side radius array format conversion.
							$radius_parts = array();
							$corners      = array( 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' );
							foreach ( $corners as $corner ) {
								$radius_parts[] = isset( $side_value[ $corner ] ) && '' !== $side_value[ $corner ] ? $side_value[ $corner ] : '0';
							}
							$compiled_styles['border'][ $property ][ $side_prop ] = implode( ' ', $radius_parts );
						} else {
							// Validate that width and style properties are strings, not arrays.
							if ( in_array( $side_prop, array( 'width', 'style' ) ) && is_array( $side_value ) ) {
								// Skip invalid array values for width, style, and color properties.
								continue;
							}
							$compiled_styles['border'][ $property ][ $side_prop ] = $side_value;
						}
					}
				}
			} else {
				// Handle general properties (width, style, color, radius).
				if ( 'color' === $property && is_string( $value ) ) {
					// Handle preset color conversion.
					if ( strpos( $value, 'var:preset|color|' ) === 0 ) {
						$color_slug                         = str_replace( 'var:preset|color|', '', $value );
						$compiled_styles['border']['color'] = "var(--wp--preset--color--{$color_slug})";
					} else {
						$compiled_styles['border']['color'] = $value;
					}
				} elseif ( 'radius' === $property && is_array( $value ) && ! empty( $value ) ) {
					// Handle border radius array format conversion.
					$radius_parts = array();
					$corners      = array( 'topLeft', 'topRight', 'bottomRight', 'bottomLeft' );
					foreach ( $corners as $corner ) {
						$radius_parts[] = isset( $value[ $corner ] ) && '' !== $value[ $corner ] ? $value[ $corner ] : '0';
					}
					$compiled_styles['border']['radius'] = implode( ' ', $radius_parts );
				} else {
					// For other properties (width, style), only accept string values, not arrays.
					if ( in_array( $property, array( 'width', 'style' ) ) && is_array( $value ) ) {
						// Skip invalid array format for width and style.
						continue;
					}
					$compiled_styles['border'][ $property ] = $value;
				}
			}
		}
	}

	/**
	 * Ensures border styles are properly set for visibility.
	 *
	 * This method handles various scenarios:
	 * - General border width without style
	 * - Individual side widths without styles
	 * - Mixed scenarios with some sides having styles and others not
	 * - Preserves explicitly set 'none' or 'hidden' styles
	 * - Validates and fixes invalid array values in border properties
	 *
	 * @since 3.0.0-beta.1
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function ensure_border_styles( &$compiled_styles ) {
		if ( ! isset( $compiled_styles['border'] ) || ! is_array( $compiled_styles['border'] ) ) {
			return;
		}

		$sides = array( 'top', 'right', 'bottom', 'left' );

		// Remove any invalid array values in individual side properties.
		// This serves as a safety net in case invalid data bypasses earlier validation.
		foreach ( $sides as $side ) {
			if ( isset( $compiled_styles['border'][ $side ] ) && is_array( $compiled_styles['border'][ $side ] ) ) {
				foreach ( array( 'width', 'style', 'color' ) as $prop ) {
					if ( isset( $compiled_styles['border'][ $side ][ $prop ] ) && is_array( $compiled_styles['border'][ $side ][ $prop ] ) ) {
						// Invalid array value - remove to prevent WordPress Style Engine errors.
						unset( $compiled_styles['border'][ $side ][ $prop ] );
					}
				}

				// If the side now has no valid properties, remove it entirely.
				if ( empty( $compiled_styles['border'][ $side ] ) ) {
					unset( $compiled_styles['border'][ $side ] );
				}
			}
		}

		// First, check if we need a general border style.
		// Add style if either width or color is present but style is missing.
		if ( ( ( ! empty( $compiled_styles['border']['width'] ) && $this->has_actual_value( $compiled_styles['border']['width'] ) ) ||
			( ! empty( $compiled_styles['border']['color'] ) && $this->has_actual_value( $compiled_styles['border']['color'] ) ) ) &&
		empty( $compiled_styles['border']['style'] ) ) {
			$compiled_styles['border']['style'] = 'solid';
		}

		// Then, handle individual sides that need styles.
		foreach ( $sides as $side ) {
			if ( isset( $compiled_styles['border'][ $side ] ) && is_array( $compiled_styles['border'][ $side ] ) ) {
				$side_data = $compiled_styles['border'][ $side ];

				// If this side has width or color but no style, it needs a style.
				if ( ( ( isset( $side_data['width'] ) && $this->has_actual_value( $side_data['width'] ) ) ||
					( isset( $side_data['color'] ) && $this->has_actual_value( $side_data['color'] ) ) ) &&
				empty( $side_data['style'] ) ) {
					// First check if there's a general style to inherit.
					if ( ! empty( $compiled_styles['border']['style'] ) ) {
						$compiled_styles['border'][ $side ]['style'] = $compiled_styles['border']['style'];
					} else {
						// No general style, so set solid for this side.
						$compiled_styles['border'][ $side ]['style'] = 'solid';
					}
				}
			}
		}

		// Handle color inheritance for individual sides.
		// If a side has width but no color, it should inherit from general border color if available.
		if ( ! empty( $compiled_styles['border']['color'] ) ) {
			foreach ( $sides as $side ) {
				if ( isset( $compiled_styles['border'][ $side ] ) &&
				is_array( $compiled_styles['border'][ $side ] ) &&
				isset( $compiled_styles['border'][ $side ]['width'] ) &&
				$this->has_actual_value( $compiled_styles['border'][ $side ]['width'] ) &&
				empty( $compiled_styles['border'][ $side ]['color'] ) ) {
					$compiled_styles['border'][ $side ]['color'] = $compiled_styles['border']['color'];
				}
			}
		}

		// Final cleanup: ensure single and mixed borders don't coexist.
		$has_single_border = isset( $compiled_styles['border']['color'] ) ||
			isset( $compiled_styles['border']['width'] ) ||
			isset( $compiled_styles['border']['style'] );

		$has_mixed_border = false;
		foreach ( $sides as $side ) {
			if ( isset( $compiled_styles['border'][ $side ] ) && is_array( $compiled_styles['border'][ $side ] ) ) {
				$has_mixed_border = true;
				break;
			}
		}

		// If both formats coexist, prioritize individual sides and remove general properties.
		if ( $has_single_border && $has_mixed_border ) {
			unset( $compiled_styles['border']['width'] );
			unset( $compiled_styles['border']['style'] );
			// Keep color for potential inheritance by sides.
		}
	}

	/**
	 * Get list of blocks that use flexbox and need justifyContent instead of textAlign.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $block_name The block name to check.
	 * @return bool True if block uses flexbox for text alignment.
	 */
	private function is_flex_text_align_block( $block_name ) {
		/**
		 * Filter the list of blocks that use flexbox and need justifyContent for text alignment.
		 * Pro plugins can extend this list for their button-like blocks.
		 *
		 * @since 3.0.0-beta.1
		 * @param array $flex_blocks List of block names that use flexbox for text alignment.
		 * @return array Filtered list of flex text alignment blocks.
		 */
		$flex_blocks = apply_filters(
			'spectra_flex_text_align_blocks',
			array(
				// Core Spectra blocks with button-like flex behavior.
				'spectra/button',                      // Button block.
				'spectra/modal-child-button',         // Modal  child trigger button.
				'spectra/tabs-child-tab-button',       // Individual tab button.
			
			// Pro blocks will be added via filter in Spectra Pro plugin itself.
			) 
		);

		return in_array( $block_name, $flex_blocks, true );
	}

	/**
	 * Process typography with proper Gutenberg fallback hierarchy.
	 *
	 * Handles fontSize and fontFamily with individual property fallback.
	 * Checks both root level fontSize and nested style.typography.fontSize.
	 * Each typography property falls back independently.
	 *
	 * @since 3.0.0-beta.1
	 * 
	 * @param array  $responsive_controls Complete responsive controls data.
	 * @param array  $fallback_devices    Device fallback order.
	 * @param array  $compiled_styles     Compiled styles passed by reference.
	 * @param string $block_name          The block name for flex text alignment handling.
	 * @return void
	 */
	private function process_typography_styles( $responsive_controls, $fallback_devices, &$compiled_styles, $block_name = '' ) {
		// Only process if typography is in the style_responsive_keys list.
		if ( ! in_array( 'typography', $this->style_responsive_keys ) ) {
			return;
		}
		
		// Process font size with fallback.
		foreach ( $fallback_devices as $device ) {
			$font_size = null;

			// Check for preset font size first.
			if (
				isset( $responsive_controls[ $device ]['fontSize'] ) &&
				$this->has_actual_value( $responsive_controls[ $device ]['fontSize'] )
			) {
				$font_size = "var(--wp--preset--font-size--{$responsive_controls[$device]['fontSize']})";
			} elseif ( // Then check for custom font size.
				isset( $responsive_controls[ $device ]['style']['typography']['fontSize'] ) &&
				$this->has_actual_value( $responsive_controls[ $device ]['style']['typography']['fontSize'] )
			) {
				$font_size = $responsive_controls[ $device ]['style']['typography']['fontSize'];
			}

			// If we found a font size, use it and stop looking.
			if ( $font_size ) {
				$compiled_styles['typography']['fontSize'] = $font_size;
				break;
			}
		}

		// Process font family with fallback.
		foreach ( $fallback_devices as $device ) {
			if (
				isset( $responsive_controls[ $device ]['fontFamily'] ) &&
				$this->has_actual_value( $responsive_controls[ $device ]['fontFamily'] )
			) {
				$compiled_styles['typography']['fontFamily'] =
					"var(--wp--preset--font-family--{$responsive_controls[$device]['fontFamily']})";
				break;
			}
		}

		// Process other typography properties with fallback.
		$typography_properties = array(
			'fontWeight',
			'fontStyle',
			'lineHeight',
			'letterSpacing',
			'textDecoration',
			'textTransform',
			'textAlign',
			'writingMode',
		);

		foreach ( $typography_properties as $property ) {
			foreach ( $fallback_devices as $device ) {
				if (
					isset( $responsive_controls[ $device ]['style']['typography'][ $property ] ) &&
					$this->has_actual_value( $responsive_controls[ $device ]['style']['typography'][ $property ] )
				) {
					// Special handling for textAlign in flex blocks - store for manual CSS generation.
					if ( 'textAlign' === $property && $this->is_flex_text_align_block( $block_name ) ) {
						$text_align_value = $responsive_controls[ $device ]['style']['typography'][ $property ];
						
						// Map textAlign values to justifyContent values.
						$justify_content_value = $text_align_value;
						if ( 'left' === $text_align_value ) {
							$justify_content_value = 'flex-start';
						} elseif ( 'right' === $text_align_value ) {
							$justify_content_value = 'flex-end';
						}
						
						// Store in a custom section for manual CSS generation.
						$compiled_styles['spectra_flex']['justifyContent'] = $justify_content_value;
					} else {
						$compiled_styles['typography'][ $property ] =
							$responsive_controls[ $device ]['style']['typography'][ $property ];
					}
					break;
				}
			}
		}

		// Set default center alignment for flex blocks if no alignment was set.
		if ( $this->is_flex_text_align_block( $block_name ) && ! isset( $compiled_styles['spectra_flex']['justifyContent'] ) ) {
			$compiled_styles['spectra_flex']['justifyContent'] = 'center';
		}
	}

	/**
	 * Process spacing with proper Gutenberg fallback hierarchy.
	 *
	 * Handles padding, margin, AND blockGap with individual property fallback.
	 * Each spacing property (padding.top, margin.left, blockGap, etc.) falls back independently.
	 *
	 * @since 3.0.0-beta.1
	 * 
	 * @param array $responsive_controls Complete responsive controls data.
	 * @param array $fallback_devices    Device fallback order.
	 * @param array $compiled_styles    Compiled styles passed by reference.
	 * @return void
	 */
	private function process_spacing_styles( $responsive_controls, $fallback_devices, &$compiled_styles ) {
		// Only process if spacing is in the style_responsive_keys list.
		if ( ! in_array( 'spacing', $this->style_responsive_keys ) ) {
			return;
		}
		
		// Process padding and margin properties.
		$this->process_spacing_properties( $responsive_controls, $fallback_devices, $compiled_styles );

		// Process block gap separately.
		$this->process_block_gap( $responsive_controls, $fallback_devices, $compiled_styles );
	}

	/**
	 * Process spacing properties (padding/margin) with fallback.
	 * 
	 * Handles both shorthand values (single value for all sides)
	 * and individual side values (top, right, bottom, left).
	 * 
	 * Prioritizes shorthand values over individual sides for consistency.
	 *
	 * @since 3.0.0-beta.1
	 * @param array $responsive_controls Responsive controls data.
	 * @param array $fallback_devices Fallback device order.
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function process_spacing_properties( $responsive_controls, $fallback_devices, &$compiled_styles ) {
		$spacing_types = array(
			'padding' => array( 'top', 'right', 'bottom', 'left' ),
			'margin'  => array( 'top', 'right', 'bottom', 'left' ),
		);

		foreach ( $spacing_types as $type => $sides ) {
			// Check for shorthand first (e.g., padding: 10px).
			foreach ( $fallback_devices as $device ) {
				$value = $responsive_controls[ $device ]['style']['spacing'][ $type ] ?? null;

				if ( isset( $value ) && is_string( $value ) && $this->has_actual_value( $value ) ) {
					$compiled_styles['spacing'][ $type ] = $value;
					break;
				}
			}

			// Process individual sides if no shorthand was found.
			if ( ! isset( $compiled_styles['spacing'][ $type ] ) ) {
				foreach ( $sides as $side ) {
					foreach ( $fallback_devices as $device ) {
						$value = $responsive_controls[ $device ]['style']['spacing'][ $type ][ $side ] ?? null;

						if ( isset( $value ) && $this->has_actual_value( $value ) ) {
							$compiled_styles['spacing'][ $type ][ $side ] = $value;
							break;
						}
					}
				}
			}
		}
	}

	/**
	 * Process block gap with proper fallback hierarchy.
	 * 
	 * Block gap is used for spacing between child elements
	 * in container blocks (columns, stack, etc).
	 *
	 * @since 3.0.0-beta.1
	 * @param array $responsive_controls Responsive controls data.
	 * @param array $fallback_devices Fallback device order.
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function process_block_gap( $responsive_controls, $fallback_devices, &$compiled_styles ) {
		foreach ( $fallback_devices as $device ) {
			$gap = $responsive_controls[ $device ]['style']['spacing']['blockGap'] ?? null;

			if ( isset( $gap ) && $this->has_actual_value( $gap ) ) {
				$compiled_styles['spacing']['blockGap'] = $gap;
				break;
			}
		}
	}

	/**
	 * Process shadow styles with proper fallback hierarchy.
	 * 
	 * Box shadow effects can be applied responsively and
	 * will follow the device fallback order.
	 *
	 * @since 3.0.0-beta.1
	 * @param array $responsive_controls Responsive controls data.
	 * @param array $fallback_devices Fallback device order.
	 * @param array $compiled_styles Compiled styles passed by reference.
	 * @return void
	 */
	private function process_shadow_styles( $responsive_controls, $fallback_devices, &$compiled_styles ) {
		// Only process if shadow is in the style_responsive_keys list.
		if ( ! in_array( 'shadow', $this->style_responsive_keys ) ) {
			return;
		}
		
		foreach ( $fallback_devices as $device ) {
			$shadow = $responsive_controls[ $device ]['style']['shadow'] ?? null;

			if ( isset( $shadow ) && $this->has_actual_value( $shadow ) ) {
				$compiled_styles['shadow'] = $shadow;
				break;
			}
		}
	}

	/**
	 * Generate layout CSS as a string for a specific device.
	 * 
	 * Layout CSS includes:
	 * - Display type (flex, grid, block)
	 * - Alignment properties
	 * - Gap settings
	 * - Content width constraints
	 *
	 * @since 3.0.0-beta.1
	 * @param array  $responsive_controls The responsive controls data.
	 * @param string $device              The target device key ('sm', 'md', 'lg').
	 * @param string $selector            The CSS selector for the block.
	 * @param string $child_reset_selector The selector for child margin resets.
	 * @return string The generated layout CSS string.
	 */
	private function generate_layout_css( $responsive_controls, $device, $selector, $child_reset_selector ) {
		// Get fallback device order for the target device.
		$fallback_devices = $this->device_fallback_order[ $device ] ?? array( 'lg' );
		$layout_css       = '';

		$gap = null;
		foreach ( $fallback_devices as $fallback_device ) {
			$gap = $responsive_controls[ $fallback_device ]['style']['spacing']['blockGap'] ?? null;

			if ( $this->has_actual_value( $gap ) ) {
				break;
			}       
		}

		// Find the first layout definition in the fallback chain.
		foreach ( $fallback_devices as $fallback_device ) {
			if (
				! isset( $responsive_controls[ $fallback_device ]['layout'] ) ||
				! $this->has_actual_value( $responsive_controls[ $fallback_device ]['layout'] )
			) {
				continue;
			}

			// Get layout and gap values.
			$layout = $responsive_controls[ $fallback_device ]['layout'];

			// Generate layout CSS using WordPress core function with a temporary ID.
			// We use a temporary ID to avoid conflicts with WordPress core selectors.
			$layout_css = wp_get_layout_style( $selector . '-temp-id', $layout, true, $gap );

			// Replace the temporary ID with the actual selector.
			$core_css = str_replace( $selector . '-temp-id', $selector, $layout_css );

			// Generate our custom layout CSS to supplement core CSS.
			$custom_css = $this->generate_custom_layout_css( $selector, $layout, $gap, $core_css, $child_reset_selector );

			// Merge core and custom CSS without duplicates.
			$layout_css = $this->merge_layout_css( $core_css, $custom_css );

			break;
		}

		// Always return a string, even if empty.
		return $layout_css;
	}

	/**
	 * Generate custom layout CSS for a specific layout type.
	 * 
	 * Provides specialized CSS for different layout types:
	 * - grid: CSS Grid layout
	 * - flex: Flexbox layout
	 * - constrained: Content width constraints
	 * - flow/default: Standard block flow
	 *
	 * Note: This function generates CSS that is specific to the given layout type,
	 * but it does not generate CSS that is specific to the given device.
	 * The device-specific CSS is handled by the generate_layout_css function.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $selector The CSS selector for the block.
	 * @param array  $layout   The layout data.
	 * @param string $gap      The block gap value.
	 * @param string $core_css The core CSS generated by WordPress.
	 * @param string $child_reset_selector The selector for child margin resets.
	 * @return string The generated custom layout CSS string.
	 */
	private function generate_custom_layout_css( $selector, $layout, $gap, $core_css, $child_reset_selector ) {
		$css  = '';
		$type = $layout['type'] ?? 'default';

		// Common alignment styles for flow and constrained layouts.
		$alignment_styles = "
			{$selector} > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }
			{$selector} > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }
			{$selector} > .aligncenter { margin-left: auto !important; margin-right: auto !important; }
		";

		// Common spacing styles for flow and constrained layouts.
		// Using 0-1-1 specificity - wins over Astra's 0-1-1 due to CSS cascade order.
		$gap_value      = $gap ?? 'var(--wp--style--block-gap, 1em)';
		$spacing_styles = "
			{$child_reset_selector} > :first-child { margin-block-start: 0; }
			{$child_reset_selector} > :last-child { margin-block-end: 0; }
			{$child_reset_selector} > * { margin-block-start: {$gap_value}; margin-block-end: 0; }
		";
		
		// Generate CSS based on layout type.
		switch ( $type ) {
			case 'grid':
				// CSS Grid layout.
				$css .= "{$selector} { display: grid;";

				// Add gap with fallback.
				$css .= ' gap: ' . ( $gap ?? 'var(--wp--style--block-gap, 1em)' ) . ';';

				$css .= ' }';
				
				// Grid children should have no margin - use 0-1-1 (wins via cascade order).
				$css .= " {$child_reset_selector} > * { margin: 0; }";
				break;

			case 'flex':
				// Flexbox layout with configurable properties.
				$flex_wrap          = $layout['flexWrap'] ?? $layout['wrap'] ?? 'wrap';
				$justify            = $layout['justifyContent'] ?? 'flex-start';
				$orientation        = $layout['orientation'] ?? 'horizontal';
				$vertical_alignment = $layout['verticalAlignment'] ?? 'center';

				// Convert WordPress justify values to CSS values.
				switch ( $justify ) {
					case 'left':
						$justify = 'flex-start';
						break;
					case 'right':
						$justify = 'flex-end';
						break;
					case 'center':
						$justify = 'center';
						break;
					case 'stretch':
						$justify = 'space-between';
						break;
				}

				// Convert vertical alignment values.
				switch ( $vertical_alignment ) {
					case 'top':
						$align_items = 'flex-start';
						break;
					case 'bottom':
						$align_items = 'flex-end';
						break;
					case 'stretch':
						$align_items = 'stretch';
						break;
					case 'center':
					default:
						$align_items = 'center';
						break;
				}

				// Set direction based on orientation.
				$direction = ( 'vertical' === $orientation ) ? 'column' : 'row';

				// Start building the flex container CSS.
				$css .= "{$selector} { display: flex;";
				
				// Add gap with fallback.
				$css .= ' gap: ' . ( $gap ?? 'var(--wp--style--block-gap, 1em)' ) . ';';

				// Add flex properties.
				$css .= " flex-wrap: {$flex_wrap};";
				
				if ( 'column' === $direction ) {
					$css .= ' flex-direction: column;';
					$css .= " align-items: {$justify};"; // In column, justify becomes align-items.
				} else {
					$css .= ' flex-direction: row;';
					$css .= " justify-content: {$justify};";
					$css .= " align-items: {$align_items};";
				}

				$css .= ' }';
				
				// Flex children should have no margin - use 0-1-1 (wins via cascade order).
				$css .= " {$child_reset_selector} > * { margin: 0; }";
				break;

			case 'constrained':
				// Constrained width layout.
				$css .= "{$selector} { display: block; }";
				$css .= $alignment_styles;

				// Get content and wide sizes from layout settings.
				$content_size    = isset( $layout['contentSize'] ) ? $layout['contentSize'] : 'var(--wp--style--global--content-size)';
				$wide_size       = isset( $layout['wideSize'] ) ? $layout['wideSize'] : 'var(--wp--style--global--wide-size)';
				$justify_content = isset( $layout['justifyContent'] ) ? $layout['justifyContent'] : 'center';
				$margin_left     = 'left' === $justify_content ? '0' : 'auto';
				$margin_right    = 'right' === $justify_content ? '0' : 'auto';
				
				// Constrained-specific styles for content width.
				$css .= "{$selector} > :where(:not(.alignleft):not(.alignright):not(.alignfull)) { max-width: {$content_size}; margin-left: {$margin_left} !important; margin-right: {$margin_right} !important; }";
				$css .= "{$selector} > .alignwide { max-width: {$wide_size}; }";
				
				$css .= $spacing_styles;
				break;

			case 'default':
			case 'flow':
				// Default block flow layout.
				$css .= "{$selector} { display: block; }";
				$css .= $alignment_styles;
				$css .= $spacing_styles;
				break;
		}

		return $css;
	}

	/**
	 * Merge core and custom CSS for a single device layout.
	 * 
	 * Combines CSS from WordPress core layout functions with
	 * our custom layout CSS, avoiding duplicates.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $core_css CSS generated by WordPress core.
	 * @param string $custom_css Custom CSS generated by this class.
	 * @return string Merged CSS string without duplicates.
	 */
	private function merge_layout_css( $core_css, $custom_css ) {
		// If core CSS is empty, just return custom CSS.
		if ( empty( $core_css ) ) {
			return $custom_css;
		}

		// If custom CSS is empty, just return core CSS.
		if ( empty( $custom_css ) ) {
			return $core_css;
		}

		// For simple cases, just concatenate the CSS strings.
		return $core_css . ' ' . $custom_css;
	}

	/**
	 * Generates CSS for grid layout positioning within style attributes.
	 *
	 * This method processes style.layout properties to generate CSS for grid positioning,
	 * including column and row placement. It follows the device fallback hierarchy
	 * to ensure proper responsive behavior.
	 *
	 * @since 3.0.0-beta.1
	 * @param array  $responsive_controls Responsive controls data containing style.layout properties.
	 * @param string $device              Device type to generate CSS for ('lg', 'md', 'sm').
	 * @param string $selector            CSS selector to target with the generated CSS.
	 * @return string Generated CSS for grid positioning.
	 */
	private function generate_style_layout_css( $responsive_controls, $device, $selector ) {
		// Only process if layout is in the style_responsive_keys list.
		if ( ! in_array( 'layout', $this->style_responsive_keys ) ) {
			return '';
		}
		
		// Get fallback device order for the target device.
		$fallback_devices = $this->device_fallback_order[ $device ] ?? array( 'lg' );
		$layout_css       = '';

		// Find the first layout definition in the fallback chain.
		foreach ( $fallback_devices as $fallback_device ) {
			// Get layout from style.layout property.
			$layout = $responsive_controls[ $fallback_device ]['style']['layout'] ?? array();

			// Skip if no layout is defined for this device.
			if ( empty( $layout ) ) {
				continue;
			}

			// Initialize array to hold CSS declarations.
			$css_declarations = array();

			// Process self-stretch property.
			$self_stretch = isset( $layout['selfStretch'] ) ? $layout['selfStretch'] : null;

			// Set flex-basis and box-sizing properties based on self-stretch value.
			if ( 'fixed' === $self_stretch && isset( $layout['flexSize'] ) ) {
				$css_declarations['flex-basis'] = $layout['flexSize'];
				$css_declarations['box-sizing'] = 'border-box';
			} elseif ( 'fill' === $self_stretch ) {
				$css_declarations['flex-grow'] = '1';
			}

			// Process grid column positioning.
			$column_start = isset( $layout['columnStart'] ) ? $layout['columnStart'] : null;
			$column_span  = isset( $layout['columnSpan'] ) ? $layout['columnSpan'] : null;
			
			// Set grid-column property based on available values.
			if ( $column_start && $column_span ) {
				// Both start position and span are defined.
				$css_declarations['grid-column'] = "$column_start / span $column_span";
			} elseif ( $column_start ) {
				// Only start position is defined.
				$css_declarations['grid-column'] = "$column_start";
			} elseif ( $column_span ) {
				// Only span is defined.
				$css_declarations['grid-column'] = "span $column_span";
			}

			// Process grid row positioning.
			$row_start = isset( $layout['rowStart'] ) ? $layout['rowStart'] : null;
			$row_span  = isset( $layout['rowSpan'] ) ? $layout['rowSpan'] : null;
			
			// Set grid-row property based on available values.
			if ( $row_start && $row_span ) {
				// Both start position and span are defined.
				$css_declarations['grid-row'] = "$row_start / span $row_span";
			} elseif ( $row_start ) {
				// Only start position is defined.
				$css_declarations['grid-row'] = "$row_start";
			} elseif ( $row_span ) {
				// Only span is defined.
				$css_declarations['grid-row'] = "span $row_span";
			}

			// Generate CSS if we have any declarations.
			if ( ! empty( $css_declarations ) ) {
				// Use WordPress Style Engine to generate valid CSS.
				$layout_css = wp_style_engine_get_stylesheet_from_css_rules(
					array(
						array(
							'selector'     => $selector,
							'declarations' => $css_declarations,
						),
					),
					array(
						'prettify' => false, // Keep CSS compact.
					)
				);
			}
			
			// Stop after finding the first valid layout in the fallback chain.
			break;
		}

		// Always return a string, even if empty.
		return $layout_css;
	}

	/**
	 * Recursively process blocks to ensure unique spectraIds.
	 * 
	 * @since 3.0.0-beta.1
	 * @param array $blocks The blocks to process.
	 * @param array $seen_ids Reference to array tracking seen IDs.
	 * @param bool  $modified Reference to flag indicating if any IDs were changed.
	 * @return array The processed blocks with unique IDs.
	 */
	private function process_blocks_for_unique_ids( $blocks, &$seen_ids, &$modified ) {
		foreach ( $blocks as &$block ) {
			// Skip if not a Spectra block.
			if ( ! isset( $block['blockName'] ) || ! $this->should_apply_responsive_controls( $block ) ) {
				// Still process inner blocks if they exist.
				if ( ! empty( $block['innerBlocks'] ) ) {
					$block['innerBlocks'] = $this->process_blocks_for_unique_ids( $block['innerBlocks'], $seen_ids, $modified );
				}
				continue;
			}

			// Check if this block has a spectraId.
			if ( isset( $block['attrs']['spectraId'] ) ) {
				$current_id = $block['attrs']['spectraId'];

				// If we've seen this ID before, generate a new one.
				if ( isset( $seen_ids[ $current_id ] ) ) {
					$new_id                      = $this->generate_unique_spectra_id();
					$block['attrs']['spectraId'] = $new_id;
					$seen_ids[ $new_id ]         = true;
					$modified                    = true;
				} else {
					// Mark this ID as seen.
					$seen_ids[ $current_id ] = true;
				}
			} else {
				// No ID exists, generate one.
				$new_id                      = $this->generate_unique_spectra_id();
				$block['attrs']['spectraId'] = $new_id;
				$seen_ids[ $new_id ]         = true;
				$modified                    = true;
			}

			// Process inner blocks recursively.
			if ( ! empty( $block['innerBlocks'] ) ) {
				$block['innerBlocks'] = $this->process_blocks_for_unique_ids( $block['innerBlocks'], $seen_ids, $modified );
			}
		}

		return $blocks;
	}

	/**
	 * Generate a unique spectraId.
	 *
	 * @since 3.0.0-beta.1
	 * @return string A unique ID for the block.
	 */
	private function generate_unique_spectra_id() {
		return 'spectra-' . wp_generate_uuid4();
	}

	/**
	 * Remove conflicting inline styles from core/image block elements.
	 *
	 * Removes CSS properties that conflict with our responsive controls from both
	 * the figure element and img elements.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $block_content The block's HTML content.
	 * @param array  $responsive_controls The responsive controls data (unused but kept for signature).
	 * @return string Modified HTML content with inline styles removed.
	 */
	private function remove_core_image_inline_styles( $block_content, $responsive_controls ) {
		// Use WordPress HTML Tag Processor to safely modify elements.
		$processor = new WP_HTML_Tag_Processor( $block_content );

		// First, remove spacing properties from figure elements.
		while ( $processor->next_tag( 'figure' ) ) {
			$style_attr = $processor->get_attribute( 'style' );
			if ( empty( $style_attr ) ) {
				continue;
			}

			// Parse the style attribute.
			$styles   = $this->parse_inline_styles( $style_attr );
			$modified = false;

			// Remove spacing properties that conflict with our responsive controls.
			$spacing_properties_to_remove = array(
				'margin',
				'margin-top',
				'margin-right',
				'margin-bottom',
				'margin-left',
			);

			foreach ( $spacing_properties_to_remove as $property ) {
				if ( isset( $styles[ $property ] ) ) {
					unset( $styles[ $property ] );
					$modified = true;
				}
			}

			// Update the style attribute if we removed any properties.
			if ( $modified ) {
				if ( empty( $styles ) ) {
					// Remove the entire style attribute if no styles remain.
					$processor->remove_attribute( 'style' );
				} else {
					// Rebuild the style attribute with remaining styles.
					$new_style = $this->build_inline_styles( $styles );
					$processor->set_attribute( 'style', $new_style );
				}
			}
		}

		// Reset processor to start from the beginning for img elements.
		$processor = new WP_HTML_Tag_Processor( $processor->get_updated_html() );

		// Then, remove entire style attribute from img elements.
		while ( $processor->next_tag( 'img' ) ) {
			// Simply remove the entire style attribute from img elements.
			$processor->remove_attribute( 'style' );
		}

		return $processor->get_updated_html();
	}

	/**
	 * Parse inline CSS styles into an associative array.
	 *
	 * @since 3.0.0-beta.1
	 * @param string $style_attr The style attribute value.
	 * @return array Associative array of CSS property => value pairs.
	 */
	private function parse_inline_styles( $style_attr ) {
		$styles = array();

		// Split by semicolon and process each declaration.
		$declarations = explode( ';', $style_attr );

		foreach ( $declarations as $declaration ) {
			$declaration = trim( $declaration );
			if ( empty( $declaration ) ) {
				continue;
			}

			// Split by first colon to separate property and value.
			$colon_pos = strpos( $declaration, ':' );
			if ( false === $colon_pos ) {
				continue;
			}

			$property = trim( substr( $declaration, 0, $colon_pos ) );
			$value    = trim( substr( $declaration, $colon_pos + 1 ) );

			if ( ! empty( $property ) && ! empty( $value ) ) {
				$styles[ $property ] = $value;
			}
		}

		return $styles;
	}

	/**
	 * Build inline CSS styles from an associative array.
	 *
	 * @since 3.0.0-beta.1
	 * @param array $styles Associative array of CSS property => value pairs.
	 * @return string CSS style string.
	 */
	private function build_inline_styles( $styles ) {
		$declarations = array();

		foreach ( $styles as $property => $value ) {
			$declarations[] = esc_attr( $property ) . ':' . esc_attr( $value );
		}

		return implode( ';', $declarations );
	}


	/**
	 * Generate orientation reverse CSS for container blocks.
	 * 
	 * Creates device-specific CSS rules for orientation reverse functionality
	 * with proper media queries and orientation-aware flex-direction values.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param string $spectra_id The unique block instance ID for CSS targeting.
	 * @param array  $attrs      The block attributes including responsiveControls.
	 * @return string Generated CSS or empty string if no orientation reverse is needed.
	 */
	private function generate_orientation_reverse_css( $spectra_id, $attrs ) {
		$responsive_controls = $attrs['responsiveControls'] ?? array();
		$layout              = $attrs['layout'] ?? array();
		$orientation_reverse = $attrs['orientationReverse'] ?? false;
		
		// Check if any breakpoint has orientation reverse enabled
		$has_orientation_reverse = $orientation_reverse;
		if ( ! $has_orientation_reverse && ! empty( $responsive_controls ) ) {
			foreach ( array( 'lg', 'md', 'sm' ) as $device ) {
				if ( isset( $responsive_controls[ $device ]['orientationReverse'] ) && $responsive_controls[ $device ]['orientationReverse'] ) {
					$has_orientation_reverse = true;
					break;
				}
			}
		}
		
		if ( ! $has_orientation_reverse ) {
			return '';
		}
		
		$css_rules = array();
		$selector  = ".wp-block-spectra-container.wp-block-spectra-container[data-spectra-id='{$spectra_id}']";
		
		// Collect orientation data for each device from responsive controls
		$orientation_devices = array();
		$default_orientation = $layout['orientation'] ?? 'horizontal';
		
		foreach ( array( 'lg', 'md', 'sm' ) as $device ) {
			if ( isset( $responsive_controls[ $device ]['layout']['orientation'] ) ) {
				$orientation_devices[ $device ] = $responsive_controls[ $device ]['layout']['orientation'];
			}
		}
		
		// Desktop orientation
		$desktop_orientation = $orientation_devices['lg'] ?? $default_orientation;
		
		// Calculate inherited orientation reverse values with proper inheritance chain
		// Desktop: Use base attribute or explicit desktop setting
		$desktop_reverse = $orientation_reverse || ( isset( $responsive_controls['lg']['orientationReverse'] ) && ! empty( $responsive_controls['lg']['orientationReverse'] ) );
		
		// Tablet: Check if explicitly set (including false), otherwise inherit from desktop
		if ( array_key_exists( 'orientationReverse', $responsive_controls['md'] ?? array() ) ) {
			// Explicit tablet setting exists (could be true or false) - use it
			$tablet_reverse = ! empty( $responsive_controls['md']['orientationReverse'] );
		} else {
			// No explicit tablet setting - inherit from desktop
			$tablet_reverse = $desktop_reverse;
		}
		
		// Mobile: Check if explicitly set (including false), otherwise inherit from tablet
		if ( array_key_exists( 'orientationReverse', $responsive_controls['sm'] ?? array() ) ) {
			// Explicit mobile setting exists (could be true or false) - use it
			$mobile_reverse = ! empty( $responsive_controls['sm']['orientationReverse'] );
		} else {
			// No explicit mobile setting - inherit from tablet (which may have inherited from desktop)
			$mobile_reverse = $tablet_reverse;
		}
		
		// Desktop rules (min-width: 1024px)
		if ( $desktop_reverse ) {
			$desktop_flex_direction = ( 'vertical' === $desktop_orientation ) ? 'column-reverse' : 'row-reverse';
			$css_rules[]            = '@media (min-width: 1024px) {';
			$css_rules[]            = "  {$selector}.is-{$desktop_orientation}-desktop {";
			$css_rules[]            = "    flex-direction: {$desktop_flex_direction} !important;";
			$css_rules[]            = '  }';
			$css_rules[]            = '}';
		}
		
		// Tablet rules (768px to 1023px)
		if ( $tablet_reverse ) {
			$tablet_orientation    = $orientation_devices['md'] ?? $desktop_orientation;
			$tablet_flex_direction = ( 'vertical' === $tablet_orientation ) ? 'column-reverse' : 'row-reverse';
			$css_rules[]           = '@media (min-width: 768px) and (max-width: 1023px) {';
			$css_rules[]           = "  {$selector}.is-{$tablet_orientation}-tablet,";
			$css_rules[]           = "  {$selector}.is-{$tablet_orientation}-tablet-from-desktop {";
			$css_rules[]           = "    flex-direction: {$tablet_flex_direction} !important;";
			$css_rules[]           = '  }';
			$css_rules[]           = '}';
		}
		
		// Mobile rules (max-width: 767px)
		if ( $mobile_reverse ) {
			$mobile_orientation    = $orientation_devices['sm'] ?? ( $orientation_devices['md'] ?? $desktop_orientation );
			$mobile_flex_direction = ( 'vertical' === $mobile_orientation ) ? 'column-reverse' : 'row-reverse';
			$css_rules[]           = '@media (max-width: 767px) {';
			$css_rules[]           = "  {$selector}.is-{$mobile_orientation}-mobile,";
			$css_rules[]           = "  {$selector}.is-{$mobile_orientation}-mobile-from-tablet,";
			$css_rules[]           = "  {$selector}.is-{$mobile_orientation}-mobile-from-desktop {";
			$css_rules[]           = "    flex-direction: {$mobile_flex_direction} !important;";
			$css_rules[]           = '  }';
			$css_rules[]           = '}';
		}
		
		return implode( "\n", $css_rules );
	}

	/**
	 * Get the default layout configuration for a specific block.
	 *
	 * Returns the default layout attributes that should be applied to blocks
	 * that don't have layout information defined. This is primarily used by
	 * the preview CSS generation system.
	 *
	 * @since 3.0.0-beta.1
	 *
	 * @param string $block_name The name of the block (e.g., 'spectra/icons').
	 * @return array Default layout configuration or empty array if none defined.
	 */
	public function get_block_default_layout( $block_name ) {
		return $this->blocks_default_layout[ $block_name ] ?? array();
	}
}
